一、直接插入排序
基本思想是从第一个数开始,从后面每插入一个数就与前面的已有的数进行比较,一般情况下从小到大排序。
Code1
#include <stdio.h>
#include <stdlib.h>
#define T int
#define MAX_SIZE 20
typedef T Sqlist[MAX_SIZE];
void InsertSort1(Sqlist &L, int n)
{
int i, j, temp;
for(i= 1; i < n; ++i)
{
if (L[i] < L[i - 1])//若当前的数L[i]小于前驱
{
temp = L[i];//用temp暂存L[i]
for (j = i - 1; j >= 0 && L[j] > temp; --j)//检查前面已经排好序的元素
L[j + 1] = L[j]; //若前驱比这个数大,逻辑上前驱后移一位,内存里体现是前一个覆盖后一个。等同于L[i] = L[j];
L[j + 1] = temp;
}
}
}
void main() {
Sqlist L = { 0,49,38,65,97,76,13,27 };//第一个数字0不做排序处理
int n = 8;
for (int i = 1; i < n; i++)
printf("%d ", L[i]);
InsertSort1(L, n);
printf("\n排序之后\n");
for (int i = 1; i < n; i++)
printf("%d ", L[i]);
}
模拟代码过程:第一个for循环开始,第一个数字49,i=1,不满足if条件。
第二个数字38,i=2,满足if(38<49),temp赋值当前数38,进入第二个for循环:
for(j=1; j>0&& L[j]>temp) L[2]=L[1]=49 ; 之后j–,跳出for循环后j=0,L[0]=temp=38,完成38的插入。
第 三个 数字65,i=3,不满足if条件。
第四个数字97,i=4,不满足if条件。
第五个数字76,i=5,满足if(76<97),temp赋值当前数76,进入第二个for循环:
j从当前数的前驱开始(即97),依次向前走, for(j=4; j>0&& L[j]>temp) L[5]=L[4]=76
不满足 for(j=3; j>0&& L[j]>temp)跳出循环,此时j=3, 所以L[4]=temp=76,完成76的插入。
到此,内存的数字排列应是0,38,49,65,76,97,13,27。
13和27插入过程也是如此,这里就不做分析了。
Code2
下面的代码也是直接插入排序,只是包装了一个swap交换函数,看起来就显得比较简洁,而且用不到第一位L[0]=0,,其实本质还是一样。
void Swap(T *a, T *b)
{
T tmp = *a;
*a = *b;
*b = tmp;
}
void InsertSort(Sqlist &L, int n)
{
for(int i=1; i<n; ++i)
{
if(L[i] < L[i-1])
{
Swap(&L[i],&L[i-1]);
for(int j=i-1; j>0&&L[j]<L[j-1]; --j)
{
Swap(&L[j],&L[j-1]);
}
}
}
}
前插存在的问题:如果是存在一组数38,49,再插入一个65,65要先插入到38的后面,再与49比较插入到49的后面,这就增加了比较和移动的次数。
1.2 改进的直接插入排序(第一位设为哨兵位)
基本思想是:把数组的第一位L[0](也就是新插入元素)当做哨兵,当新插入元素小于它的前一位元素时,用作新插入元素与前面其他已经排好序的元素之间做比较;当新插入元素大于它的前一位元素,不做比较。
如何进行比较并移动位置呢?
让L[0](新插入元素都会赋值给L[0])依次与新插入元素的前一位元素 (L[j]) 进行比较,L[0]<L[j],说明新插入元素要放到L[j]的前面,这时要让L[j]依次赋值给他的后一位L[j+1],从而找到新插入元素在数组中的位置,最后让L[0]赋值到那个位置即可。
改进的直接插入排序与上面的Code1代码类似,只不过上面code1中用了一个temp变量。
void InsertSort(Sqlist &L, int n)
{
int i,j;
for (i=2;i<=n;i++)
{
if (L[i] < L[i - 1]) {
L[0] = L[i];
for (j = i - 1; L[0] < L[j]; --j)
{
L[j + 1] = L[j];
}
L[j + 1] = L[0];
}
}
}
二、折半插入排序
先用折半查找找到元素应该插入的位置,在移动元素。虽然减少了比较的次数,但移动元素的次数没变。
void BInsertSort(Sqlist &L, int n)
{
int i, j, low, high, mid;
for (i = 2; i < n; ++i)
{
L[0] = L[i];//将当前要插入的数暂存到L[0]辅助空间
low = 1; high = i - 1;//设置折半查找的范围
while (low <= high)
{
int mid = (low + high) / 2; //找到中间的那个数与将要插入的数比较,缩小范围
if (L[0] >= L[mid])
low = mid + 1;//查找右半子表,相等时为了保证稳定性要在右边插入
else
high = mid - 1;//查找左半子表
}
for (int j = i; j > high + 1; --j)//找到插入的位置,后面的数后移一位
{
L[j] = L[j - 1];//后移元素,空出插入位置
}
L[high + 1] = L[0];//将当前数插入
}
}
模拟代码过程如下: 元素是49,38,65,97,76,13,27
i=2时,L[0]=L[i]=38,low=high=1,可以进入while,
第1次while,mid=1,if (L[0] >= L[mid])不成立,high=1-1=0查找左边部分
第2次while,while(low<=high)不成立,退出while,接下来就是移动元素了。
i=3时,L[0]=L[i]=65,low=1,high=2,可以进入while,
第1次while,mid=1,if (L[0] >= L[mid])成立,low=1+1=2
第2次while,mid=2,if (L[0] >= L[mid])成立,low=2+1=3
第2次while,while(low<=high)不成立,退出while,但不满足for循环,故无需移动,赋值即可。
三、希尔排序
它的工作原理是比较相隔一定距离的元素,并且每趟比较所用的距离随算法进行而减小,因此,希尔排序也叫做缩减增量排序。对于大规模的乱序数组插入排序很慢,因为它只会交换相邻元素,因此只能一点点地挪到相应位置,遇到极端情况如最小元素刚好在数组的最右端,那它要经过n-1次移动才能到正确位置,希尔排序为了加快速度,采用交换不相邻元素的方法对数组的一部分进行排序,先令不同间隔的数组变成有序状态,最后再用一次直接插入排序完成排序。
void ShellInsert(SqList &L, int n, int dk)//dk代表增量
{
for(int i=dk+1; i<n; ++i)
{
if(L[i] < L[i-dk])
{
L[0] = L[i];
for(int j=i-dk; j>0&&L[0]<L[j]; j-=dk)
{
L[j+dk] = L[j];
}
L[j+dk] = L[0];
}
}
}
void ShellSort(SqList &L, int n, int dlta[], int t)
{
for(int k=0; k<t; ++k)
{
ShellInsert(L, n, dlta[k]);
}
}
其他的插入排序还有2路-插入排序、表排序,但都不常用。