快排原理讲解
第一步,理解算法核心
假设你有一堆横排的体重不一的萝卜,你想把它们按照重量从小到大进行排序。
快排思路
准备工作:
0、如果最左边萝卜的下标>=最右右边萝卜下标,结束。(这就是我们的递归出口)
1、把第一个萝卜挖出来备用。
2、找两个工人一个叫A一个叫B,分别站在第一个萝卜(注意第一个萝卜的坑已经空了)和最后一个萝卜的位置。
3、如果A和B相遇了,则执行6,否则继续执行4和5
4、B往左走寻找比挖出来的第一个萝卜轻,且位置在A右边的萝卜,找到了就挖出来放到左所在的空坑里(这时B守着空坑)
5、由于B把A的坑用掉了,所以A要往右寻找比一个萝卜重,且位置在B左边的萝卜,找到了就挖出来放到B守着的空坑里(这时A又守空坑了),执行3
6、这时候两人已经相遇了,我们曾经把第一个萝卜挖出来,所以这时候必定有一个位置是空着的,这个位置也必然是他们相遇的这个位置,为什么呢?因为A和B相遇有两种情况,A往右走,与遇到B和B往左走遇到A,A和B的工作其实就是找到合适的萝卜去填对方的坑,当有一方在走,另一方所在位置必然是空的,所以A遇到B时候B是空的,B遇到A的时候A时是空的,所以最后的空坑肯定是他们相遇的位置。这时候只要把我们挖出的第一个萝卜放入这个空坑,就可以达到一个效果那就是相遇位置放上第一个萝卜之后,它左边的萝卜质量都小于它,它右边位置的萝卜质量都大于它。这时候就执行步骤7。
7、把所有萝卜分成两堆,[第一个萝卜,相遇位置]为第一堆,[相遇位置+1,第最后一个萝卜]为第二堆。对这两堆萝卜从步骤0开始执同样的操作。
注意
这里所谓的挖出萝卜产生空坑是逻辑上的不是物理上的,我们执行a=b之后,逻辑意义是将b存入了a,但是b中的值还是存在的。同理我们把A位置萝卜存入B位置之后A位置的值还是存在的,但是已经没有意义了,可以被别的值覆盖掉,也就是说我在第二步中的X的含义不是这里没有值了,而是这里的值已经拷贝到另一个位置,而这个位置可以被别的值直接覆盖。
第二步,数据模拟
注意:加红的表示A和B当前所在位置
初始萝卜:
5 6 7 3 4
0、左边位置0,右边4,继续执行
1、挖掉5,变成 X 6 7 3 4
2、A站在0,B站在4
3、A和B没有相遇,执行4、5
4、B从所在位置开始往左找发现4比挖掉的5小,且位置在A右边,所以把4放到A的位置,变成4 6 7 3 X
5、A从所在位置往右找,找比第一个萝卜大的萝卜,找到6,将6放到B所在坑位,变成4 X 7 3 6
3、A和B没有相遇继续4、5
4、B从所在位置开始往左找发现3比挖掉的5小,且位置在A右边,所以把3放到A的位置,变成4 3 7 X 6
5、A从所在位置往右找,找到7,将6放到B所在坑位,变成4 3 X 7 6
3、A和B没有相遇,执行4、5
4、B从所在位置开始往左找,发现A的位置和它相等了,而且是个空坑,所以B虚空将该位置并不存在的萝卜放在该位置,这步属于累赘执行。变成4 3 X 7 6
5、A从所在位置开始往右找,发现B的位置和它祥光了,而且是个空坑,所以A也虚空将该位置并不存在的萝卜放在该位置,这步属于累赘执行。变成4 3 X 7 6
3、A已经遇到B执行6
6、将第一次挖出的5放到相遇位置,变成了4 3 5 7 6
7、分成两堆,一堆[0,2],另一堆[3,4]
8、对[0, 2]这堆的萝卜进行步骤0开始的一系列操作,对[3, 4]这堆的萝卜进行步骤0开始的一系列操作。
伪代码
QuickSort(萝卜数组a, 左边位置, 右边位置) {
if (左边位置 >= 右边位置) {
return;//步骤0
}
key = 第一个萝卜重量;//步骤1
将左右两边位置分别存入A、B之中;//步骤2
while (A位置 < B位置) {//步骤3
while (B位置 > A位置 && B位置萝卜质量 >= key)
right–;//步骤4.1
将B位置萝卜放入A所在空位;//步骤4.2
while (left < right && a[left] <= key)
left++;//步骤5.1
a[right] = a[left];//步骤5.2
}
a[left] = key;//步骤6
QuickSort(萝卜数组a, 左边位置, A位置);//步骤7.1
QuickSort(萝卜数组a, A位置+1, 右边位置);//步骤7.2
}
难点
临界情况:
5 3
这组数据的运行情况是这样的:
记录5
B找到3比5小移到A位置变成3 X,
然后A右移遇到B,将B位置数放入A位置(其实就是同意位置放入同一位置)。
A和B已经相遇,跳出while循环,将5放入当前空坑变成3 5。
对[0,1],[2,1]对两段进行递归调用。
第一段:
[0,1],数据为3 5
这组数据运行情况是这样的:
记录3
B找到3位置遇到A,将B位置数放入A位置(同一位置放入同一位置)。
循环结束,分成两堆[0,0]和[1,1],并继续进行递归调用,满足递归出口条件l >= r直接结束。
第二段:
[2,1],这段满足递归出口条件l >= r,直接结束。
源代码
def quick_sort(L,start,end):
a=start
b=end
mid=L[a]
if a>=b:
return
while a<b:
while a<b and L[b]>mid:
b-=1
L[a]=L[b]
while a <b and L[a]<mid:
a+=1
L[b]=L[a]
L[a]=mid
quick_sort(L,start,a)
quick_sort(L,a+1,end)
return l
l = [5,3,1,6,9,2,11]
quick_sort(l,0,len(l)-1)
算法分析
最佳情况:T(n) = O(nlogn) 最差情况:T(n) = O(n2) 平均情况:T(n) = O(nlogn)
空间复杂度O(nlogn),属于内部排序,不稳定