基本排序算法总结与对比 之三 ——插入排序
1、直接插入排序
直接插入排序过程如下图所示:
① 从第一个元素开始 仅一个3 为有序数列,紧跟有序数列后的 第一个元素 1 往有序数列里插入,形成包含 2 个元素 1, 3 的有序数列;
② 紧跟有序数列后的 第一个元素 4 往有序数列里插入,形成包含 3 个元素 1, 3, 4 的有序数列;
③ 紧跟有序数列后的 第一个元素 5 往有序数列里插入,形成包含 4 个元素 1, 3, 4, 5 的有序数列;
④ 紧跟有序数列后的 第一个元素 2 往有序数列里插入,形成包含 5 个元素 1, 2, 3, 4, 5 的有序数列; 排序完成。
显然,最耗时的过程为:插入元素后,插入点后边元素 往后移的过程。
直接插入排序代码如下:
template<typename T>
void insertSort(T arr[], int lo, int hi)
{
T tmp;
for(int i = lo + 1; i < hi; i++) //待插入的元素从lo+1开始,到hi-1停止;区间[i,hi)
{
tmp = arr[i];
int j = i - 1;
while(j >= lo && tmp < arr[j]) //遍历有序数组[lo,i),大于arr[i]的往后移
{
arr[j + 1] = arr[j];
j--;
} //循环结束后,j 为 小于等于 arr[i] 的最大秩
// j + 1 为 大于 arr[i] 的最小秩
arr[j + 1] = tmp;
}
}
下面是另一种写法,两种写法实现过程、原理都是一样的,二者取一即可。(下面这种写法的 移动元素的部分 与折半插入排序 的写法 相同。 )
template<typename T>
void insertSort(T arr[], int lo, int hi)
{
T tmp;
for (int i = lo + 1; i < hi; i++) //待插入的元素从lo+1开始,到hi-1停止;区间[i,hi)
{
for (int j = lo; j < i; j++) //遍历[lo,i)的有序数列
{
if (arr[i] < arr[j]) //如果在有序数列中发现第一个比arr[i]大的元素arr[j]则
{
tmp = arr[i];
for (int k = i; k > j; k--) //从arr[j]开始到arr[i-1]的每个元素往后挪一位
{
arr[k] = arr[k - 1];
}
arr[j] = tmp; //再arr[i]插到arr[j]前面
}
}
}
}
以上代码所示的插入排序:
在完全顺序时,比较次数为n(n-1)/2,交换次数为0,平均时间复杂度为O(n^2)
在完全逆序时,比较次数为n-1,交换次数为n(n-1)/2,平均时间复杂度也为O(n^2)
即使忽视比较所耗时间,算法的平均复杂度仍然为O(n^2)
2、折半插入排序
直接插入排序在寻找待排元素在有序数列中的位置时,使用的是线性查找方法;既然已知是有序数列,在查找待排元素位置时也可以使用二分查找方法。 使用二分查找的插入排序及折半插入排序。
template<typename T>
void insertSort(T arr[], int lo, int hi)
{
T tmp;
int _lo, _hi;
for(int i = lo + 1; i < hi; i++)
{
/**
此处 二分查找部分 可参考 邓俊辉 《数据结构》 p56
*/
_lo = lo, _hi = i; //在有序向量的 区间[lo, i) 内查找元素
while(_lo < _hi)
{
int _mi = _lo + (_hi - _lo) >> 1;
arr[i] < arr[_mi] ? _hi = _mi : _lo = _mi + 1;
} //循环结束时 _lo 即为大于 arr[i] 的最小秩
//查找失败标志 为 _lo = i (i 为右开区间,即 查找的数 比 有序数列 中 任何数 都大)
if(_lo != i)
{
tmp = arr[i];
for(int k = i; k > _lo; k--)
{
arr[k] = arr[k - 1];
}
arr[_lo] = tmp;
}
}
}
需要指出的是,尽管使用了二分查找方法定位元素位置,但折半插入排序的时间复杂度仍然为O(n^2),交换操作的时间复杂度仍为O(n^2);但是,对于有些 比较操作 比较耗时的数据类型,适合采取折半插入排序。