1. 直接插入排序
情景:拿到一个数key,要把他插入到一个已经有序的数组。我们从这个有序数组的最后一个位置的数开始比较如果比key大就把这个数往后挪一个位置,如果小于等于key就把key插入到这个数的后面。
实际中我们玩扑克牌时,就用了两种排序一种是直接插入排序,一种是选择排序。
单趟
我们可以从单趟排起,在一组有效数据中,如下图,我们要插入5,我们把数据一个一个往后挪,最终到它相应的位置上
void InsertSort(int* a, int n)
{
// 把end+1位置数据插入到[0, end]的有序区间内
int end;
int x = a[end - 1];
while (end >= 0)
{
if (a[end] > x)
{
a[end + 1] = a[end];
--end;
}
else
{
break;
}
}
// 不论是end小于0终止还是break终止的循环,最后都是把x放到end+1的位置
a[end + 1] = tmp;
}
我们从end开始比较,end为10大于5,把10往后挪,end--,再和5比,这样一直比下去,直到找到它的位置。
多趟
搞清楚单趟是怎么回事后,我们就知道了多趟是怎么样的,如下图
void InsertSort(int* a, int n)
{
assert(a);
for (int i = 0; i < n - 1; ++i)
{
int end = i;
int x = a[end + 1];
while (end >= 0)
{
if (a[end] > x)
{
a[end + 1] = a[end];
--end;
}
else
break;
}
a[end + 1] = x;
}
}
直接插入排序的特性
- 元素已经有序或接近有序时,直接插入排序的时间效率越高。
- 时间复杂度:O(N^2)。 最好情况(有序):O(N),最坏情况(逆序):O(N^2))
- 空间复杂度:O(1)
- 稳定性:稳定
希尔排序
基本思想:
对于直接插入排序,有序或者接近有序时最快,时间复杂度差不多是O(N)。希尔排序就是在直接插入排序之前对原数组进行预排序,使得原数组接近有序。直接插入排序相当于gap=1
初始排序越逆,希尔排序效果越好
预排序
设间隔 gap ,就是每间隔gap个数的数组成一组,一共有gap组。当gap=1时就是直接插入排序。
void ShellSort(int* a, int n)
{
// 对间隔为gap的其中一组数据进行直接插入排序
int gap;
int end;
int x = a[end + gap];
while (end >= 0)
{
if (a[end] > x)
{
a[end + gap] = a[end];
end-=gap;
}
else
{
break;
}
}
a[end + gap] = x;
}
完成一组排序
通过外套一个for循环,完成1组的排序,其中i代表end的值,从0开始,循环条件为i小于n-gap就可以完成所有组的排序。注意这里不是严格按照一组一组的排,而是混在一起,但最终完成的效果还是一样的。
void ShellSort(int* a, int n)
{
// 1、对gap为间隔的每一组进行排序
int gap;
for (int i = 0; i < n - gap; +=gap)
{
int end=i;
int x = a[end + gap];
// 2、单次预排序
while (end >= 0)
{
if (a[end] > x)
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = x;
}
}
完成排序
void ShellSort(int* a, int n)
{
// 1、对gap为间隔的每一组进行排序
int gap = 3;
for (int j = 0; j < gap; j++)
{
for (int i = 0; i < n - gap; i += gap)
{
int end = i;
int x = a[end + gap];
// 2、单次预排序
while (end >= 0)
{
if (a[end] > x)
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = x;
}
}
}
优化排序
对于以上代码,我们可以进行优化
// 希尔排序
void ShellSort(int* a, int n)
{
int gap=n;
// 1、预排序,直到gap为1,就是直接插入排序
while (gap > 1)
{
gap = gap / 3 + 1;// 最后的+1保证gap最后可以为1
// 2、对gap为间隔的每一组进行排序
for (int i = 0; i < n - gap; ++i)
{
int end = i;
int x = a[end + gap];
// 3、单次预排序
while (end >= 0)
{
if (a[end] > tmp)
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = x;
}
}
}
希尔排序的特性
希尔排序是对直接插入排序的优化。当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。整体而言,可以达到优化的效果。
时间复杂度:O(N^1.3 — N^2)
空间复杂度:O(1)
稳定性:不稳定,因为依据gap分成不同组,相同数据的相对位置可能被打乱