插入排序
一、直接插入排序
概述:
直接插入排序是一种最简单的排序方法,它的基本操作是将一个记录插入到已排好序的有序表中,从而得到一个新的、记录数增 1 的有序表。
一般情况下,第 i 趟直接插入排序的操作为:在含有 i-1 个记录的有序子序列 r [1.....i-1] 中插入一个记录 r[i] 后,变成含有 i 个记录的有序子序列。
和顺序查找类似,为了在查找插入位置的过程中避免数组下标出界,在 r[0] 处设置监视哨。
排序过程:
先将序列中的第 1 个记录看成是一个有序的子序列,然后从第 2 个记录起逐个进行插入,直至整个序列变成按关键字非递减有序序列为止。
注意:如图,初始关键字中有两个49,后一个用下划线标注,排序后下划线标注的 49 仍在后面,因此,直接插入排序是稳定的。
直接插入排序的算法性能:
二、折半插入排序
概述:
折半插入排序是直接插入排序与折半查找二者的结合,仍然是将待排序元素插入到前面的有序序列,插入方式也是由后往前插,只不过直接插入排序是边比较边移位。而折半插入排序则是先通过折半查找找到位置后再一并移位,最终将待排序元素赋值到空出位置。
排序过程:
-
初始状态:设序列中第一个为有序,其中 i 为1,即:
-
当 i = 2: low = 1,high = 1,则中间值 m = 1((low+high)/2,下文都是如此计算)。 由于 38<49 (即 r[0]<r[m]),则 high = m-1 = 0 。 由于此时 low > high ,即找到插入位置:high+1。将 49 后移,并将 r[0] 插入。
-
当 i = 3:low = 1,high = 2,则中间值 m = 1((low+high)/2,)。 由于 65>38 (即 r[0]>r[m]),则 low = 1+m = 2,继续找,中间值 m = 2((low+high)/2,)。 由于 65>49 (即 r[0]>r[m]),则 low = 1+m = 3 由于此时 low > high ,即找到插入位置:high+1。将 r[0] 插入。
-
当 i = 4:.....................
-
............
-
.............
-
当 i = 8: low = 1,high = 7,则中间值 m = 4((low+high)/2,下文都是如此计算)。 由于 49 = 49 (即 r[0]=r[m]),则 low = 1+m = 5,继续找,中间值 m = 6((low+high)/2,)。 由于 49 < 76 (即 r[0]<r[m]),则 high = m - 1 = 5,继续找,中间值 m = 5((low+high)/2,)。 由于 49 < 65 (即 r[0]<r[m]),则 high = m - 1 = 4。 由于此时 low > high ,即找到插入位置:high+1。将 65,76,97 后移,并将 r[0] 插入。
折半插入排序的算法性能:
三、2-路插入排序
概述:
2-路插入排序算法,是在直接插入排序和折半插入排序算法上再改进的。主要目的是减少排序过程中的移动的记录次数。但是需要 N 个记录的辅助空间。
具体做法:设置一个和原数组 r 同类型,大小的数组 d,首先将 r[1] 赋值给 d[0]。并且将 d[0] 看成是排好序中处于中间位置的记录,然后从 r 的第二个记录开始比较,依次插入到 d[0] 之前或者之后的有序序列中。
重点是要把辅助数组看成是循环数组,设置first和final标识,来记录辅助数组的开头和结尾。first记录 d 的开头位置。final 记录 d 的结尾位置,排好序以后,从first开始输出排序好的记录。
排序过程:
使用 2-路插入排序算法对无序表{3,1,7,5,2,4,9,6}
排序。
- 将记录 3 添加到数组 d 中:
- 然后将 1 插入到数组 d 中,如下图所示:
- 将记录 7 插入到数组 d 中,如下图所示:
- 将记录 5 插入到数组 d 中,由于其比 7小,但是比 3 大,所以需要移动 7 的位置,然后将 5 插入,如下图所示:
- 将记录 2 插入到数组 d 中,由于比 1大,比 3 小,所以需要移动 3、7、5 的位置,然后将 2 插入,如下图所示:
- 将记录 4 插入到数组 d 中,需要移动 5 和 7 的位置,如下图所示:
- 将记录 9 插入到数组 d 中,如下图所示:
- 将记录 6 插入到数组 d 中,如下图所示:
最终存储在原数组时,从 d[7] 开始依次存储。
2-路插入排序的算法性能:
实现代码
typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int Boolean; /* Boolean是布尔类型,其值是TRUE或FALSE */
#include<malloc.h> /* malloc()等 */
#include<stdio.h> /* EOF(=^Z或F6),NULL */
#include<process.h> /* exit() */
/* 函数结果状态代码 */
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
/* 对两个数值型关键字的比较约定为如下的宏定义 */
#define EQ(a,b) ((a)==(b))
#define LT(a,b) ((a)<(b))
#define LQ(a,b) ((a)<=(b))
/* ------------------------------- 待排记录的数据类型 --------------------------------*/
typedef int InfoType; /* 定义其它数据项的类型 */
#define MAXSIZE 20 /* 一个用作示例的小顺序表的最大长度 */
typedef int KeyType; /* 定义关键字类型为整型 */
typedef struct
{
KeyType key; /* 关键字项 */
InfoType otherinfo; /* 其它数据项,具体类型在主程中定义 */
}RedType; /* 记录类型 */
typedef struct
{
RedType r[MAXSIZE + 1]; /* r[0]闲置或用作哨兵单元 */
int length; /* 顺序表长度 */
}SqList; /* 顺序表类型 */
/* ------------------------------------------------------------------------------------------*/
/* ----------------------------- 顺序表插入排序的函数(3个) -------------------------------*/
void InsertSort(SqList *L)
{ /* 对顺序表L作直接插入排序。算法10.1 */
int i, j;
for (i = 2; i <= (*L).length; ++i)
if LT((*L).r[i].key, (*L).r[i - 1].key) /* "<",需将L.r[i]插入有序子表 */
{
(*L).r[0] = (*L).r[i]; /* 复制为哨兵 */
for (j = i - 1; LT((*L).r[0].key, (*L).r[j].key); --j)
(*L).r[j + 1] = (*L).r[j]; /* 记录后移 */
(*L).r[j + 1] = (*L).r[0]; /* 插入到正确位置 */
}
}
void BInsertSort(SqList *L)
{ /* 对顺序表L作折半插入排序。算法10.2 */
int i, j, m, low, high;
for (i = 2; i <= (*L).length; ++i)
{
(*L).r[0] = (*L).r[i]; /* 将L.r[i]暂存到L.r[0] */
low = 1;
high = i - 1;
while (low <= high)
{ /* 在r[low..high]中折半查找有序插入的位置 */
m = (low + high) / 2; /* 折半 */
if LT((*L).r[0].key, (*L).r[m].key)
high = m - 1; /* 插入点在低半区 */
else
low = m + 1; /* 插入点在高半区 */
}
for (j = i - 1; j >= high + 1; --j)
(*L).r[j + 1] = (*L).r[j]; /* 记录后移 */
(*L).r[high + 1] = (*L).r[0]; /* 插入 */
}
}
void P2_InsertSort(SqList *L)
{ /* 2_路插入排序 */
int i, j, first, final;
RedType *d;
d = (RedType*)malloc((*L).length * sizeof(RedType)); /* 生成L.length个记录的临时空间 */
d[0] = (*L).r[1]; /* 设L的第1个记录为d中排好序的记录(在位置[0]) */
first = final = 0; /* first、final分别指示d中排好序的记录的第1个和最后1个记录的位置 */
for (i = 2; i <= (*L).length; ++i)
{ /* 依次将L的第2个~最后1个记录插入d中 */
if ((*L).r[i].key < d[first].key)
{ /* 待插记录小于d中最小值,插到d[first]之前(不需移动d数组的元素) */
first = (first - 1 + (*L).length) % (*L).length; /* 设d为循环向量 */
d[first] = (*L).r[i];
}
else if ((*L).r[i].key > d[final].key)
{ /* 待插记录大于d中最大值,插到d[final]之后(不需移动d数组的元素) */
final = final + 1;
d[final] = (*L).r[i];
}
else
{ /* 待插记录大于d中最小值,小于d中最大值,插到d的中间(需要移动d数组的元素) */
j = final++; /* 移动d的尾部元素以便按序插入记录 */
while ((*L).r[i].key < d[j].key)
{
d[(j + 1) % (*L).length] = d[j];
j = (j - 1 + (*L).length) % (*L).length;
}
d[j + 1] = (*L).r[i];
}
}
for (i = 1; i <= (*L).length; i++) /* 把d赋给L.r */
(*L).r[i] = d[(i + first - 1) % (*L).length]; /* 线性关系 */
}
/* ------------------------------------------------------------------------------------------*/
/* 检验以上操作的主程序 */
void print(SqList L)
{
int i;
for (i = 1; i <= L.length; i++)
printf("(%d,%d)", L.r[i].key, L.r[i].otherinfo);
printf("\n");
}
#define N 8
void main()
{
RedType d[N] = { {49,1},{38,2},{65,3},{97,4},{76,5},{13,6},{27,7},{49,8} };
SqList l1, l2, l3;
int i;
for (i = 0; i < N; i++) /* 给l1.r赋值 */
l1.r[i + 1] = d[i];
l1.length = N;
l2 = l3 = l1; /* 复制顺序表l2、l3与l1相同 */
printf("排序前:\n");
print(l1);
InsertSort(&l1);
printf("直接插入排序后:\n");
print(l1);
BInsertSort(&l2);
printf("折半插入排序后:\n");
print(l2);
P2_InsertSort(&l3);
printf("2_路插入排序后:\n");
print(l3);
}
运行结果: