Lec 34 - Quicksort II
前几天把bearmaps搞定了。(其实还是有些不完美的地方不过应该不是我实现的算法的问题。给了一个完整的前端+后端框架,里面有点bug得慢慢研究)刷了刷题,感觉比一个月前要强不少,谢谢josh hug。
啥时候才能找个工作哎。
本章接上回,将具体的partition和avoid worst case操作。
Quicksort vs. Mergesort
复习一下,quicksort三个步骤:
- 设定一个pivot(这里是头部),然后以它为基Partition两部分
- quicksort左半部分
- quicksort右半部分
最好情况O(N logN),最坏情况O(N2),平均O(N logN)
几个avoid worst cases的方法:
- Randomness: 随机挑选pivot或者shuffle一下
- Smarter pivot selection: 每次选的都是median,那么就是最佳。所以如果计算出median,就能保证复杂度在O(N logN)
- Introspection: 如果recursion次数太多了,那就换一种sort方式
- Preprocess the array: 提前检查一下array的情况,判断quicksort的性能,很难。
1. Randomness
一般不这么做,因为随机数的产生是一件很麻烦的事情。
2. Smarter Pivot Selection(linear time pivot pick
目标是找到一个O(N)复杂度的找median的方法,这样每次Partition之前,找到median并将它设为pivot,加上partition,依然是O(N)复杂度。
3. Introspection
如果超过了某个递归深度,那么就切换到mergesort,不是太常用。
对Mergesort和应用3-scan partition algorithm,选用头部作为pivot,shuffle的quicksort进行1000个长度为10000的数组排序测试,结果显示quicksort性能并不比mergesort好,可以说是远远不如。(我一直觉得这个quicksort贼奇怪,人mergesort不优化直接就O(N logN),这还得一直优化。但就是quicksort快能有什么办法)
3-scan partition algorithm复杂度有点高,3N,看看能不能降一下。Tony Hoare本人就提出了一个好方法:
- 首先建两个指向两端的指针L和G。
- L++,G–。如果L碰见大于等于pivot的数,就停下;如果G碰见小于等于pivot的数,就停下
- 交换array[L]和array[G]
- 继续L++, G–。重复步骤2和3.
- 如果G < L,结束循环。G就是pivot的位置。
- 交换pivot和G
再做一次实验,就比mergesort快了。
目前选择pivot的方式还是头部,worst case avoidance strategy是suffle,那么现在试一下选择median作为pivot的方法?
这里介绍了一种叫PICK的方法,可以以线性的时间复杂度选择数组中第i小的数。
然而用了PICK方法的quicksort,性能反而是最糟糕的。说明计算median方法还是消耗太大了。
Quick Select
其实partition在选择median里仍然有应用。(蜜汁性能优越的partition)下面介绍的就是目前性能最优的median finding algorithm。
讲起来不难,仍然是对头部进行partition操作,发现哪部分元素比较多,就在该部分继续partiton,直到pivot落在了中间位置。
平均下来,Quick Select复杂度为线性。
然而应用该方法找median后,quicksort性能还是不行。。。
Stability
Sort的stability定义是:如果初始array的顺序被记录(sort之后被保存了下来),那就说这种方法是stable的。
更多的优化方式包括:
- 当subproblem的数组长度不足15时,换成insertion sort
- 总之是让sort跟adaptive,应用不同方法的不同优势