本文由 简悦 SimpRead 转码, 原文地址 https://www.cnblogs.com/20145221GQ/p/5407824.html
Java 实现:数据结构之排序
0. 概述
- 形式化定义:假设有 n 个记录的序列(待排序列)为 {R1, R2 , …, Rn},其相应的关键字序列为 { K1, K2, …, Kn }。找到{1,2, …, n} 的一个排列 p1,p2, …, pn,使得 Kp1≤Kp2≤ …≤ Kpn (升序),按此排列将 n 个记录重新排列为 { Rp1, Rp2, …,Rpn }的操作称作排序。
- 排序方法分类
- 基于比较的排序:
- 比较两个关键字大小
- 移动关键字到合适位置(交换或复制)
- 不基于比较的排序
- 基于比较的排序:
- 排序有内部排序和外部排序之分,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。
- 本篇博客所要介绍的是内部排序中的八大排序方法(参考博客地址为 C 语言实现):
1. 插入排序—直接插入排序 (Straight Insertion Sort)
- 基本思想
在要排序的一组数中,假设前面 (n-1)[n>=2] 个数已经是排好顺序的,现在要把第 n 个数插到前面的有序数中,使得这 n 个数也是排好顺序的。如此反复循环,直到全部排好顺序。 - 代码实现(InsertSort.java)
1 | package DataStructures.Sort; |
- 运行结果(InsertSortDemo.java)
2. 插入排序—希尔排序(Shell’s Sort)
- 基本思想
算法先将要排序的一组数按某个增量 d(n/2,n 为要排序数的个数)分成若干组,每组中记录的下标相差 d. 对每组中全部元素进行直接插入排序,然后再用一个较小的增量(d/2)对它进行分组,在每组中再进行直接插入排序。当增量减到 1 时,进行直接插入排序后,排序完成。 - 代码实现(ShellSort.java)
1 | package DataStructures.Sort; |
- 运行结果(ShellSortDemo.java)
3. 选择排序—简单选择排序(Simple Selection Sort)
- 基本思想
在要排序的一组数中,选出最小(或者最大)的一个数与第 1 个位置的数交换;然后在剩下的数当中再找最小(或者最大)的与第 2 个位置的数交换,依次类推,直到第 n-1 个元素(倒数第二个数)和第 n 个元素(最后一个数)比较为止。 - 代码实现(SelectSort.java)
1 | package DataStructures.Sort; |
- 运行结果(SelectSortDemo.java)
4. 选择排序—堆排序(Heap Sort)
- 基本思想
- 堆排序是一种树形选择排序,是对直接选择排序的有效改进。可以将一个堆看做是一棵完全二叉树的顺序存储。以升序排序为例:
- 通过建大顶堆,将待排序列中的最大元素筛选出来(即根结点位置)。
- 将根结点与待排序列中最后一个元素交换。
- 调整剩余元素再次成为大顶堆。
- 不断重复上述过程,直至堆中只剩 2 个元素为止。
- 代码实现(HeapSort.java)
1 | //以建立大顶堆为例 |
- 运行结果(HeapSortDemo.java)
5. 交换排序—冒泡排序(Bubble Sort)
- 基本思想
在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。 - 代码实现(BubbleSort.java)
1 | package DataStructures.Sort; |
- 运行结果(BubbleSortDemo.java)
6. 交换排序—快速排序(Quick Sort)
- 基本思想
- 选择一个基准元素, 通常选择第一个元素或者最后一个元素
- 通过一趟排序讲待排序的记录分割成独立的两部分,其中一部分记录的元素值均比基准元素值小。另一部分记录的 元素值比基准值大。
- 此时基准元素在其排好序后的正确位置
- 然后分别对这两部分记录用同样的方法继续进行排序,直到整个序列有序
- 代码实现(QuickSort.java)
1 | package DataStructures.Sort; |
- 运行结果(QuickSortDemo.java)
7. 归并排序(Merge Sort)
- 基本思想
- 归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。
- 分解:将含有 n 个元素的待排序列分解为各含 n/2 个元素的两个子序列。
- 解决:用归并排序递归地排序两个子序列。
- 合并:合并两个已排序的子序列,以得到最终结果。
- 代码实现(MergeSort.java)
1 | package DataStructures.Sort; |
- 运行结果(MergeSortDemo.java)
8. 基数排序 (Radix Sort)
- 基本思想
- 一种借助 “多关键字排序” 的思想来实现 “单关键字排序” 的算法。
- 将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。
- 代码实现(RadixSort.java)
1 | package DataStructures.Sort; |
- 运行结果(RadixDemo.java)
9. 脉络梳理
- 排序算法稳定性:
- 首先,通俗地讲就是能保证排序前 2 个相等的数其在序列的前后位置顺序和排序后它们两个的前后位置顺序相同。在简单形式化一下,如果 Ai = Aj, Ai 原来在位置前,排序后 Ai 还是要在 Aj 位置前。
- 其次,说一下稳定性的好处。排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,第一个键排序的结果可以为第二个键排序所用。基数排序就 是这样,先按低位排序,逐次按高位排序,低位相同的元素其顺序再高位也相同时是不会改变的。
- 所以,堆排序、快速排序、希尔排序、直接选择排序不是稳定的排序算法,而基数排序、冒泡排序、直接插入排序、折半插入排序、归并排序是稳定的排序算法。(相关解释参考:排序算法稳定性)
- 各种排序特点的比较:
排序算法 | 平均时间 | 最差情形 | 稳定度 | 额外空间 | 备注 |
---|---|---|---|---|---|
直接插入排序 | O(n^2) | O(n^2) | 稳定 | O(1) | n 小时较好 |
希尔排序 | O(nlogn) | O(n^s),1<s<2 | 不稳定 | O(1) | s 是所选分组 |
简单选择排序 | O(n^2) | O(n^2) | 不稳定 | O(1) | n 小时较好 |
堆排序 | O(nlogn) | O(nlogn) | 不稳定 | O(1) | n 大时较好 |
冒泡排序 | O(n^2) | O(n^2) | 稳定 | O(1) | n 小时较好 |
快速排序 | O(n) | O(n^2) | 不稳定 | O(nlogn) | n 大时较好 |
归并排序 | O(nlogn) | O(nlogn) | 稳定 | O(1) | n 大时较好 |
基数排序 | O(logrd) | O(logrd) | 稳定 | O(n) | d 是关键字项数 (0-9),r 是基数 (个十百) |