快排原理讲解

源代码
public class MainDemo {
public static void main(String[] args) {
Integer a[] = {3, 2, 1, 4, 5, 6, 7, 1};
//递归调用
QuickSort(a, 0, a.length-1);
System.out.println(Arrays.toString(a));

}

private static void QuickSort(Integer[] a, int l, int r) {

if (l >= r) {
return;//l和r代表左右两边的的萝卜下标
}

int key = a[l];//挖出第一个萝卜备用
int left = l, right = r;//工人A和B分别站在最左边和最右边位置开始寻找需要交换位置的萝卜


while (left < right) {
while (right > left && a[right] >= key) {
//B往左走寻找比挖出来的第一个萝卜轻,且位置在A右边的萝卜
right--;
}
a[left] = a[right];
while (left < right && a[left] <= key) {
//A要往右寻找比一个萝卜重,且位置在B左边的萝卜
left++;
}
a[right] = a[left];
}

a[left] = key;

QuickSort(a, l, left);
QuickSort(a, left + 1, r);

}

}

原理讲解
款速排序是很经典的排序算法,递归最难理解的就是临界情况,大家可以自行对{1,2}、{2,1}、{2,2}等特殊情况进行模拟调用,理解了这些临界情况的处理方式,快排就很好懂了,如果认为笔者有讲述不清晰的地方,或者描述不清楚或有误的地方,请不吝指正,感激不尽。下面我分两步进行讲解说明。

第一步,理解核心思路
假设你有一堆横排的体重不一的萝卜,你想把它们按照重量从小到大进行排序。
用快排来完成的话:

准备工作:
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,直接结束。


————————————————
版权声明:本文为CSDN博主「TesuZer」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/TesuZer/article/details/80969006

原文地址:https://www.cnblogs.com/sx66/p/11838669.html