一般有直接插入、折半插入、希尔排序三种。
三种插入排序的基本思想大致相同
给定一个序列a[1...n]
前两种将一个序列分成有序部分(sorted)和无需部分(unsorted), 循环遍历序列a, 当遍历到第r个下标时, 区间 [1,r-1] 是有序部分,区间[r, n]是无序的,当前任务就是讲下标为r的数插入到有序部分,将区间[1,r]变为有序,这样有序区间长度加一,无序区间长度减一,循环结束后区间[1,n]内的数就是有序的,排序完成。
代码如下:
#include
using namespace std;
#define MAXSIZE 100
typedef int KeyType;
typedef struct///定义每个节点信息
{
KeyType key;
// InfoType otherinfo;
}RType;
typedef struct///定义顺序表
{
RType r[MAXSIZE + 1];
int length;
}SqList;
void InsertSort(SqList &L)
{
for(int i = 2; i <= L.length; i ++)
{
/***
r[1...i-1]有序,r[i+1...n]无序
对于当前循环需要将r[i]插入到有序段中,使r[1...r]有序,
即找到r[i] 在 r[1...i-1]中的插入位置即可
***/
if(L.r[i-1].key > L.r[i].key)
{
///当r[i] < r[i-1] 时需要将r[i]插入到区间r[1...i-1]中
L.r[0] = L.r[i];
int j;
for(j = i - 1; L.r[0].key < L.r[j].key; j --)
{
///寻找插入位置,当r[0] > r[j]则跳出循环,j+1就是r[i]要插入的位置
///这里体会r[0]的妙用
L.r[j+1] = L.r[j];
}
L.r[j+1] = L.r[0];
}
}
}
int main()
{
SqList L;
printf("请输入序列的个数:");
scanf("%d", &L.length);
printf("\n\n请输入序列:");
for(int i = 1; i <= L.length; i ++)
{
scanf("%d", &L.r[i].key);
}
InsertSort(L);
printf("\n\n");
for(int i = 1; i <= L.length; i ++)
{
printf("%5d", L.r[i].key);
}
printf("\n");
return 0;
}
/**
7
49 38 65 97 76 13 27
10
18 73 40 84 71 59 64 85 41 98
**/
和直接插入排序差不多,不同的是使用折半查找来寻找第i个数在区间r[1...i-1]中的插入位置,减少了关键字的比较
代码如下:
#include
using namespace std;
#define MAXSIZE 100
typedef int KeyType;
typedef struct///定义每个节点信息
{
KeyType key;
// InfoType otherinfo;
}RType;
typedef struct///定义顺序表
{
RType r[MAXSIZE + 1];
int length;
}SqList;
void BInsertSort(SqList &L)
{
for(int i = 2; i <= L.length; i ++)
{
L.r[0] = L.r[i];
int left = 1, right = i - 1, mid;
while(left <= right)
{
mid = (left + right) >> 1;
if(L.r[0].key < L.r[mid].key)right = mid - 1;
else left = mid + 1;
}
///插入位置是 right + 1,思考为什么?
for(int j = i - 1; j >= right + 1; j --)
{
L.r[j+1] = L.r[j];
}
L.r[right+1] = L.r[0];
}
}
int main()
{
SqList L;
printf("请输入序列的个数:");
scanf("%d", &L.length);
printf("\n\n请输入序列:");
for(int i = 1; i <= L.length; i ++)
{
scanf("%d", &L.r[i].key);
}
BInsertSort(L);
printf("\n\n");
for(int i = 1; i <= L.length; i ++)
{
printf("%5d", L.r[i].key);
}
printf("\n");
return 0;
}
/**
7
49 38 65 97 76 13 27
10
18 73 40 84 71 59 64 85 41 98
**/
思考插入位置是 right + 1。
注意插入的位置满足大于等于 r[i] 的第一位
以上是循环里面的两种条件:
当r[0] < r[mid],此时right = mid - 1, 如果r[0] >= r[right],则插入位置是就是right+1;
当r[0] >= r[mid],则left = mid +1, 如果r[0] <= r[left], 所以left就是插入位置,然后接下来的循环中right始终都是mid-1,最终循环结束时
right = left - 1;
随着循环的进行,其他情况都能得到以上两种情况
同时循环结束条件是left = right + 1,所以程序中插入位置right+1也能用left代替
直接插入排序与折板插入排序的时间复杂度都是O(n^2),移动次数也一样,不过后者的关键字比较次数较少,空间复杂度均是O(1)。
将序列分成若干组,对每一组进行插入排序
代码如下:
#include
using namespace std;
#define MAXSIZE 100
typedef int KeyType;
typedef struct///定义每个节点信息
{
KeyType key;
// InfoType otherinfo;
}RType;
typedef struct///定义顺序表
{
RType r[MAXSIZE + 1];
int length;
}SqList;
///相当于对每一个分组进行直接插入排序
void ShellInsert(SqList &L, int dk)
{
for(int i = dk + 1; i <= L.length; i ++)
{
if(L.r[i-dk].key > L.r[i].key)
{
L.r[0] = L.r[i];
int j;
for(j = i - dk; j > 0 && L.r[0].key < L.r[j].key; j -= dk)
{
L.r[j+dk] = L.r[j];
}
L.r[j+dk] = L.r[0];
}
}
}
void ShellSort(SqList &L, int d[], int t)
{
for(int i = 0; i < t; i ++)
{
ShellInsert(L, d[i]);
}
}
int main()
{
SqList L;
printf("请输入序列的个数:");
scanf("%d", &L.length);
printf("\n\n请输入序列:");
for(int i = 1; i <= L.length; i ++)
{
scanf("%d", &L.r[i].key);
}
int d[3] = {5, 3, 1};
int t = 3;
ShellSort(L, d, t);
printf("\n\n");
for(int i = 1; i <= L.length; i ++)
{
printf("%5d", L.r[i].key);
}
printf("\n");
return 0;
}
/**
以d[0] = 5, d[1] = 3, d[2] = 1 为例
7
49 38 65 97 76 13 27
10
18 73 40 84 71 59 64 85 41 98
**/
对以上的算法用到一个分组函数d[n], 不过目前还任未找到一个较好的函数。
注意:最后一个分组必定是1,来确保最终的序列有序。
此时算法就退化到直接插入排序,不过经过前面的对每个分组排序后,一般序列基本有序,此时移动次数较少。
总体来说,希尔排序的时间复杂度可减小到n(log n)^2, 空间复杂度O(1)。