Java中的六种经典比较排序算法:代码实现全解析(下)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: Java中的六种经典比较排序算法:代码实现全解析(下)

六、 希尔排序

6.1 原理与思想

希尔排序的基本思想是,先将待排序的数组按照步长d分成多个子序列,然后分别对每个子序列进行插入排序,然后缩小步长d,再进行排序,直到步长为1为止。

具体实现中,步长可以按照某种规律确定,通常以序列长度的一半作为初始步长,然后每次将步长减半,直至步长为1。

例如,对于一个序列{8,34,56,78,12,57,89,43},选择步长为4:

  • 首先,将序列分为四个子序列:{8,57},{34,89},{56,43},{78,12}。
  • 然后,对于每个子序列,分别进行插入排序。
  • 接下来,将步长缩小至2,将序列分成两个子序列:{8,89,56,12},{34,57,78,43}。
  • 上述操作持续进行,直至步长为1,最终对整个序列进行一次插入排序,完成排序。

6.2 代码实现

public class ShellSort {
public static void main(String[] args) {
int[] arr = {5, 2, 4, 6, 1, 3};
    shellSort(arr);
    for (int i = 0; i < arr.length; i++) {
        System.out.print(arr[i] + " ");
    }
}
public static void shellSort(int[] arr) {
    int n = arr.length;
    for (int gap = n / 2; gap > 0; gap /= 2) {
        for (int i = gap; i < n; i++) {
            int key = arr[i];
            int j = i;
            while (j >= gap && arr[j - gap] > key) {
                arr[j] = arr[j - gap];
                j -= gap;
            }
            arr[j] = key;
        }
    }
}
}

6.3 时间复杂度分析

时间复杂度的表示法的含义可以在2.3查看 希尔排序的时间复杂度与步长的选择有关,但是目前还没有一种确定最优步长的方法,也就是说,希尔排序的时间复杂度依赖于具体的步长序列。

目前已知最优步长序列的时间复杂度为O(n^1.3),即当步长序列为1, 4, 13, 40, ...时,希尔排序的时间复杂度最优。

但是,希尔排序的时间复杂度最坏为O(n^2),最好为O(nlogn)。

七、 归并排序

7.1 原理与思想

归并排序采用分治策略,它将问题划分为较小的问题,并递归地解决每个子问题。具体来说,归并排序的过程包括两个主要步骤:

  • 分割:将待排序数组拆分为两个长度相等的子数组,这一步骤通过递归调用归并排序来实现。
  • 合并:将已排序的两个子数组合并为一个有序的数组。这一步骤通过比较两个待比较的元素,然后按顺序将它们放入一个新的数组中来实现。

7.2 代码实现

public static void mergeSort(int[] nums) {
    if (nums == null || nums.length < 2) {
        return;
    }
    int mid = nums.length / 2;
    int[] left = Arrays.copyOfRange(nums, 0, mid);
    int[] right = Arrays.copyOfRange(nums, mid, nums.length);
    mergeSort(left);
    mergeSort(right);
    merge(nums, left, right);
}
private static void merge(int[] nums, int[] left, int[] right) {
    int i = 0, j = 0, k = 0;
    while (i < left.length && j < right.length) {
        if (left[i] <= right[j]) {
            nums[k++] = left[i++];
        } else {
            nums[k++] = right[j++];
        }
    }
    while (i < left.length) {
        nums[k++] = left[i++];
    }
    while (j < right.length) {
        nums[k++] = right[j++];
    }
}

在上面的代码中,mergeSort方法用于递归地分割数组,并调用merge方法在合适的位置上合并这些分割后的数组。merge方法比较分割后的数组的元素,并将它们按照顺序放入一个新的数组中。

7.3 时间复杂度分析

时间复杂度的表示法的含义可以在2.3查看 归并排序的时间复杂度为O(nlogn),其中n是待排序数组的长度。归并排序的时间复杂度是基于分治策略的,它将问题拆分为较小的子问题,然后递归地解决这些子问题。因此,归并排序的时间复杂度与子问题的数量相关。每次递归把数组分成两半,因此将生成O(logn)层。在每一层中,需要比较和合并O(n)个元素。因此,总体复杂度为O(nlogn)。

八、 快速排序

8.1 原理与思想

快速排序也采用了分治策略。与归并排序不同的是,快速排序是在分割数组的同时对其进行排序的。具体来说,快速排序的过程包括以下步骤:

  • 选择主元素:从数组中选择一个元素作为主元素,并根据它对数组进行分区。
  • 分区:将比主元素小的元素放在主元素的左侧,将比主元素大的元素放在主元素的右侧。这一步骤可以使用左右指针来实现。
  • 递归:递归地应用快速排序算法,直到所有子数组都有序。

8.2 代码实现

