👉插入排序
插入排序的核心思想:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为 止,得到一个新的有序序列
也就是说可以把数组分成前后两个数组,每一次前面的数组都是有序的,再将有序数组后面的数插入到有序的数组中,每一次插入后都会形成一个新的有序数组
下面图片以升序为例
插入排序有几个特性,当数列越接近有序时,插入排序的时间效率越高;它的最坏时间复杂度为O(N^2);插入排序是一个稳定的排序法
void InsertSort(int* a, int n) {
for (int i = 0; i < n - 1; i++) {
int end = i;
int tmp = a[end + 1];
while (end >= 0) {
if (a[end] > tmp) {
a[end + 1] = a[end];
end--;
}
else
break;
}
a[end + 1] = tmp;
}
}
👉冒泡排序
冒泡排序是一种容易理解且稳定的排序,但是其效率较低,时间复杂度为O(N^2),所以对于数据量较大的情况下不推荐使用
其原理十分的简单,下面以升序为例:
每一趟排序过程都是让相邻的两个数据进行比较,如果后一个数据比前一个小则进行交换,下面来看动图理解
上图体现出了第一趟排序的过程,接下来看第二趟,看看有什么区别
不难看出,第二趟比第一趟需要排序的次数减少了。这是因为第一趟的时候已经把最大的数据排到了最后面,因此第二趟只需要把第二大的数据排到倒数第二个位置即可。
从中我们可以得出,假设数组长度为n,则需要排n趟,每一趟需要进行n - i次比较(i 为第几趟排序)。所以冒泡排序的时间复杂度为O(N^2),利用代码实现冒泡排序,只需要用到两个循环即可完成。
当然在代码里我们可以做个简单的优化,因为利用冒泡排序的话,如果某一趟排序结束后,期间并没有发生一次数据的交换,那么这时该数组一定有序,所以我们不妨设一个变量来监视一下是否发生交换,如果没有交换那就退出循环即可
void BubbleSort(int* a, int n) {
for (int i = 0; i < n - 1; i++) {
int exchange = 0;
for (int j = 1; j < n; j++) {
if (a[j - 1] > a[j]) {
Swap(&a[j - 1], &a[j]);
exchange = 1;
}
}
if (exchange == 0)
break;
}
}
👉希尔排序
希尔排序是对插入排序的一种优化,因为插入排序的特性数列越接近有序,它的效率就越快。
所以希尔排序的步骤就是,先对数列进行预排序,也就是让数列接近有序;然后再进行插入排序
预排序
预排序的思想是:
选定一个gap数,让数列每一组间隔为gap的元素组成一个数列,对于这个间隔数的取值,没有一个固定的取值,这里gap的值第一趟取n / 3 + 1;之后的每一趟都是gap / 3 + 1.
当gap==1时,数列会接近有序了,这个时候就相当于是插入排序了
如下图
图中将数列看成[9, 7, 3];[1, 4, 5];[2, 8];[5, 6]这几组。然后分别对这几组进行插入排序,得到新的数列
得到新的数列后,更新gap的值,这个时候gap / 3 + 1后就会变成2,所以再以2为间隔进行分组
分好后再分别对每组进行排序
可以看出这个时候整个数列就相对原数列来说接近有序很多了,这时候我们再更新一下gap,这时我们会发现gap已经是1了,那这个时候排序的过程就相当于是插入排序了
说到这里,是不是觉得希尔排序相较于插入排序也没多大的变化呢(我一开始也是这么觉得),其实希尔排序相较于插入排序的效率快的不是一丁半点,但是由于gap的取值并不是固定的,所以它的时间复杂度也很难计算出来,在《计算机程序设计技巧》这本书中提到了,它的时间复杂度在O(N^1.25)到 O(N^1.25 * 1.6)之间,比插入排序快多了,话不多说,看看代码是如何实现的。
void ShellSort(int* a, 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 = a[end + gap];
while (end >= 0) {
if (a[end] > tmp) {
a[end + gap] = a[end];
end -= gap;
}
else
break;
}
a[end + gap] = tmp;
}
}
}
👉选择排序
直接选择排序
基本思想:每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完
在这里我直接同时选最大和最小值
首先预设两个位置存放最大最小值,这里采用升序,所以一开始下标0的位置(begin)存放最小值,n-1的位置(end)存放最大值。依次遍历数组,假设min,max为begin位置的值,如果遍历到的值小于min,则更新min,同理如果大于max,则更新max,第一趟走完后,最小的数就会在最左边,最大的就会在最右边
可以看到,第一趟结束后,最小的0到最左边,最大的9到了最右边。第一趟排完后只需要让begin++一下排下一个位置,让end–一下排下一个位置,并且每一趟的开始,min,max都等于begin。如果begin>end,说明不需要再排了,已经排好了
代码实现:
void SelectSort(int* a, int n) {
int begin = 0, end = n - 1;
while (begin < end) {
int min = begin, max = begin;
for (int i = begin + 1; i <= end; i++) {
if (a[i] > a[max])
max = i;
if (a[i] < a[min])
min = i;
}
Swap(&a[begin], &a[min]);
if (max == begin)
max = min;
Swap(&a[end], &a[max]);
end--;
begin++;
}
}
堆排序
这个在之前二叉树的文章里有讲了,具体链接:
👉总结
这几个排序的思路和实现都不是很难,搞清了思路后代码的细节也要多加注意,还有剩下的几个排序再之后的文章更新。如果哪里不足,望各位大佬指出🍗
|