插入排序
1.基本思想
把待排的记录按其关键码值的大小逐个插入到一个已经排好的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列。
实际上我们生活中玩扑克牌时使用的就是插入排序的思想,从第一张牌开始调整,将每张扑克牌按数值大小排成顺序序列,就达到了目的。
2.直接插入排序
在有序序列中插入一个元素,保持序列有序,有序长度不断增加;
起初,a[0]是长度为0的子序列,随着逐一插入a[1]、a[2]、…、a[n-1],此时数字排序完毕。
以下面一组数为例,我们一起理解一下插入排序的执行过程吧!
如果新插入数据大于等于前面的数据,不用挪动,如果小于前面的数,记录插入的数据,将大于该数的值往后挪动,再将其放入空位。
(1)插入9至a[1]处从下标为1处开始比较,a[1]<a[0],tmp = a[1],a[0]后移,使a[0]-a[1]是有序序列。
(2)插入1至a[2]处,a[2]<a[1],a[2]>a[0],只需往后挪动a[1],a[2]放入a[1]处。
(3)插入5至a[3]处,a[3]>a[0]及a[1],但a[3]<a[2],记录a[3],后移a[2]
(4)插入7至a[4],只需挪动a[3]
(5)插入4至a[5],…
…
依次比较,最后得到如下序列
代码实现:
void InsertSort(int *a,int n)
{
for(int i = 0; i < n - 1 ;i++)
{
int endi = i;
int tmp = a[endi + 1];
while (endi >= 0) //控制单趟 在[0,endi]区间插入数据
{
if (a[endi] > tmp)
{
a[endi + 1] = a[endi];
endi--;
}
else
{
break;
}
}
a[endi + 1] = tmp;
}
}
<总结>
这种排序算法对于接近有序的元素集合效率很高,因为越是有序,数据挪动次数就越少。但是对于完全逆序的元素集合,每插入一个数据,前面的值都要向后挪动,数据越多,挪动次数也将越多,效率就会降低。
时间复杂度O(N^2)
空间复杂度O(1)
3.希尔排序
希尔排序(Shell’s Sort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本。希尔排序的出现正是为了解决插入排序处理逆序元素集合效率高的问题。
算法思想:先取一个小于n的整数d1作为第一个增量,把文件的全部记录分组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序(称为;然后,取第二个增量d2 =1( …)
该方法实质上是一种分组插入方法。
比较相隔较远距离(称为增量)的数,使得数移动时能跨过多个元素,则进行一次比较就可能消除多个元素交换。算法先将要排序的一组数按某个增量d分成若干组,每组中记录的下标相差d。对每组中全部元素进行排序,然后再用一个较小的增量对它进行分组,在每组中再进行排序。当增量减到1时,整个要排序的数被分成一组,排序完成。
下面我们再一起来分析一下希尔排序的执行过程~
(仍然使用上组数据,增量gap依次使用5,3,1)
初始状态
(1)gap == 5时(相同颜色的数据比较大小,前者大则交换)
(2)gap == 2时
(3)在经历前两次预排序后,此时数据已经很接近有序了,令gap == 1
代码实现:
//希尔排序
void ShellSort(int* a, int n)
{
int gap = n;
while (gap > 1)
{
gap = gap/ 3 + 1; //确保最后一趟gap==1,进行直接插入排序
for (int i = 0; i < gap; i++) //分成了gap组
{
for (int j = i; j < n - gap; j += gap) //对间隔gap的一组数据排序
{
int endi = j;
int tmp = a[endi + gap];
while (endi >= 0)
{
if (a[endi] > tmp)
{
a[endi + gap] = a[endi];
endi -= gap;
}
else
{
break;
}
}
a[endi + gap] = tmp;
}
}
}
}
还可进行减少一层循环,让代码看起来简洁一些,如下
void ShellSort(int* a, int n)
{
int gap = n;
while (gap > 1)
{
gap = gap / 3 + 1; //确保最后一趟gap==1,进行直接插入排序
//分成了gap组
for (int j = 0; j < n - gap; j++) //对间隔gap的一组数据排序
{
int endi = j;
int tmp = a[endi + gap];
while (endi >= 0)
{
if (a[endi] > tmp)
{
a[endi + gap] = a[endi];
endi -= gap;
}
else
{
break;
}
}
a[endi + gap] = tmp;
}
}
}
<总结>
1.希尔排序分为两步,一是gap>1时进行预排序,gap越大,大的值就越快调到后面,小的值可以更快调到前面,但越不接近有序;gap越小,跳的越慢,但是越接近有序。二是gap==1时,就是进行直接插入排序。
2.相比与插入排序,希尔排序的时间复杂度低于插入排序,但因为难以计算,平均计算下来是O(N^1.3)。