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

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 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 启示

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

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

目录
相关文章
|
5天前
|
自然语言处理 搜索推荐 数据安全/隐私保护
鸿蒙登录页面好看的样式设计-HarmonyOS应用开发实战与ArkTS代码解析【HarmonyOS 5.0(Next)】
鸿蒙登录页面设计展示了 HarmonyOS 5.0(Next)的未来美学理念,结合科技与艺术,为用户带来视觉盛宴。该页面使用 ArkTS 开发,支持个性化定制和无缝智能设备连接。代码解析涵盖了声明式 UI、状态管理、事件处理及路由导航等关键概念,帮助开发者快速上手 HarmonyOS 应用开发。通过这段代码,开发者可以了解如何构建交互式界面并实现跨设备协同工作,推动智能生态的发展。
72 10
鸿蒙登录页面好看的样式设计-HarmonyOS应用开发实战与ArkTS代码解析【HarmonyOS 5.0(Next)】
|
5天前
|
存储 缓存 Java
Java 并发编程——volatile 关键字解析
本文介绍了Java线程中的`volatile`关键字及其与`synchronized`锁的区别。`volatile`保证了变量的可见性和一定的有序性,但不能保证原子性。它通过内存屏障实现,避免指令重排序,确保线程间数据一致。相比`synchronized`,`volatile`性能更优,适用于简单状态标记和某些特定场景,如单例模式中的双重检查锁定。文中还解释了Java内存模型的基本概念,包括主内存、工作内存及并发编程中的原子性、可见性和有序性。
Java 并发编程——volatile 关键字解析
|
21天前
|
机器学习/深度学习 人工智能 算法
深入解析图神经网络:Graph Transformer的算法基础与工程实践
Graph Transformer是一种结合了Transformer自注意力机制与图神经网络(GNNs)特点的神经网络模型,专为处理图结构数据而设计。它通过改进的数据表示方法、自注意力机制、拉普拉斯位置编码、消息传递与聚合机制等核心技术,实现了对图中节点间关系信息的高效处理及长程依赖关系的捕捉,显著提升了图相关任务的性能。本文详细解析了Graph Transformer的技术原理、实现细节及应用场景,并通过图书推荐系统的实例,展示了其在实际问题解决中的强大能力。
123 30
|
1天前
|
存储 监控 算法
企业内网监控系统中基于哈希表的 C# 算法解析
在企业内网监控系统中,哈希表作为一种高效的数据结构,能够快速处理大量网络连接和用户操作记录,确保网络安全与效率。通过C#代码示例展示了如何使用哈希表存储和管理用户的登录时间、访问IP及操作行为等信息,实现快速的查找、插入和删除操作。哈希表的应用显著提升了系统的实时性和准确性,尽管存在哈希冲突等问题,但通过合理设计哈希函数和冲突解决策略,可以确保系统稳定运行,为企业提供有力的安全保障。
|
2天前
|
Java 数据库连接 Spring
反射-----浅解析(Java)
在java中,我们可以通过反射机制,知道任何一个类的成员变量(成员属性)和成员方法,也可以堆任何一个对象,调用这个对象的任何属性和方法,更进一步我们还可以修改部分信息和。
|
25天前
|
存储 算法 Java
Java内存管理深度解析####
本文深入探讨了Java虚拟机(JVM)中的内存分配与垃圾回收机制,揭示了其高效管理内存的奥秘。文章首先概述了JVM内存模型,随后详细阐述了堆、栈、方法区等关键区域的作用及管理策略。在垃圾回收部分,重点介绍了标记-清除、复制算法、标记-整理等多种回收算法的工作原理及其适用场景,并通过实际案例分析了不同GC策略对应用性能的影响。对于开发者而言,理解这些原理有助于编写出更加高效、稳定的Java应用程序。 ####
|
25天前
|
存储 监控 算法
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####
|
25天前
|
PHP 开发者 容器
PHP命名空间深度解析:避免命名冲突与提升代码组织####
本文深入探讨了PHP中命名空间的概念、用途及最佳实践,揭示其在解决全局命名冲突、提高代码可维护性方面的重要性。通过生动实例和详尽分析,本文将帮助开发者有效利用命名空间来优化大型项目结构,确保代码的清晰与高效。 ####
23 1
|
3天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
5天前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。

热门文章

最新文章

推荐镜像

更多