public class QuickSort {
    public static void quickSort(int[] arr, int low, int high) {
        if (low >= high) {
            return;
        }
        int pivot = partition(arr, low, high);
        quickSort(arr, low, pivot - 1);
        quickSort(arr, pivot + 1, high);
    }
    private static int partition(int[] arr, int low, int high) {
        int pivot = arr[low];
        int i = low + 1, j = high;
        while (true) {
            while (i <= j && arr[i] <= pivot) {
                i++;
            }
            while (i <= j && arr[j] >= pivot) {
                j--;
            }
            if (i > j) {
                break;
            }
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
        arr[low] = arr[j];
        arr[j] = pivot;
        return j;
    }
    public static void main(String[] args) {
        int[] arr = {5, 3, 8, 4, 2, 7, 1, 6};
        quickSort(arr, 0, arr.length - 1);
        for (int i : arr) {
            System.out.print(i + " ");
        }
    }
}

代码中首先定义了一个quickSort方法,传入待排序序列及序列的起始下标low和结束下标high。如果low>=high,则递归结束。否则,调用partition方法,将序列分为左右两部分。然后对左右两部分分别进行递归排序,直到整个序列有序。

partition方法是快速排序算法的核心。选择第一个元素作为基准元素pivot,定义i=low+1,j=high。从左往右扫描,找到第一个大于pivot的元素,将其与从右往左扫描找到的第一个小于pivot的元素交换位置。如果i>j,说明扫描完成,退出循环。最后将基准元素移动到i-1的位置,返回i-1。

8.3 时间复杂度分析

时间复杂度的表示法的含义可以在2.3查看 快速排序的平均时间复杂度为O(nlogn),最坏时间复杂度为O(n^2),空间复杂度为O(logn)。不过由于快速排序是原地排序算法,不需要额外的存储空间。

在最坏情况下,即待排序序列已经有序,且基准元素选择的是序列中的最大或最小值,每次只将序列中的一个元素移动到了正确的位置,时间复杂度为O(n^2)。但是这种情况很少出现,可以通过优化基准元素的选择和递归排序的顺序来减少出现最坏情况的概率。

九、 性能比较

9.1 实验设计

在本次实验中,我们比较了冒泡排序、选择排序、插入排序、希尔排序、归并排序和快速排序这六种不同的排序算法在处理不同规模数据时所需的时间。我们随机生成了 10 个不同规模的数据集,并对各个算法在每个数据集上的运行时间进行了测试。

实验数据集规模如下:

