前言
学习自用,排序算法再回顾,这里就不放代码了
正文
冒泡排序
冒泡是从前往后相邻比较,每一次比较会把当前最大值传到后面,传到最后面以后就不用管最后面的了
因为他是只比较相邻的而且是从前往后的,所以适合用在链表的排序,(因为单向链表从前往后走,且相邻之间很容易用一个next指针比较)
从前往后这个特性应该代码的局部性也比较好吧?
时间复杂度是O(n^2),看代码可以说是两个循环所以是O(n^2),具体推导TODO
可以看出冒泡排序除了相邻比较以外,还会用到很多次相邻的交换
没有用到任何额外的空间,同时也很容易看出来,可以采用动态规划的思想来优化这个冒泡排序吗TODO
冒泡稳定,因为每次往后走如果碰到相同的是不会移动的
优化:当一次内层循环发现没有元素要交换的时候说明已经有序,提前结束外层循环
优化:拆半插入TODO
选择排序
每一轮找出一个最小值,和最左边位置进行交换,并不断调整最左边界
可以看出和冒泡排序一个区别是它不需要每次都交换,而是都遍历一遍来找到一个最值,相比起冒泡省去了每次遍历的很多次交换
但是时间复杂度还是O(n^2),来自于它的比较次数,不过因为省去的这些swap用时会比冒泡排序更优
空间会用到每次的一个变量来存储最值,还是属于O(1)的复杂度
代码也是很简略,两个for循环
以数组实现的不稳定,基于链表的选择排序是稳定的,网上有人说链表的选择排序反而像插入排序,这个没实现过TODO
插入排序
这个代码不是很直观,但是平时打牌我们都是用这个来排序的,一句话概括是把未排序的插到排好序的序列里
它的代码也是不难,内层的while第一眼可能不太好理解,
它也像冒泡排序一样会涉及到相邻移动的情况,最好情况是不用移动,可以增加代码判断从而结束外层循环,最差情况TODO
新来的可以插在旧的右边,所以不打乱原本相对顺序,插入排序也是稳定的
优化:折半插入TODO
可以看出,插入排序和选择排序一样都是O(n^2)的复杂度,,但是插入排序的比较次数会更少,因为选择排序每遍都要走完未排序数据,所以从这可知道插入排序如果对于基本有序的数组的效率是最高的
插入排序的数据需要比数据多1个位置,用来留空插入,空间复杂度也是O(1)
归并排序
要先知道一个外排的概念,即在O(n)的时间复杂度中把两个有序数据合并,而这样需要用到额外的空间,所以整体的归并排序也是O(n)的空间复杂度
另外很容易知道外排是稳定的,所以总体归并排序也是稳定的
归并的代码分为两部分,归和并,利用递归来实现,选取left->right区间,取mid=两者中间,对两个区间left->mid和mid+1->right分别进行递归,在递归后可看成两个区间数组已经有序,就到了并的部分,把两个区间进行外排,最终放回nums
代码要注意左右区间是如何选择,左闭右开是更为常用的方式
归并总体来说是个分治的思想,可以说是分治来完成的,利用归并排序可以解决一道力扣题叫做最大和,就是利用到了这个归并中并的过程
时间复杂度需要复杂的推导,是nlogn的复杂度,详细推导TODO
有自底向上的优化?
快速排序
美名其曰快速排序.有多种实现方法,还有许多种优化方法,那么多内容能单独开一篇文章来讲快速排序,这里略过
堆排序
首先堆的性质是根部是最值,则每次取个最值放在数组最后面,一个位置,不就是一个选择排序的思想吗,所以也是一个不稳定的
堆排序分为两部分,第一部分是建堆,第二部分是每次取最值后的堆调整,每次取最值调整次数是log(当前树高)
第一部分的建堆过程是从下往上的,就像是每次从下往上,即从枝往根冒泡的过程,冒泡的过程即每次选一个最值冒泡到最上面,但是这个冒泡的路径长度是树高
第二部分这个操作叫做堆化heapify,这个得深究下才好理解TODO
非比较类排序
O(n)的排序算法,有桶排序,计数排序,基数排序
希尔排序
不是很懂啊
进阶
选择排序和插入排序的对比:
主要有两个开销,一个是比较的开销,一个是数据交换swap的开销
平均情况下插入排序需要n*(n-1)/4次比较,选择排序固定需要n*(n-1)/2次比较,论比较是插入排序更快,
而插入排序需要元素移动,移动次数是?选择排序则需要元素的互相交换swap,单独的一次移动和一次元素交换明显是移动的开销更小,如果没优化的情况下(我也不知道能怎么优化),一次元素的交换可能相当于三次元素的移动吧,
内省排序
通过这个内省排序可以加深对这些算法的理解,这里先TODO