插入排序是一种原地排序算法,即它的空间复杂度为o(1),原始插入排序的最坏(即原始序列的顺序刚好和我们所希望的顺序相反)和平均时间复杂度均为o(n*n),而在最好的情况下(即原始序列已经是按照我们所希望的顺序排好的序列)为o(n)。
下面的例子展示原始插入排序的整个过程:整个序列存放在数组中,按照从小到大的顺序进行排列。
8 2 4 9 3 6.//原始序列
从第二个元素开始,每个元素依次与其前面已经排好序的每个元素进行比较,直到找到正确的插入位置为止。
对于元素2(首先使用一个关键变量(key)存放它),它前面的8比它大,于是8往后移动,整个序列变成:
8 8 4 9 3 6
此时2前面的所有元素比较完毕,并使用key覆盖第一个8,于是序列变成:
2 8 4 9 3 6
对于元素4,4与8比较,8大于4,于是8往后移动,序列变成:
2 8 8 9 3 6
4再与2比较,2小于4,退出循环,4插入2 和8之间,于是用4覆盖第一个8,序列变成:
2 4 8 9 3 6
依次类推。实现的代码如下:
void InsertSort(int a[], int length)
{
if(NULL == a)
return;
if(length <= 0)
return;
for(int i =1; i < length; i++)
{
int key = a[i];
int j = i - 1;
while(j >= 0 && a[j] > key)
{
a[j+1] = a[j];
j--;
}
a[j+1] = key;
}
}对于原始插入排序,新插入的元素几乎和已经排好序的每个元素比较了一次(事实上可能不是这样的,如果新插入的元素位于已经排好序的序列中间某个位置),对于在已经排好序的序列中寻找新元素的插入位置,其实没有必要这样一一比较,既然序列已经是有序的,我们自然想到了二分查找的方法来寻找插入的位置。我们称之为折半插入排序,实现的代码如下:
void halfInsertSort(int a[], int length)
{
if(NULL == a)
return;
if(length <= 0)
return;
for(int i =1; i < length; i++)
{
int key = a[i];
int low = 0;
int high = i -1;
int mid=0 ;
int index = 0;//插入位置
while(low <= high)//二分查找法搜素插入位置
{
mid = (low + high)/2;
if(key < a[mid] )
high = mid - 1;
else
low = mid + 1;
}
if(key > a[mid] )
index = mid + 1;
else
index = mid;
for(int k = i; k > index; k--)//插入位置以后的元素整体向后移动
a[k] = a[k-1];
a[index] = key;
}
}理论上来说,折半插入排序减少了新插入元素与已经排好序的元素的比较次数,比较的时间复杂度从o(n*n)下降到o(nlogn)
两种算法的时间复杂度的验证:
算法运行环境为:AMD单核,主频2.1GHz.
随机产生一个具有100,000个元素的数组
第一次试验:原始插入排序:16782ms, 折半插入排序:15812ms,折半插入排序比原始插入排序少970ms
第二次试验:原始插入排序:16656ms, 折半插入排序:16047ms,折半插入排序比原始插入排序少619ms
第三次试验:原始插入排序:16594ms, 折半插入排序:16188ms,折半插入排序比原始插入排序少406ms
其实通过上面的数据可以看出来,折半插入排序并没有比原始插入排序快多少,实际上折半插入排序的比较次数确实比原始插入排序的比较次数少很多,下面是忽略折半插入排序算法中插入位置以后的元素整体向后移动的过程两种算法比较,即对于折半插入排序我们只寻找插入位置
原始插入排序:17188ms, 折半插入排序:15ms,折半插入排序比原始插入排序少17,173ms,可以看出折半插入排序寻找插入位置的时间是非常小的。最大的问题出在在数组中插入一个元素其效率是很低的,因为在数组中插入一个元素后,插入位置以后的元素必须整体移动。
所以对于一个需要经常做插入的序列,最好是用链表进行存储。
参考资料:<<算法之道>>
本文对比了原始插入排序和折半插入排序在实际运行中的性能,通过多次试验发现,虽然折半插入排序在寻找插入位置上速度明显更快,但由于数组插入操作的低效,整体运行时间并未显著优于原始插入排序。建议对于频繁插入操作的序列,考虑使用链表存储。
9956

被折叠的 条评论
为什么被折叠?



