一、基本思想
直接插入排序是一种较为简单的排序算法,形象地比喻,就很像你打扑克牌时,不停的向你已经排好的牌中插入你的牌,直到你“牌”插完,一副牌也就牌好了(下图只是看看,打的时候反正我从不这样拿牌!)
但好歹是这一个众所皆知的排序算法,肯定有较为正式一点的说法,如是:插入排序通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入 ,如此重复,直至完成序列排序。
过程如下面网图,把未点亮的图当作你还没有摸到的牌,当你拿到“牌”时就要在你已经排好的牌中,一张一张地从后往前看,直到找到一张比你小的牌,你就可以安心插入了:
------------------------*注意: 为了下文便于理解, “关键位”替换为"牌" ----------------------------------
二、算法实现
1.直接插入排序
遵从上面的思想,我们便来用代码实现一下:
//插入排序
void InsertSort(int* arr, int n)
{
for (int i = 0; i < n - 1; i++)//第一层循环,保证所有“牌”(数据)都拿到
{
int end = i;//每次从已经排好的“牌”(序列)最后一个开始
int tmp = arr[end + 1];//拿“牌”
while (end >= 0)//开始从后往前遍历,找比手中"牌"小的
{
if (tmp < arr[end])//比手中“牌”大的,往后挪
{
arr[end+1] =arr[end];
end--;
}
else
{
break;//找的比手中“牌”小的,不找了
}
}
arr[end + 1] = tmp;//插在比手中“牌”小的后面
}
}
下面看看该代码的效果
输入:
输出:
2.二分插入排序
上个方法,虽然说完成了任务,但是各位没有觉得次次都要比较,很累吗?各位真的没有在找比手中“牌”小的牌时想到二分查找吗?没错二分插入排序就是将查找部分改为了二分查找,形成了直接插入排序的变种-二分插入排序。
代码实现如下:
// 二分插入排序
void BinInsertSort(int* a, int n)
{
for (int i = 0; i < n-1; i++)
{
int left = 0;
int right = i;
int temp = a[i+1];
while (left <= right)//"="为了得出“牌”最终的位置
{
int mid = (left + right) / 2; //二分区域
if (a[mid] > temp)
{
right = mid-1; //向左缩小区域
}
else
{
left = mid+1; //向右缩小区域
}
}
for (int j = i+1; j > left; j--) //vi[left,i-1]的元素整体后移
{
a[j] = a[j-1];
}
a[right+1] = temp;
}
}
输入:
输出:
需要注意的是,当left与right相等时,并不是找到了比手中牌小的位置,而是找到了一个合适的位置,只有在进行一次比较,才能确定牌的位置,所以在第一层循环时的条件加了“=”。
如下图:
同理,当“牌”换成10时,right不会动,a[right+1],"牌"依旧处于原位置。
三、性能分析
1 时间复杂度:
(1)顺序排列时,只需比较(n-1)次,插入排序时间复杂度为O(n);
(2)逆序排序时,需比较n(n-1)/2次,插入排序时间复杂度为O(n^2);
(3)当原始序列杂乱无序时,平均时间复杂度为O(n^2)。
2 空间复杂度:
插入排序过程中,只需要一个临时变量temp存储待排序元素,因此空间复杂度为O(1)。
3 算法稳定性:
插入排序是一种稳定的排序算法。
*注意:二分插入仅供思路,所以未加入分析