插入排序
回想下我们在打扑克时如何对扑克进行排序的,通常我们会从左往右看,每次只考虑一张牌,把它插入合适的位置。
举个例子:
4,2,5,1,3
利用上面扑克牌排序的方法进行排序。
4:4 就一个数字,认定为有序了。
2:2比4小,把2插入到4前面,变成了2,4
5:5会和它之前的4比较下,发现5比4大,无需动了,所以就是2,4,5
1:1和它之前的5比较,1小于5,再和4比较,1比4小,再和2比较,1比2小,到头了,1比所有的数都小,所以1插到最前面变成1,2,4,5
3:类似于1的方法,最终的排序结果为1,2,3,4,5
计算机编程实现这样一个方法时,唯一的区别就是我们要为插入的那个数字腾出空间,如何腾空间?就是该数之前,比它大的数字逐个往后挪。
上例中对1进行插入时会,2,4,5往后挪动变成
_,2,4,5,3
1最后放在下划线那个位置。
我们对所有目标数据一个个进行比较,插入后,整个数据就会变得有序,这样的排序方法就成为插入排序。
插入排序的动态演示图:
下面是代码实现
void insert_sort(int nums[], int len){
for (int i = 1; i < len; ++i){
int j = i;
int target_value = nums[i];
while(j>0 && target_value < nums[j-1]){
nums[j] = nums[j-1];
j-=1;
}
nums[j] = target_value;
}
}
不难看出我们只用了一个变量来缓存当前插入的数字,所以
空间复杂度为O(1)
考虑这样一个序列:5,4,3,2,1。我们要对它进行从小到大排序,不难发现,我们在对每次数字进行插入时,需要和它之前的所有数字进行比较,这样的序列对插入排序来说,时间复杂度是最高的,O(N2/2)
而对于随机循序的,时间复杂度大概是O(N2/4)
对于已经有序的序列:1,2,3,4,5。我们再对它进行从小到达排序时,代码中的5-8是不需要执行的。时间复杂度也就是O(N)
观察这样一个序列
2,1,4,3,6,5
如果用插入排序对它进行排序时候,需要3次插入操作,相比最开始的那个例子会发现时间复杂度明显少狠多。这样的序列有个明显的特征就是:部分有序(partially sorted)。红色数字按序排列,黑色数字按序排列
插入排序对部分有序的数据处理时,时间复杂度会小很多。
希尔排序
希尔排序就是利用插入排序对部分有序表现很好这样一个特点。具体做法是:
1.每隔h步,对数据进行插入排序。
2.不断缩小h,重复1操作,直至h为1
h初始值根据数据量大小来定
举例
4,2,5,1,3
h取2,对4,5,3进行插入排序变成3,4,5,整个序列为3,2,4,1,5。同样对2,1进行插入排序变成1,2,整个序列为3,1,4,2,5
h取1,相当于插入排序,此时的序列是部分有序了,所以耗时会很小。
希尔排序的动态演示图:
实现代码为
void insert_sort(int nums[], int len, int h){
for (int i = h; i < len; i+=h){
int j = i;
int target_value = nums[i];
while(j-h>=0 && target_value < nums[j-h]){
nums[j] = nums[j-h];
j-=h;
}
nums[j] = target_value;
}
}
void shell_sort(int nums[], int len){
int h = len;
int gap = 3;
h /= gap;
while(h >= 1){
insert_sort(nums, len, h);
h = h/gap;
}
}
希尔排序相当于把所有元素分成几个部分来排序,先一个个地把几个小部分排好,让所有元素有个大概的顺序,最后整体上用插入排序。希尔排序的时间复杂度为O(N3/2)
参考:https://algs4.cs.princeton.edu/21elementary/