排序算法作为算法的基础,我想大家都不陌生,甚至是张口就来的程度。什么插入排序,选择排序,冒泡排序,快速排序等等。但是真到了要你把代码写下来的时候,很多人都是懵逼的。
那么,废话不多说。现在就让我通过一系列的文章,带大家一次性的深入理解排序的原理,争取学习一次,终身受用。
插入排序
首先要跟大家介绍的就是插入排序。插入排序是最好理解的一种排序方式,大家可以想一下咱们平时摸扑克牌。手里的牌都是按照从小到大的顺序排列的,当你摸到一张新牌的时候,你是会把旧有的牌的对应位置挪开一个空间,把新牌按照顺序插入进去的。这其实就是插入排序的基本原理。
下面给出代码上的实现:
这里假设手里现在已经有了i=0的这张牌,i其实就是马上要抓回来的那张牌。
private static void insertionSort(int[] arr){
for(int i=1;i<arr.length;i++){
int c = arr[i];
int j = i;
for(;j>0 && arr[j-1] > c;j--){
arr[j] = arr[j-1];
}
arr[j] = c;
}
}
然后我们在main方法里进行调用
int[] arr = new int[]{5,7,3,1,2,7,6};
insertionSort(arr);
分步打印出来的结果为:
下面咱们分步进行拆解,标注成紫色的为已经抓到手里的牌。
此时手里的牌是数字5,下一张要摸的牌是7。由于5比7小, 不做交换。直接将7放到5后面。
此时手里的牌是5和7。下一张要摸的牌是3。3先跟7比较,由于3比7小,所以7往后挪一个位置。然后3再跟5比较,3比5小,5再往后挪一个位置。然后到头了,就把5空出来的那个位置留给3了。
此时手里的牌是3,5和7。下一张要摸的牌是1。1先跟7比较,由于1比7小,所以7往后挪一个位置。然后1再跟5比较,1比5小,5再往后挪一个位置。然后1再跟3比较,1比3小,3再往后挪一个位置。然后到头了,就把3空出来的那个位置留给1了。
此时手里的牌是1,3,5和7。下一张要摸的牌是2。2先跟7比较,由于2比7小,所以7往后挪一个位置。然后2再跟5比较,2比5小,5再往后挪一个位置。然后2再跟3比较,2比3小,3再往后挪一个位置。然后2再跟1比较,2比1大,就把3空出来的那个位置留给2了。
此时手里的牌是1,3,5和7。下一张要摸的牌是7。注意此时出现了重复的数字7,新摸到的7和手里的7进行比较,是相等的,不会触发牌的移动,所以新摸到的7直接放到手里牌堆的最后。这种相等大小的数字不触发挪动的情况,我们称之为稳定排序。
后续的结果如下,可自行推导。大概的操作就是,摸到一张牌,把手里按照顺序排好的牌依次往后挪,直到找到大小合适的位置,把摸到的牌插进去,这也就是插入排序的比较形象的表述。
插入排序的特点:
时间复杂度为O(n*n)
当数组刚好是完全顺序时,每次只用比较一次就能找到正确的位置。这个过程重复n次,就可以清空未排序区间。
插入排序最坏时间复杂度则需要O(n*n)。即当数组刚好是完全逆序时,每次都要比较n次才能找到正确位置。这个过程重复n次,就可以清空未排序区间,所以最坏时间复杂度为O(n*n)。
插入排序的平均时间复杂度是O(n*n)。这是因为往数组中插入一个元素的平均时间复杂度为O(n),而插入排序可以理解为重复n次的数组插入操作,所以平均时间复杂度为 O(n*n)。
空间复杂度为O(1)、原地排序:指针i 和 j使用常数大小的额外空间。
稳定排序:在插入操作过程中,我们会将元素插入到相等元素的右侧,不会改变它们的顺序。
结论:
尽管插入排序的时间复杂度更高,但在数据量较小的情况下,插入排序通常更快。实际上,Java的内置排序函数采用了插入排序,大致思路为:对于长数组,采用基于分治策略的排序算法,例如快速排序;对于短数组,直接使用插入排序。
关于不同排序算法的对比,我们放到这一系列的文章的最后进行讨论。
为了帮助大家面试,我整理了一份面试资料。总共几十页PDF,加我公众号 天鹭城,回复2,一次性送给大家。