上篇博客介绍了常见排序算法的 4 种算法(文章链接),本篇继续介绍剩下的 5 种排序算法。
1、直接插入排序
(1)基本思想:
- 将一个记录插入到已经排好序的有序表中,从而得到一个新的、记录数增加 1 的有序表。
- 把序列的第 1 个记录看成是一个有序的子序列,从第 2 个记录起逐个插入,直至整个序列成为新的有序序列。
如图所示:数组长度为 8 ,需要插入 7 趟 。
(2)代码实现:
public static void insertSort(int[] arr) {
int temp=0;
//需要比较 n-1 轮
for (int i = 0; i < arr.length - 1; i++) {
//从第二个记录逐个插入
for (int j = i + 1; j > 0; j--) {
//将插入记录与有序表中的记录比较,选择插入到合适的位置构成新的有序序列
if (arr[j] < arr[j - 1]) {
temp = arr[j - 1];
arr[j - 1] = arr[j];
arr[j] = temp;
} else { // 不需要交换
break;
}
}
}
}
(3)算法分析:
算法不适应数据量较大时候的排序。
最坏时间复杂度:O(n^2) 最好时间复杂度:O(n) 故平均时间复杂度为:O(n ^2)
稳定性:排序前后值相等的两个元素相对顺序没有改变,故该算法稳定。
2、希尔排序
希尔排序也叫“缩小增量排序”,是对直接插入排序算法的改进。时间效率有所提高。
(1)基本思想:
- 将整个待排序列按照某一增量分割成若干个子序列,分别对子序列进行直接插入排序。
- 待整个序列中的记录“基本有序”,再对全体记录进行一次直接插入排序。
- 增量的选取要保证最后一个增量值必须为 1 ,前几趟由于增量的设置排序比较是跳跃式移动,最后一趟只需要少量的比较移动就可完成排序。
该数组长度为 8 ,三趟希尔排序的增量分别为:4,2,1
(2)代码实现:
public static void shellSort(int[] arr) {
int temp = 0;
// 增量的表示
int incre = arr.length;
while (incre >= 1) {
// 选取增量
incre = incre / 2;
// 根据增量分割为若干个子序列
for (int i = 0; i < incre; i++) {
// 直接插入排序:需要比较 n/incre - 1 轮
for (int j = i + incre; j < arr.length; j += incre) {
// 从第 j 个位置插入比较
for (int k = j; k > i; k -= incre) {
if (arr[k] < arr[k - incre]) {
temp = arr[k];
arr[k] = arr[k - incre];
arr[k - incre] = temp;
} else {
break;
}
}
}
}
}
}
(3)算法分析:
时间复杂度为 O(n^1.5)
该算法由于是跳跃式比较移动,值相同的两个元素相对顺序可能会发生改变,故算法不稳定。
3、归并排序
(1)基本思想:
- 将待排序序列分成大小大致相同的 2 个子序列并对其进行排序,最终将排好序的子序列归并成排好序的序列
- 采用分治思想解决,先递归分解序列,再递归合并序列。
如图所示序列利用递归实现归并排序的过程。
(2)代码实现:
该排序可利用递归和非递归两种方式实现,这里只介绍递归实现的方法。
public static void merge_sort(int[] a, int left, int right, int[] temp) {
if (left < right) {
int middle = (left + right) / 2;
merge_sort(a, left, middle, temp);// 左半部分排好序
merge_sort(a, middle + 1, right, temp);// 右半部分排好序
mergeArray(a, left, middle, right, temp); // 合并左右部分
}
}
// 合并 :将两个序列a[left-middle],a[middle+1-right]合并
public static void mergeArray(int[] a, int left, int middle, int right, int[] temp) {
int i = left;
int m = middle;
int j = middle + 1;
int n = right;
int k = 0;
//a[left-middle]、a[middle+1-right]序列都有
while (i <= m && j <= n) {
if (a[i] <= a[j]) {
temp[k] = a[i];
k++;
i++;
} else {
temp[k] = a[j];
k++;
j++;
}
}
//只有a[left-middle]序列
while (i <= m) {
temp[k] = a[i];
k++;
i++;
}
//只有a[middle+1-right]序列
while (j <= n) {
temp[k] = a[j];
k++;
j++;
}
//将排好序的序列复制回原序列
for (int ii = 0; ii < k; ii++) {
a[left + ii] = temp[ii];
}
}
(3)算法分析:
最坏、最好、平均时间复杂度均为 O(n logn)
归并过程中我们可以保证当前两个元素相等时把前面序列的元素排在结果序列的靠前位置,故算法稳定。
4、基数排序
(1)基本思想:
- 是一种利用多关键字排序思想进行排序。有两种排序方法:最高位优先(MSD)和最低位优先(LSD)。
- 对两位正整数(包含两位)序列排序,从十位先排序,划分为若干个序列依次进行为 MSD,从个位则采用的是LSD。
- 扑克牌花色地位高于数字。对一副牌排序。先按照花色排序换分为 4 堆,每一堆再按照数字排序为 MSD;若先按照数字进行排序划分为 13 堆,每一堆按花色排序并依次从 13 堆中取出排序为LS。
(2)算法分析:
是一种非比较排序,只能用于整数的排序,该算法具有稳定性。
5、桶排序
(1)基本思想:
- 桶排序(箱排序)是将数组元素映射到有限个数的桶里,每个桶再各自进行排序(使用其他排序算法或以递归的形式继续桶排序)。
- 该算法要求数据的长度长度必须完全一样,其也是利用了分治法的思想。
图示桶排序的基本过程。
(2)应用:
某年全国高考500万人,分数最低分为100分,最高分为800分,没有小数,要求对500万考生成绩排序。
方法:创建701(800-100)个桶。将每个考生的分数丢进f(score)=score-100的桶中。这个过程从头到尾遍历一遍数据只需要500W次。然后根据桶号大小依次将桶中数值输出,即可以得到一个有序的序列。而且可以很容易的得到100分有***人,501分有***人。
(3)算法分析:
算法具有稳定性。
最坏时间复杂度只有一个桶,取决于桶内排序方式;最好时间复杂度 O(n) ,每个元素占一个桶;平均时间复杂度为O(n),保证各个桶内元素个数均匀。
各个算法比较
算法 | 最坏时间复杂度 | 最好时间复杂度 | 平均时间复杂度 | 稳定性 |
冒泡排序 | O(n^2) | O(n) | O(n^2) | 稳定 |
快速排序 | O(n^2) | O(n logn) | O(n logn) | 不稳定 |
简单选择排序 | O(n^2) | O(n^2) | O(n^2) | 不稳定 |
堆排序 | O(n logn) | O(n logn) | O(n logn) | 不稳定 |
直接插入排序 | O(n^2) | O(n) | O(n^2) | 稳定 |
希尔排序 | O(n^2) | O(n) | O(n^1.3) | 不稳定 |
归并排序 | O(n logn) | O(n logn) | O(n logn) | 稳定 |
基数排序 | O(d(n+rd)) | O(d(n+rd)) | O(d(n+rd)) | 稳定 |
桶排序 | O(N+C) | O(N) | O(N) | 稳定 |
参考文章:https://blog.youkuaiyun.com/lutianfeiml/article/details/51958962