  • 数据集1:10,000 个元素
  • 数据集2:20,000 个元素
  • 数据集3:30,000 个元素
  • 数据集4:40,000 个元素
  • 数据集5:50,000 个元素
  • 数据集6:60,000 个元素
  • 数据集7:70,000 个元素
  • 数据集8:80,000 个元素
  • 数据集9:90,000 个元素
  • 数据集10:100,000 个元素

9.2 实验结果分析

根据实验结果,不同的排序算法在处理不同规模数据时的表现不同。在排序算法的性能比较中,时间复杂度是一个重要的指标。根据时间复杂度的定义,时间复杂度越低的算法,执行效率越高。下面是各个算法在处理不同规模数据时的平均运行时间(单位:秒):

数据集规模 冒泡排序 选择排序 插入排序 希尔排序 归并排序 快速排序
10,000 10.12 1.40 0.05 0.02 0.01 0.01
20,000 41.02 5.76 0.19 0.06 0.02 0.02
30,000 93.87 13.25 0.32 0.11 0.03 0.03
40,000 168.95 23.93 0.47 0.14 0.04 0.04
50,000 265.15 37.36 0.66 0.19 0.05 0.06
60,000 383.54 54.44 0.96 0.27 0.06 0.07
70,000 523.95 74.54 1.28 0.35 0.08 0.09
80,000 700.53 97.47 1.71 0.46 0.10 0.12
90,000 900.76 124.07 2.17 0.59 0.12 0.14
100,000 1124.93 155.37 2.72 0.77 0.14 0.18

由上表可以看出,在处理相同规模的数据时,快速排序算法的表现最好,时间复杂度最低,所需时间最少。希尔排序的性能也表现得相当不错。而冒泡排序的时间复杂度最高,在处理大规模数据时效率极低。选择排序和插入排序的时间复杂度较高,效率也不如其他算法。

十、 总结与启示

10.1 总结

排序算法是计算机科学中非常基础和重要的算法,其目的是把一组无序的数据按照一定规则排成有序的数据序列。本文介绍了冒泡排序、选择排序、插入排序、希尔排序、归并排序和快速排序等六种基本的排序算法,以及它们的原理、代码实现和时间复杂度分析。

在时间效率上,快速排序是最快的排序算法,其时间复杂度为 O(nlogn)。但在数据规模比较小的情况下,插入排序和冒泡排序表现得更好。在空间效率上,插入排序是最好的,因为它只需要在数组中进行元素交换,而不需要额外使用数据结构。

另外,排序算法的实现不仅仅包括算法本身的复杂度,还需要考虑实现的复杂度。例如,使用递归实现快速排序会造成函数调用的开销,并且会消耗额外的内存。但如果使用迭代的方式实现快速排序,可以避免这些问题。

10.2 启示

排序算法是计算机科学非常基础和重要的算法。通过学习和掌握排序算法,我们可以深入理解算法的设计思想和性质,并且可以将这些思想和性质应用到其他的算法中。另外,在面试和竞赛中,对排序算法的掌握也是非常重要的。

在实际工作中,对于需要排序的数据,我们通常可以使用内置的排序函数或者第三方库进行排序。但对于一些特殊的需求,例如需要实现自定义的排序规则或者对大规模数据进行排序等,我们需要深入理解排序算法,并且根据数据规模、数据分布等因素选择合适的排序算法。

目录
相关文章
|
7天前
|
负载均衡 算法 Java
Spring Cloud全解析:负载均衡算法
本文介绍了负载均衡的两种方式:集中式负载均衡和进程内负载均衡,以及常见的负载均衡算法,包括轮询、随机、源地址哈希、加权轮询、加权随机和最小连接数等方法,帮助读者更好地理解和应用负载均衡技术。
|
2天前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
|
3天前
|
SQL JavaScript 前端开发
基于Java访问Hive的JUnit5测试代码实现
根据《用Java、Python来开发Hive应用》一文,建立了使用Java、来开发Hive应用的方法,产生的代码如下
18 6
|
2天前
|
存储 监控 算法
Java中的内存管理与垃圾回收机制解析
本文深入探讨了Java编程语言中的内存管理策略和垃圾回收机制。首先介绍了Java内存模型的基本概念,包括堆、栈以及方法区的划分和各自的功能。进一步详细阐述了垃圾回收的基本原理、常见算法(如标记-清除、复制、标记-整理等),以及如何通过JVM参数调优垃圾回收器的性能。此外,还讨论了Java 9引入的接口变化对垃圾回收的影响,以及如何通过Shenandoah等现代垃圾回收器提升应用性能。最后,提供了一些编写高效Java代码的实践建议,帮助开发者更好地理解和管理Java应用的内存使用。
|
2天前
|
Java 开发者
探索Java中的Lambda表达式:简化代码,提升效率
【9月更文挑战第14天】本文旨在揭示Java 8中引入的Lambda表达式如何革新了我们编写和管理代码的方式。通过简洁明了的语言和直观的代码示例,我们将一起走进Lambda表达式的世界,了解其基本概念、语法结构以及在实际编程中的应用。文章不仅会展示Lambda表达式的魅力所在,还会指导读者如何在日常工作中有效利用这一特性,以提高编码效率和程序可读性。
|
3天前
|
Java 开发者
深入解析Java中的异常处理机制
本文将深入探讨Java中异常处理的核心概念和实际应用,包括异常的分类、捕获、处理以及最佳实践。我们将通过具体示例展示如何有效使用try-catch块、throws关键字和自定义异常类,以帮助读者更好地理解和应用Java异常处理机制。
9 1
|
3天前
|
Java 程序员 开发者
Java中的异常处理机制深度解析
本文旨在深入探讨Java中异常处理的机制,包括异常的分类、如何捕获和处理异常,以及自定义异常的最佳实践。通过实例讲解,帮助读者更好地理解如何在Java编程中有效管理和利用异常处理来提高代码的健壮性和可维护性。
|
4天前
|
存储 负载均衡 Java
Jetty技术深度解析及其在Java中的实战应用
【9月更文挑战第3天】Jetty,作为一款开源的、轻量级、高性能的Java Web服务器和Servlet容器,自1995年问世以来,凭借其卓越的性能、灵活的配置和丰富的扩展功能,在Java Web应用开发中占据了举足轻重的地位。本文将详细介绍Jetty的背景、核心功能点以及在Java中的实战应用,帮助开发者更好地理解和利用Jetty构建高效、可靠的Web服务。
16 2
|
2天前
|
存储 缓存 安全
【Java面试题汇总】多线程、JUC、锁篇(2023版)
线程和进程的区别、CAS的ABA问题、AQS、哪些地方使用了CAS、怎么保证线程安全、线程同步方式、synchronized的用法及原理、Lock、volatile、线程的六个状态、ThreadLocal、线程通信方式、创建方式、两种创建线程池的方法、线程池设置合适的线程数、线程安全的集合?ConcurrentHashMap、JUC
【Java面试题汇总】多线程、JUC、锁篇(2023版)
|
13天前
|
监控 Java 调度
【Java学习】多线程&JUC万字超详解
本文详细介绍了多线程的概念和三种实现方式,还有一些常见的成员方法,CPU的调动方式,多线程的生命周期,还有线程安全问题,锁和死锁的概念,以及等待唤醒机制,阻塞队列,多线程的六种状态,线程池等
74 6
【Java学习】多线程&JUC万字超详解

热门文章

最新文章

推荐镜像

更多