插入排序是排序的一种,从排序的过程上看大意是在已经排好序的具有n个记录的序列中,为一个新增的记录在序列中找到合适的位置,然后插进去,形成具有n+1个记录的有序序列,当然这种有序并不一定是按照位置有序,可能根据索引等方式实现有序。算法以及相关代码参考严蔚敏《数据结构》插入排序章节,本文作为自己学习的笔记总结。
直接插入排序
直接插入排序的基本操作是将一个记录插入到已经排好序的序表中,从而形成一个新的、记录数增1的有序表。
原理大致为:前n个记录排好序以后,现在为第n+1个记录排序,比方说序列按关键字非递减排序,通过依次把第n+1个记录与第n个到第1个记录作比较,当第i个记录(1<=i<=n)关键字大于第n+1个记录关键字时,把第i个记录移到第i+1个位置上;当出现第i个记录值<=第n+1个记录值时(当然这里指的第n+1个记录值是指开始排序前的第n+1个记录,一次排序过程中,前面的记录可能后移),直接把原来的第n+1个记录插入在第i+1位置上,这时,一次排序完成,停止与前面的记录比较。
如果要排序的序列有x个记录,则从第2个记录到第x个记录执行上述排序过程,共执行x-1次。
直接排序的时间复杂度为O(n^2),适合n较小时排序。
实现函数参见void InsertSort(SqList &)。
折半插入排序
表插入排序
表插入排序不需要移动记录,它根据索引项确定表中记录的排序关系,因为增加了索引项,所以需要比上面排序方法额外的空间,参见定义静态链表结构体struct SLinkList中表节点struct SLNode,用此结构体实现表插入算法。
排序完成的有序表应当是这样的:表头的next指向记录中关键字最小的记录所在的位置,由表头查找到排序后第一个记录,根据第一个记录的next值指向的位置存放的是排序后的第二个记录,依次类推,最后一个记录的next值指向头结点,所以,它们构成了循环链表。
排序过程:首先将静态链表中表头节点和第一个记录节点构成循环链表,实现方法就是更改这两个节点的next,使它们分别指向对方位置;依次从第2个记录开始,插入到循环表,根据next索引实现关键字非递减;比方说已经把前n个记录构成循环链表,现在要把第n+1个记录并入链表中,从头结点的next开始查找表记录,如果记录的关键字小于第n+1记录关键字,则根据next找到下一个记录继续比较;否则就找到插入位置,即关键字不小于第n+1个记录的前面,实现方法就是修改相应的next值;如果最后next值指向表头,则把记录插在表尾。
表插入排序和直接插入排序相比,不同之处是以修改2n次记录代替移动记录,排序过程所需关键字比较过程相同,时间复杂度仍为O(n^2)。
实现函数参见void ListInsertSort(SLinkList &)。
另一方面,表插入排序结果只是求得一个有序链表,只能进行顺序查找,如果要实现有序表的折半查找,要对记录进行重新排列。
重排记录的做法是:顺序扫描有序链表,将链表中第i个结点移动至数组的第i个分量中。在移动后,如果第i个结点原来在数组的第j个分量中,则把第i个分量的next索引改为j值,因为原来第i个分量的记录移动到j后,有序链表没法正确索引到原记录,通过更改next值可以间接查到得到,以后判断next值是原链表的索引值还是更改的间接索引值的判定标准就是要重排的记录m值是否大于i,如果大于,则第i个next是间接索引;否则就是原索引,如果是间接索引就根据间接索引查找相应记录,在进行判定。
重排序实现函数参见void Arrange(SLinkList &L)。
希尔排序
希尔排序又称增量缩小排序,它也是一种属于插入排序类的方法,在时间效率上有较大的改进。
基本思想是:先将整个待排序记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,在进行一次直接插入排序。
分成若干子列的含义是以增量dk为距离形成的子列,对这个子列进行排序,这样较小的记录不是一步一步的挪动,是以dk挪动的,最后一次必须使dk=1,其实就是进行了一次之间插入排序,但是因为前面的排序过程已经基本有序,所以要求移动的记录不多。
对于给定的dk值,进行一次希尔插入排序的函数参见void ShellInsert(SqList &L,int dk)。
给定顺序表,进行希尔排序的函数参见void ShellSort(SqList &L,int dlta[],int t)。
插入排序的具体函数实现
#include <iostream>
using namespace std;
//****************************************************************
#define N 10 //定义顺序表的最大长度
typedef int KeyType; //定义关键字类型为整数类型
//定义记录结构体
struct RedType
{
KeyType key; //为简化处理,只有一个数据项
};
//定义顺序表结构体,下面的直接插入排序、折半插入排序用到的结构体
struct SqList
{
RedType r[N+1]; //定义顺序表结构体,r[0]作为哨兵或者闲置不用
int length; //顺序表长度标识
};
//表节点类型
struct SLNode
{
RedType rc; //数据项
int next; //指针
};
//定义静态链表结构体,进行表插入排序
struct SLinkList
{
SLNode r[N+1]; //r[0]为表头指针
int length; //链表当前长度标识
};
//***************************************************************
//函数声明
void InsertSort(SqList &);
void BInsertSort(SqList &);
void ListInsertSort(SLinkList &);
void Arrange(SLinkList &L);
void ShellSort(SqList &L,int dlta[],int t);
//void Arrange1(SLinkList &L);
//void ShellInsert(SqList &L,int dk);
//mian函数
int main(void)
{
//定义结构体变量,还有10个数据项
SqList list={{{0},{25},{16},{38},{19},{7},{56},{12},{3},{14},{25}},10}; //进行直接插入排序
SqList list2=list; //进行折半插入排序
SqList list4=list; //进行希尔排序
SLinkList list3={{{{0},},{{25},},{{16},},{{38},},{{19},},{{7},},{{56},},{{12},},{{3},},{{14},},{{25},}},10};//进行表插入排序
//用直接插入排序对list进行排序输出排序结果
cout<<"直接插入排序结果如下:"<<endl;
InsertSort(list);
for(int i=1;i<=list.length;i++)
cout<<list.r[i].key<<" ";
cout<<endl<<endl;
//用折半插入排序对list2进行排序输出排序结果
cout<<"折半插入排序结果如下:"<<endl;
BInsertSort(list2);
for(int i=1;i<=list2.length;i++)
cout<<list2.r[i].key<<" ";
cout<<endl<<endl;
//用表插入排序对list3进行排序输出排序结果
cout<<"表插入排序结果如下:"<<endl;
ListInsertSort(list3);
if(list3.length>0)
{
int p=0;
while(list3.r[p].next)
{
p=list3.r[p].next;
cout<<list3.r[p].rc.key<<" ";
}
}
cout<<endl<<endl;
//对list3进行表插入排序后,在对list3进行重新排列,使其按记录关键字递增
cout<<"表插入排序后,对记录重新排列结果如下:"<<endl;
Arrange(list3);
for(int i=1;i<=list3.length;i++)
cout<<list3.r[i].rc.key<<" ";
cout<<endl<<endl;
//希尔排序对list4进行排序输出的结果
cout<<"希尔排序结果如下:"<<endl;
int a[3]={5,3,1};
ShellSort(list4,a,3);
//ShellInsert(list4,1);
for(int i=1;i<=list4.length;i++)
cout<<list4.r[i].key<<" ";
cout<<endl<<endl;
system("pause");
return 0;
}
//********************************************************************
//相应函数定义
//两个关键字类型数字比较大小,a>b返回1,否则返回0
int LeftBig(KeyType a,KeyType b)
{
if(a>b)
return 1;
else
return 0;
}
//直接插入排序函 数InsertSort
void InsertSort(SqList &L)
{
for(int i=2;i<=L.length;i++) //从第二个值开始,依次将第i个记录插入到前i-1个已排序记录中
if(LeftBig(L.r[i-1].key,L.r[i].key))
{
int j;
L.r[0].key=L.r[i].key; //L.r[0]作为哨兵,暂存L.r[i]的值
for(j=i-1;LeftBig(L.r[j].key,L.r[0].key);j--) //循环执行条件为L.r[j]>L.r[0],当L.r[j]<=L.r[0]或者j=0时,for语句不满足执行条件,终止执行
L.r[j+1].key=L.r[j].key; //把大于L.r[i]的关键字的值向后移动位置,等待L.r[i]的插入
L.r[j+1].key=L.r[0].key; //把L.r[0]暂存的L.r[i]的值插入到查找到的位置中
}//if
}//InsertSoft
//折半插入排序函数BInsertSort
void BInsertSort(SqList &L)
{
int low,high,m; //low,high,m分别作为折半查找过程中的最小值、最大值、均值变量
for(int i=2;i<=L.length;i++) //从第二个值开始,依次将第i个记录插入到前i-1个已排序记录中
{
low=1; //在第一个到第i-1个值之间寻找插入位置
high=i-1; //
L.r[0].key=L.r[i].key; //对于不同的i值相应的初始化
while(low<=high) //折半查询插入位置
{
m=(low+high)/2;
if(LeftBig(L.r[m].key,L.r[0].key))
high=m-1; //插入点在高半区
else
low=m+1; //插入点在低半区
}//while
//找到插入位置后移动记录
for(int j=i-1;j>=high+1;--j)
L.r[j+1].key=L.r[j].key;
//插入L.r[i]记录
L.r[high+1].key=L.r[0].key;
}//for
}//BInsertSort
//表插入排序函数ListInsertSort
void ListInsertSort(SLinkList &L)
{
L.r[0].next=1;
L.r[1].next=0; //首先将表头r[0]和第一个数据记录r[1]构成循环链表
int p,q; //记录查找插入位置的过程的指针值
for(int i=2;i<=L.length;i++) //从第二个值开始,依次将第i个记录插入到前i-1个已排序记录中
{
q=0;
p=L.r[0].next;
while(LeftBig(L.r[i].rc.key,L.r[p].rc.key)&&L.r[p].next) //在循环链表中,从表头r[0]开始,依次比较链表上数据主键大小,当
{ //r[i]值不大于r[p]值时,插在p位置前,当r[0]值大于所有已排序链表上的值
q=p; //时,插在表尾
p=L.r[p].next;
}//while
//跳出while循环,已经找到插入点
//插入点在p之前
if(!LeftBig(L.r[i].rc.key,L.r[p].rc.key)) //
{
L.r[q].next=i;
L.r[i].next=p;
}
//插入点在表尾
else
{
L.r[p].next=i;
L.r[i].next=0;
}
}//for
}//ListInsertSort
//对表插入排序后的表进行排列,使其按数组序列有序,函数Arrange1
//本函数有问题,不能正常运行,下一个函数是正确的实现,关于本函数的bug点参见下个函数
void Arrange1(SLinkList &L)
{
int p=L.r[0].next,q=L.r[p].next,i=1;
SLNode node;
while(p)
{
if(p>i)
{
node=L.r[i];
L.r[i]=L.r[p];
L.r[i].next=p;
L.r[p]=node;
i++;
p=q;
}
else if(p==i)
{
i++;
p=q;
}
else
{
p=L.r[p].next;
q=L.r[p].next;
}
}
}
//对表插入排序后的表进行排列,使其按数组序列关键字非递减有序,函数Arrange
//这个函数功能本来打算是用上面的函数Arrange1实现的,但是上面的函数有bug,开始时把q=L.r[p].next;放
//在if/else if语句中,死循环;后来移到else语句中,可以执行但是结果出错;。。。至今不知是两种方法逻辑错在哪里。。。
void Arrange(SLinkList &L)
{
int p=L.r[0].next,q; //p,q分别指示当前要排、下一个要排的记录的位置
SLNode node;
for(int i=1;i<L.length;i++) //前i-1个记录已经排列,查找第i个放到第i个记录上
{
while(p<i)
p=L.r[p].next; //p<i时,说明此记录已经移动到后面,因为前i-1个已经排好,直到找到相应的p>=i
q=L.r[p].next; //找到相应的p后,q标识下一个要排的记录位置
//把p记录移动到i位置上,同时,r[i].next指出p位置,这样可以帮助上面while语句搜寻到i记录的原有值
if(p!=i)
{
node=L.r[i];
L.r[i]=L.r[p];
L.r[i].next=p; //指出移动到的位置
L.r[p]=node;
}
p=q; //开始下一个记录的排列
}//for
}//Arrange
//一次希尔排序的算法函数ShellInsert
void ShellInsert(SqList &L,int dk)
{
//dk是对比的间隔例如dk=3时,第(1,4,7...)、(2,5,8...)...形成的序列进行直接插入排序
int j;
for(int i=dk+1;i<=L.length;i++) //从第dk+1个记录开始排序,这部分跟直接插入排序逻辑实现是一样的,唯一的不同
{ //在于排序的序列是间隔为dk,而不一定是1
if(LeftBig(L.r[i-dk].key,L.r[i].key))
{
L.r[0].key=L.r[i].key;
for(j=i-dk;j>0 && LeftBig(L.r[j].key,L.r[0].key);j-=dk) //循环判断条件加入j>0,因为dk!=1,可能导致j-dk<0,跳过j-dk=0跳出循环
L.r[j+dk].key=L.r[j].key;
L.r[j+dk].key=L.r[0].key;
}//if
}//for
}//ShellInsert
//希尔排序函数ShellSort
void ShellSort(SqList &L,int dlta[],int t)
{
//dlta是数组,里面记录dk值,t为执行希尔插入排序的次数,每次的dk由dlta给出
for(int i=0;i<t;i++)
{
ShellInsert(L,dlta[i]);
}//for
}//ShellSort