插入排序是一种简单直观的排序方法,其基本思想是每次将一个待排序的记录按其关键词大小插入前面已经排好的子序列,直到全部记录插入完成。
一,直接插入排序:从小到大排序
数组序号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
待排序列 | 49 | 38 | 65 | 97 | 76 | 13 | 27 | 49 |
第一轮 | 38 | 49 | 65 | 97 | 76 | 13 | 27 | 49 |
第二轮 | 38 | 49 | 65 | 97 | 76 | 13 | 27 | 49 |
第一轮,数组序号为1开始与前面元素进行比较,38 <49,所以将38与49交换位置
第二轮:数组序号为3开始与前面的元素进行比较,49,38都小于65,故位置不变
代码展示:
//直接插入排序,从小到大进行排列
void InsertSort(Str &L)
{
for(int i = 1; i < L.length; ++i)//最开始从第二个元素和前面元素相比
{
if(L.data[i] < L.data[i-1])//如果后面的元素比前面的元素小,执行后续操作
{
int temp = L.data[i];//先将元素存储在一个定义的元素中,防止被覆盖
int j;
for(j = i-1; j >= 0 && L.data[j] > temp; --j)//从i-1的那个元素开始前面的元素都和temp的元素进行比较,如果比temp元素大,就依次往后移动
{
L.data[j+1] = L.data[j];//如果比temp元素大,就依次往后移动
}
L.data[j+1] = temp;//加入说前面元素都是比temp中元素大,那么最后当j = -1是不满足循环条件,所以j应该加1,其他位置也是同样的逻辑
}
}
}
结果:
二,折半插入排序
将待排序元素前面已经排序好的元素进行折半查找,找到位置后,将元素逐个往后移动,然后再将待排序元素赋值到该位置上
以下面为例讲解:13是待排元素
数组序号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
待排序列 | 13 | 38 | 49 | 65 | 76 | 97 | 13 | 27 | 49 |
第一次 | low | mid | high | ||||||
第二次 | low mid | high | |||||||
第三次 | high | low | |||||||
后移 | 13 | 38 | 38 | 49 | 65 | 76 | 97 | 27 | 49 |
13 | 38 | 49 | 65 | 76 | 97 | 27 | 49 |
第一次的时候:low = 1, high = 5, mid = (low+high)/2 = 3,对应元素65>13,进行第二次
第二次的时候:low = 1, high = mid-1 = 2, mid = (low+high)/2 = 1,向下取整;mid对应元素为38>13,进行第三次
第三次的时候:low = 1; high = mid-1 = 0;此时low>high,结束
然后将元素比13大的元素全部后移,最后将数组序号为0的元素赋值到序号为low的位置
代码展示:
//折半插入排序
void Binary_InsertSort(Str &L)
{
int i,j,low,high,mid;
for(i = 2; i <= L.length; ++i)//从第二个元素开始,依次向后
{
L.data[0] = L.data[i];//将需要和前面元素进行比较的元素放在L.data[0]中,防止被覆盖
low = 1;//折半插入排序中折半过程是对前面已经排序好的元素进行折半,所以最开low = 1
high = i-1;//i前面的元素是已经排序好的,所以high = i-1;
while(low <= high)//循环条件
{
mid = (low+high)/2;//中间值,向下取整
if(L.data[mid] > L.data[0])//查找左半子表
high = mid-1;
else
low = mid+1;//查找右半子表,当等于的时候也是将low = mid+1,这样就可以使数组稳定
}
for(j = i-1; j >= low; --j)
{
L.data[j+1] = L.data[j];//统一后移元素,空出插入位置
}
L.data[low] = L.data[0];//将元素插入空出来的位置
}
}
结果:
三,希尔排序
将待排序列分割若干的”特殊“子表,即把相隔某个”增量“(缩小增量)的记录组成一个子表,对各个子表分别进行直接插入排序,当整个表中的元素以呈”基本有序“时,再对全体记录进行一次直接插入排序。
每次将增量缩小一半,最开始为:8/2 = 4;考试中可能遇到各种增量
数组序号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
待排序列 | 49 | 38 | 65 | 97 | 76 | 13 | 27 | 49 | |
子表1 | 49 | 76 | |||||||
子表2 | 38 | 13 | |||||||
子表3 | 65 | 27 | |||||||
子表4 | 97 | 49 | |||||||
第一趟排序结果 | 49 | 13 | 27 | 49 | 76 | 38 | 65 | 97 | |
增量变为:4/2 = 2
数组序号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
待排序列 | 49 | 13 | 27 | 49 | 76 | 38 | 65 | 97 | |
子表1 | 49 | 27 | 76 | 65 | |||||
子表2 | 13 | 49 | 38 | 97 | |||||
排序后子表1 | 27 | 49 | 65 | 76 | |||||
排序后1子表2 | 13 | 38 | 49 | 97 | |||||
第二趟排序结果 | 27 | 13 | 49 | 38 | 65 | 49 | 76 | 97 |
后续增量变为:2/2 =1,变成直接插入排序
代码展示:
//希尔排序
void ShellSort(Str &L)
{
int dk,i,j;
for(dk = L.length/2; dk >= 1; dk = dk/2)//最开始令缩小增量dk为待排序列元素个数的一半,当dk = 1变成直接插入排序
{
for(i = 1; i < dk+1; ++i)//每次使用缩小变量组成子表的个数为dk,每个子表的第一个元素是从1到dk
{
for(j = i+dk; j <= L.length; j = j+dk)//从子表的第二个元素开始,循环
{
L.data[0] = L.data[j];//先将待排序元素存储到数组序号为0的位置上,以免移动时元素被覆盖无法找到
if(L.data[j-dk] > L.data[0])//如果子表中以排序元素大于待排序元素
{
for(j; j > i && L.data[j-dk] > L.data[0]; j = j-dk)//j为待排序元素序号,如果已排序元素的序号大于子表第一个元素序号并且大于待排序元素,循环,从后往前,依次访问
{
L.data[j] = L.data[j-dk];//满足条件之后从后往前将元素依次往后移动
}
L.data[j] = L.data[0];//最后将数组序号为0中的元素赋值到腾出的位置,从前面可以分析出,如果子表第一个满足条件,执行循环语句之后,j就成为子表第一个元素所在序号,所以L.data[j] = L.data[0]
}
}
}
}
}
结果:
课本上的希尔排序代码如下:
相当于几个子表循环进行元素比较移动
//课本希尔排序代码
void ShellSort(ElemType A[],int n)
{
//A[0]只是暂存单位,不是哨兵,当j <= 0时,插入位置已到
int dk,i,j;
for(dk = n/2; dk >=1; dk = dk/2)
{
for(i = dk+1; i <= n; ++i)
{
if(A[i] < A[i-dk])
{
A[0] = A[i];
for(j = i-dk; j > 0 && A[0] < A[j]; j -= dk)
{
A[j+dk] = A[j];
}
A[j+dk] = A[0];
}
}
}
}
插入排序 | 空间复杂度 | 时间复杂度 | 稳定性 | 适用性 |
直接插入排序 | O(1) | O() | 稳定 | 顺序存储和链式存储 |
折半插入排序 | O(1) | O() | 稳定 | 顺序存储 |
希尔排序 | O(1) | O() | 稳定 | 顺序存储 |
稳定性:待排序中有相同的元素,在进行某种算法的排序之后,若是这些相同元素的相对位置不发生改变的话,就称这种排序算法是稳定的
算法是否稳定不能够衡量一个算法的优劣,它主要对算法的性质进行描述。
完整代码:
#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>
#define Size 8
//定义动态分配内存数组
typedef struct
{
int *data;
int length;
}Str;
//函数说明
void CreatString(Str &L);
void CreatString1(Str &L);
void InsertSort(Str &L);
void StrPrint(Str L);
void StrPrint1(Str L);
void Binary_InsertSort(Str &L);
void ShellSort(Str &L);
int main(void)
{
Str L;
CreatString(L);
printf("数组元素为:\n");
StrPrint(L);
InsertSort(L);
printf("直接插入排序之后数组元素为:\n");
StrPrint(L);
Str L1;
CreatString1(L1);
printf("折半插入排序之后数组元素为:\n");
Binary_InsertSort(L1);
StrPrint1(L1);
Str L2;
CreatString1(L2);
printf("希尔排序之后数组元素为:\n");
ShellSort(L2);
StrPrint1(L2);
return 0;
}
void CreatString(Str &L)
{
L.data = (int *)malloc(sizeof(int)*Size);
L.length = Size;
int val;
for(int i = 0; i < L.length; ++i)
{
printf("请输入数组第%d个值:",i+1);
scanf_s("%d",&val);
L.data[i] = val;
}
}
void CreatString1(Str &L)
{
L.data = (int *)malloc(sizeof(int)*Size+1);
L.length = Size;
int val;
for(int i = 1; i <= L.length; ++i)
{
printf("请输入数组第%d个值:",i);
scanf_s("%d",&val);
L.data[i] = val;
}
}
//直接插入排序,从小到大进行排列
void InsertSort(Str &L)
{
for(int i = 1; i < L.length; ++i)//最开始从第二个元素和前面元素相比
{
if(L.data[i] < L.data[i-1])//如果后面的元素比前面的元素小,执行后续操作
{
int temp = L.data[i];//先将元素存储在一个定义的元素中,防止被覆盖
int j;
for(j = i-1; j >= 0 && L.data[j] > temp; --j)//从i-1的那个元素开始前面的元素都和temp的元素进行比较,如果比temp元素大,就依次往后移动
{
L.data[j+1] = L.data[j];//如果比temp元素大,就依次往后移动
}
L.data[j+1] = temp;//加入说前面元素都是比temp中元素大,那么最后当j = -1是不满足循环条件,所以j应该加1,其他位置也是同样的逻辑
}
}
}
//遍历输出
void StrPrint(Str L)
{
for(int i = 0; i < L.length; ++i)
{
printf("%d ",L.data[i]);
}
printf("\n");
}
void StrPrint1(Str L)
{
for(int i = 1; i <= L.length; ++i)
{
printf("%d ",L.data[i]);
}
printf("\n");
}
//折半插入排序
void Binary_InsertSort(Str &L)
{
int i,j,low,high,mid;
for(i = 2; i <= L.length; ++i)//从第二个元素开始,依次向后
{
L.data[0] = L.data[i];//将需要和前面元素进行比较的元素放在L.data[0]中,防止被覆盖
low = 1;//折半插入排序中折半过程是对前面已经排序好的元素进行折半,所以最开low = 1
high = i-1;//i前面的元素是已经排序好的,所以high = i-1;
while(low <= high)//循环条件
{
mid = (low+high)/2;//中间值,向下取整
if(L.data[mid] > L.data[0])//查找左半子表
high = mid-1;
else
low = mid+1;//查找右半子表,当等于的时候也是将low = mid+1,这样就可以使数组稳定
}
for(j = i-1; j >= low; --j)
{
L.data[j+1] = L.data[j];//统一后移元素,空出插入位置
}
L.data[low] = L.data[0];//将元素插入空出来的位置
}
}
//希尔排序
void ShellSort(Str &L)
{
int dk,i,j;
for(dk = L.length/2; dk >= 1; dk = dk/2)//最开始令缩小增量dk为待排序列元素个数的一半,当dk = 1变成直接插入排序
{
for(i = 1; i < dk+1; ++i)//每次使用缩小变量组成子表的个数为dk,每个子表的第一个元素是从1到dk
{
for(j = i+dk; j <= L.length; j = j+dk)//从子表的第二个元素开始,循环
{
L.data[0] = L.data[j];//先将待排序元素存储到数组序号为0的位置上,以免移动时元素被覆盖无法找到
if(L.data[j-dk] > L.data[0])//如果子表中以排序元素大于待排序元素
{
for(j; j > i && L.data[j-dk] > L.data[0]; j = j-dk)//j为待排序元素序号,如果已排序元素的序号大于子表第一个元素序号并且大于待排序元素,循环,从后往前,依次访问
{
L.data[j] = L.data[j-dk];//满足条件之后从后往前将元素依次往后移动
}
L.data[j] = L.data[0];//最后将数组序号为0中的元素赋值到腾出的位置,从前面可以分析出,如果子表第一个满足条件,执行循环语句之后,j就成为子表第一个元素所在序号,所以L.data[j] = L.data[0]
}
}
}
}
}