一、排序概念及运用
排序在数据结构中是非常重要的一部分,所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
在生活中也有很多的应用,比如当我们搜索一款产品时,我们可以选择按销量多少的顺序来给我们推荐产品,也可以按照价格高低来给我们推荐产品,所以排序在生活中也是很常见的。

1.1 插入排序
(1)直接插入排序
插入排序又分为直接插入排序和希尔排序。直接插入排序是比较好理解的,比如我们日常生活中的扑克牌游戏,当我们拿到牌的时候我们会习惯性的直接将牌按我们想要的顺序排列。

那么希尔排序又是怎么回事呢?我用一张清晰的思路图来向大家展示:

void InitSort(int* arr, int n) {
for (int i = 0; i < n - 1; i++) {
int end = i;
int tmp = arr[end + 1];
while (end >= 0) {
if (arr[end] > tmp) {
arr[end + 1] = arr[end];
end--;
} else {
break;
}
}
arr[end + 1] = tmp;
}
}

对于直接插入来说,这个算法的时间复杂度是多少呢?代码中有两层嵌套,如果有最坏的情况发生的话,时间复杂度为 O(n^2),但是最好的情况是 O(n)。
(2)希尔排序
我们在直接插入排序中,当数组为降序时,时间复杂度会高一点,此时就要说我们的希尔排序了。希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数(通常是 gap = n/3+1),把待排序文件所有记录分成各组,所有的距离相等的记录分在同一组内,并对每一组内的记录进行排序,然后 gap=gap/3+1 得到下一个整数,再将数组分成各组,进行插入排序,当 gap=1 时,就相当于直接插入排序。它是在直接插入排序算法的基础上进行改进而来的,综合来说它的效率肯定是要高于直接插入排序算法的。
总结下来就是第一步预排序,第二步直接插入排序。

void ShellSort(int* arr, int n) {
int gap = n;
while (gap > 1) {
gap = gap / 3 + 1;
for (int i = 0; i < n - gap; i++) {
int end = i;
int tmp = arr[end + gap];
while (end >= 0) {
if (arr[end] > tmp) {
arr[end + gap] = arr[end];
end -= gap;
} else {
break;
}
}
arr[end + gap] = tmp;
}
}
}

对于希尔排序,直接的思路是比较好理解的,但是如果我们写成代码的话,是有点不好理解的,尤其是对于 gap 分组再嵌套进入循环,大家可以借助画图的方法深入理解一下。对于希尔排序的时间复杂度,因为 gap 的取值很多,导致很难去计算,因此很多书中给出的希尔排序的时间复杂度都不固定。但是外层循环的时间复杂度可以直接给出为:O(log2 n) 或者 O(log3 n),即 O(log n)。总之希尔排序的效率是高于直接插入排序的。
1.2 选择排序
选择排序的基本思想:每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。
(1)直接选择排序
- 在元素集合 array[i]--array[n-1] 中选择关键码最大 (小) 的数据元素。
- 若它不是这组元素中的最后一个 (第一个) 元素,则将它与这组元素中的最后一个(第一个)元素交换。
- 在剩余的 array[i]--array[n-2](array[i+1]--array[n-1])集合中,重复上述步骤,直到集合剩余 1 个元素。
Swap(int* a, int* b) {
int temp;
temp = *a;
*a = *b;
*b = temp;
}
void SelectSort(int* arr, int n) {
for (int i = 0; i < n; i++) {
int begin = i;
int mini = i; //找最小的值
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[mini]) {
mini = j;
}
}
//找到最小的值了,i 和 mini 位置的数据进行交换
Swap(&arr[i], &arr[mini]);
}
}

这一部分的算法思路还是很好理解的,我们进行了两次嵌套循环,在每一次中找到最小的值,然后将最小的值放在第一位,同时这个算法的时间复杂度跟我们的直接插入排序是一样的,最差情况是 O(n^2)。但是我们能不能换一个思路,我们在找最小值的情况下能不能直接找最大值,同时进行寻找,那么我们的时间效率会不会提高呢?
void SelectSort2(int* arr, int n)//优化
{
int begin = 0;
int end = n - 1;
while (begin < end) {
int mini = begin, maxi = begin;
for (int i = begin + 1; i <= end; i++) {
if (arr[i] > arr[maxi]) {
maxi = i;
}
if (arr[i] < arr[mini]) {
mini = i;
}
}
Swap(&arr[mini], &arr[begin]);
Swap(&arr[maxi], &arr[end]);
++begin;
--end;
}
}

这是我们按照之后的思路写的算法,根据我的代码,我们是同时找最小值和最大值,最后在交换,但是代码出现了问题,最后并没有按我们想要的排序,那么问题到底出现在哪了呢?

所以我们还要处理这种特殊的情况,我们就要在交换之前再加上一个 if 条件:


直接选择排序的特性总结:
- 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用。
- 时间复杂度:O(n^2)。
- 空间复杂度:O(1)。
(2)堆排序
堆排序也是属于选择排序的一种,堆排序 (Heapsort) 是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。在前面学习堆的时候我们也详细的讲解过堆排序的原理思路以及详细的代码展示。那么堆排序的时间复杂度是 O(nlogn)。
在插入排序中,希尔排序是直接插入排序的优化,效率提高了不少;在选择排序中,直接选择排序的时间复杂度是比较高的,也就是说这个排序算法的效率是比较低的,在平时的计算中我们是不选择的,但是我们也要了解这种算法。


