快排原理讲解

源代码

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,直接结束。

你可能感兴趣的:(算法)