折半插入排序
插入类排序就是在一个有序的序列中,插入一个新的关键字,直到所有的关键字都插入形成一个有序的序列。
插入类排序还包括折半插入排序和希尔排序
折半插入排序将比较和移动这两个操作分离出来,也就是先利用折半查找找到插入的位置,然后一次性移动元素,再插入该元素。
排序过程
下标 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
值 | 5 | 13 | 27 | 39 | 45 | 52 | 72 |
- 第①次:i=1,j=7,25<39,所以修改j=3,i不变。
- 第②次:i=1,j=3,13<25,所以修改i=3,j不变。
- 第③次:i=3,j=3,27>25,所以修改j=2,i不变。此时i=3>j=2 查找结束,所以25应该插入到13和27之间,也就是将13后面的元素都向后移动一位,腾出空位给25。
下标 | 1 | 2 | 3 --------> | 4 --------> | 5 --------> | 6 --------> | 7 --------> |
值 | 5 | 13 | 27 | 39 | 45 | 52 | 72 |
插入25
下标 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
值 | 5 | 13 | 25 | 27 | 39 | 45 | 52 | 72 |
插入位置为j+1
算法分析
时间复杂度:
折半插入排序相比直接插入排序只是在寻找待插入位置时比较的次数减少(不是一个一个比较),每个待插入元素比较的次数大约在log2n(折半查找判定树),所以n个元素比较操作的时间复杂度为O(nlog2n)。
比较次数和表的初始状态无关,因为比较次数实际是和折半的次数有关系,只要满足low<high,就要不断折半比较,折半一次比较一次。取决于表的长度n
但是折半插入排序和直接插入排序在移动关键字的次数方面是一样的(也就是说找到了
具体的插入位置后,还是和直接插入排序一样移动后面的关键字腾出空位)所以折半插入排序的时间复杂度还是O(n2)。
稳定性:和直接插入排序稳定性相同,是稳定的。
代码实现
#include <stdio.h>
#define MAXSIZE 8
typedef int KeyType; //定义关键字类型
typedef struct //记录类型
{
KeyType key; //关键字项
} ElemType; //排序的记录类型定义
/*
直接插入排序
参数说明:
arr:传入要排序的数组
n:数组的大小
*/
void InsertSort(ElemType A[] , int n)
{
int i,j,low,high,mid;
for(i=2;i<=n;i++){ //依次将A[2]-A[n]插入前面已排序序列
A[0]=A[i];//将A[i]暂存于A[0]
low=1; //设置折半查找范围
high=i-1;
while(low<=high){//折半查找(默认递增有序)
mid=(low+high)/2;//取中间点
if(A[mid].key>A[0].key) //查找左半边表
high=mid-1;
else low=mid+1; //查找右半边表
}
for(j=i-1;j>=high+1;--j)
A[j+1]=A[j]; //统一后移元素,空出插入位置
A[high+1]=A[0];//插入操作
}
}
int main()
{
ElemType A[MAXSIZE] = {0};
int arr[] = {0,6,5,12,3,2,1,9};
int i;
printf("-----------------折半插入排序----------------\n" );
printf("排序前:");
for(i = 1; i < MAXSIZE; i++)
{
A[i].key = arr[i];
printf("A[%d].key = %d\t" , i , A[i].key);
}
printf("\n");
//插入排序
InsertSort(A, MAXSIZE);
printf("排序后:");
for(i = 2; i < MAXSIZE+1; i++)
{
printf("A[%d].key = %d\t" , i-1 , A[i].key);
}
return 0;
}