插入排序详解——直接插入排序+希尔排序
@[TOC]
这篇文章我们来学习排序。
1. 排序的概念及其运用
1.1 排序的概念
排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。 稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。 内部排序:数据元素全部放在内存中的排序。 外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。
1.2 排序的应用
其实在我们生活中,很多地方都要用到排序。 比如:
1.3 常见的排序算法
接下来,我们就来讲解并实现一下常见的排序算法。
插入排序
2.1 直接插入排序
首先我们来学习直接插入排序:
算法思想
直接插入排序是一种简单的插入排序法,其基本思想是:
把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。
实际中我们玩扑克牌时,就用了插入排序的思想
举例(升序)
排序我们一般是对一个数组进行操作:
当插入第i(i>=1)个元素时,前面的array[0],array[1],…,array[i-1]已经排好序,此时用array[i]的排序码与array[i-1],array[i-2],…的排序码顺序进行比较,找到插入位置即将array[i]插入,原来位置上的元素顺序后移
一趟直接插入排序: 所以一趟直接插入排序就是这样的:
end指向原有序数据的最后一个,那要插入的数据和end指向的数据进行对比,如果比end指向的数据大,那直接放在end后面,如果小,则把end指向的大的数据向后移动,end- - ,与前面的元素进行比较,直到遇到比要插入的元素小的数据,然后把要插入的数据放在其后面。 那如果前面的元素都比要插入的数据大呢? 那就一直比,直到比完第一个元素,然后end- -之后变成-1,还是放到end位置的后面,即让它成为新的第一个元素。
代码实现
那我们先来写一下一趟直接插入排序的代码: 这是一趟的,那现在有一个数组,我们如何使用直接插入排序对其进行排序呢?
我们是不是可以先把数组的第一个元素看成是有序的,让end指向第一个元素,然后把第二个元素当作即将要插入的数据,这样一趟之后,前两个就有序了,然后我们再插入第三个,依次循环往复,当数组最后一个元素进行完插入,整个数组就有序了。
那其实在我们刚才写的一趟直接插入排序的基础上,外层加个循环控制end就行了。
//直接插入排序
void SInsertSort(int* arr, int len)
{
for (int i = 0; i < len - 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;
}
}
🆗,我们来测试一下: 这就是直接插入排序。
直接插入排序特性总结
元素集合越接近有序,直接插入排序算法的时间效率越高 当数据完全有序时,直接插入排序就是O(N),只需比较N次,不需要交换。
时间复杂度:O(N^2) 当数据完全逆序时,此时是最坏情况,时间复杂度是O(N^2)
空间复杂度:O(1),它是一种稳定的排序算法 不需要额外开辟新空间。
稳定性:稳定。 因为直接插入排序,如果有相同的数据,我们可以保证排序前后它们的相对位置不变。(只要能够做到这样我们就认为该排序是稳定的,无法做到的则是不稳定)
2.2 希尔排序( 缩小增量排序 )
接下来我们来学习希尔排序:
希尔排序其实是对直接插入排序的优化。
那希尔排序是如何对直接插入排序进行优化的,该算法的思想又是什么呢? 那如何进行预排序呢? 比如:
让gap等于1时,其实就是直接插入排序,但是在此之前已经进行了预排序,此时再进行直接插入排序就会比对原始数据直接进行直接插入排序快很多。
代码实现
那一趟预排序应该怎么实现呢?
其实很简单,我们说预排序是选定一个整数gap作为间隔,将要排序的数据进行分组,对分组后的数据进行直接插入排序。 那其实跟直接插入排序是一样的,只不过直接插入排序的gap是1罢了。 所以我们说当gap等于1时就是直接插入排序了。
所以,一趟预排序的实现,我们只需把直接插入排序中的1换成数据间隔gap就行了。 但是一趟过后还没完:
希尔排序的思想是我们选定一个gap之后,不断缩小gap,也就是说可能要进行多次预排序。 但是,要求gap最后一次取的值必须是1,因为预排序之后数据并不是已经有序了,而是相比原始数据更加接近有序了,所以最好还要进行一次直接插入排序(gap==1),这一趟过后,排序就完成了。
那gap的值要如何取呢?
常用的取法是:
gap的初值取数据个数的一半,然后每次缩小2,这样不管数据个数n是奇数还是偶数,最后一次gap正好为1。
gap初值取n的三分之一,但是要加个1,为什么要加个1呢? 因为每次除3的情况下,gap最后一次取值不一定是1。 比如对6个数据排序,n=6,6除3=2,2除3就是0了。 所以加个1,保证最后一次gap取1。
那现在我们只需外层再嵌套一个循环来控制gap的值就行了。
//希尔排序
void ShellSort(int* arr, int len)
{
int gap = len;
// gap > 1 预排序
// gap == 1 最后一次直接插入排序
while (gap > 1)
{
//gap /= 2;
gap = gap / 3 + 1;
for (int i = 0; i < len - gap; i++)
{
int end = i;
int tmp = arr[end + gap];
while (end >= 0)
{
if (tmp < arr[end])
{
arr[end + gap] = arr[end];
end -= gap;
}
else
{
break;
}
}
arr[end + gap] = tmp;
}
}
}
我们测试一下:
希尔排序特性总结
希尔排序是对直接插入排序的优化。
当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。
希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些书中给出的希尔排序的时间复杂度都不固定: 《数据结构(C语言版)》--- 严蔚敏 《数据结构-用面相对象方法与C++描述》--- 殷人昆 比
稳定性:不稳定 因为在预排序的过程中可能会把相同的数据分到不同组里,这样排完之后它们的相对顺序与原来相比就可能改变了。
- 点赞
- 收藏
- 关注作者
评论(0)