在学数据结构的第一节课就知道了数据结构课程是要管理并且学会操作数据,当然操作数据首先想到的就是数据的排序,排过顺序的数据的使用价值才够大。前面我们学习了顺序表也学习了链表等等,这些就是储存数据的方法,下面我们来看一看插入排序的特点与效率怎么样。
英文名:Insertion Sort
插入排序的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
趣味解释:插入排序操作类似于摸牌并将其从大到小排列。每次摸到一张牌后,根据其点数插入到确切位置。
如上图:表示的是摸到梅花7后进行插入的过程。忽略最右边的梅花10,相当于一开始7在最右边,然后逐个与左边的排相比较(当然左边的牌早已排好顺序),将其放置在合适的位置。当摸到后面的牌后重复上述过程即可。
① 从第一个元素开始,该元素可以认为已经被排序
② 取出下一个元素,在已经排序的元素序列中从后向前扫描
③如果该元素(已排序)大于新元素,将该元素移到下一位置
④ 重复步骤③,直到找到已排序的元素小于或者等于新元素的位置
⑤将新元素插入到该位置后
⑥ 重复步骤②~⑤
最好情况就是全部有序,此时只需遍历一次,最好的时间复杂度为O(n)
最坏情况全部反序,内层每次遍历已排序部分,最坏时间复杂度为O()
因此直接插入排序的平均时间复杂度为O()
这个很好想,也就是每次遍历空间复杂度都是O(1)
#include
//插入排序
void InsertSort(int arr[], int n)
{
int i = 1;
for (i = 1; i < n; i++)
{
int end = i - 1;
int tmp = arr[i];
while (end >= 0)
{
if (tmp < arr[end])
{
arr[end + 1] = arr[end];
end--;
}
else
{
break;
}
}
arr[end + 1] = tmp;
}
}
//打印数据
void print(int arr[], int n)
{
for (int i = 0; i < n; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int main()
{
int arr[10] = { 2,3,5,1,6,9,0,4,7,8 };
int sz = sizeof(arr) / sizeof(arr[0]);
printf("排序前:");
print(arr, sz);
InsertSort(arr, sz);
printf("排序后:");
print(arr, sz);
return 0;
}
⭕直接插入排序每次往前插入时,是按顺序依次往前查找,数据量较大时,必然比较耗时,效率低。
⭕改进思路: 在往前找合适的插入位置时采用二分查找的方式,即折半插入。
二分插入排序相对直接插入排序而言:平均性能更快,时间复杂度降至O(NlogN),排序是稳定的,但排序的比较次数与初始序列无关,相比直接插入排序,在速度上有一定提升。逻辑步骤:
① 从第一个元素开始,该元素可以认为已经被排序
② 取出下一个元素,在已经排序的元素序列中二分查找到第一个比它大的数的位置
③将新元素插入到该位置后
④ 重复上述两步
// 插入排序改进:二分插入排序
void BinaryInsertSort(int arr[], int len)
{
int key, left, right, middle;
for (int i=1; ikey)
right = middle-1;
else
left = middle+1;
}
for(int j=i-1; j>=left; j--)
{
a[j+1] = a[j];
}
a[left] = key;
}
}
⭕插入排序对几乎已排好序的数据操作时,效率很高,可以达到线性排序的效率。
⭕插入排序在每次往前插入时只能将数据移动一位,效率比较低。
先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。
改进思路二的方法实际上就是希尔排序。在这里只给出思路,在后续希尔排序中再做具体讲解。