[toc]


快速排序

程序代码

package com.uplooking.bigdata.datastructure;

import java.util.Arrays;

public class QuickSort {

    public static void main(String[] args) {
        int[] arr = {8, -2, 3, 9, 0, 1, 7, 6};
        System.out.println("排序前:" + Arrays.toString(arr));
        quickSort(arr);
        System.out.println("排序后:" + Arrays.toString(arr));
    }

    public static void quickSort(int[] arr) {
        quickSort(arr, 0, arr.length - 1);
    }

    /**
     * 快速排序
     *  是一种分而治之的思想,以某一个基准元素为标准,将集合中比该基准元素小的都放到其左侧,反之放到其右侧。
     *  这样我们就能够找到该基准元素在该集合中所处的位置。
     *  同理,我们就能够找到每一个元素在集合中恰当的位置。
     *  有点类似于二分查找。
     *  一般这个基准元素就是第一个元素。
     *  设置两个指针,一个设置在开头,一个设置末尾,从末尾开始找,找到一个比基本元素小的便停下来,然后
     *  从左侧开始找,直到找到一个比基准元素大的元素,停下来,二者元素进行交换,直到两个指针碰头,本次循环结束
     *  指针指向的位置就是该基准元素在集合中应该所处的位置
     *  eg
     *      {8, -2, 3, 9, 0, 1, 7, 6}
     *  benchmark
     *  第一个bm=8
     *      end = length - 1 = 7
     *      start=0
     *      end--,我们发现6比8小,end指针停下来了,当前索引为j=7
     *      start++,直到元素为9的位置停下来,当前索引i=3
     *      将i和j所对应的元素进行交换
     *    {8, -2, 3, [6], 0, 1, 7, [9]}
     *                i             j
     * 循环继续
     *    end--,到索引为6的位置又停下来了
     *    start++,直到和end碰头都没有再找到比8大的元素,所以我们就能断定,碰头的这个位置就应该是8在该集合中应该在的位置
     *   交换8的索引和碰头的索引
     *   {8, -2, 3, 6, 0, 1, 7, 9}
     *                       i=j=6
     * 交换:{7, -2, 3, 6, 0, 1, 8, 9}
     * 同理,我们可以在8左侧重复上述操作,8的右侧也可以重复上述操作
     * 使用递归调用的方式来完成集合的排序
     * @param arr
     */
    public static void quickSort(int[] arr, int low, int high) {
        if(low > high) {
            return;
        }
        // 默认以[low, high]中的arr[low]作为基准值
        int index = arr[low];
        // 定义左指针
        int start = low;
        // 定义右指针
        int end = high;
        // 开始向基准值扫描,当start < end条件不满足时
        // 说明指针碰头,则需要将基准值与start进行交换
        // 即该基准值在整个元素集合中的位置已经确定
        // 其左边的值比基准值小,其右边的值比基准值大
        while (start < end) {
            // 按照前面算法的设计,先从右边开始扫描,直到找到比基准值小的数再停下来
            // (下面循环的意义就是,在start < end的前提下,如果end位置的值比index值大或等于,就继续往左边找)
            while (start < end && arr[end] >= index) {
                end--;
            }
            // 财从左边开始扫描,直到找到比基准值大的数再停下来
            // (下面循环的意义就是,在start < end的前提下,如果start位置的值比index值小或等于,就继续往右边找)
            while (start < end && arr[start] <= index) {
                start++;
            }
            if (start < end) {
                // 前面的循环结束后,如果start还是小于end
                // 那么就交换arr[start]和arr[end]的值
                swap(arr, start, end);
            }
        }
        // 上面的循环结束后,start指针和end指针碰头,交换arr[start]和基准值
        arr[low] = arr[start];
        arr[start] = index;
        // 交换后,arr[start]左边比它小,右边比它大,然后再以它为基准
        // 左边和右边进行相同的操作
        quickSort(arr, low, start - 1);
        quickSort(arr, start + 1, high);
    }

    /**
     * 位操作
     * 按位与(&)
     * 1&1=1
     * 1&0=0
     * 0&1=0
     * 0&0=0
     * 异或(^)
     * (值不同)就取真(1),否则为(0)
     * 1&1=0
     * 1&0=1
     * 0&1=1
     * 0&0=0
     * 非
     *
     * @param arr
     * @param i
     * @param j
     */
    private static void swap(int[] arr, int i, int j) {
        /*int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;*/
        arr[i] = arr[i] ^ arr[j];
        arr[j] = arr[i] ^ arr[j];
        arr[i] = arr[i] ^ arr[j];
    }

}

/*
交换a=3和b=5,不用第三方变量,效率最高
    方法一:
        a = a + b = 8
        b = a - b = (8 - 5) = 3
        a = a - b = (8 - 3) = 5
异或的方式
    a=3-->低8为0000 0011
    b=5-->低8为0000 0101

    a = a ^ b
         0000 0011
       ^ 0000 0101
       ---------------
         0000 0110 --->6
    b = a ^ b
         0000 0110
       ^ 0000 0101
       --------------
         0000 0011--->3
    a = a ^ b
         0000 0110
       ^ 0000 0011
      ---------------
         0000 0101--->5
*/

测试

执行结果如下:

排序前:[8, -2, 3, 9, 0, 1, 7, 6]
排序后:[-2, 0, 1, 3, 6, 7, 8, 9]

时间复杂度分析

1.在最快及平均情况下,时间复杂度为O(nlog2n)。
最坏情况就是每次挑中的中间值不是最大就是最小,其时间复杂度为O(n^2)。
说明:
平均的时间复杂度记住就好了。
但是最坏的情况确实是可以算出来的,
假设每次选的基准值都是最大的,那么对于end的操作,一开始就找到比index值小的数,
而对于start的操作,则要一直循环n-1次才能让循环停下来。

交换一次index值后,选择的基准值又是最大的,那么start的操作就是n-2次......

以此类推,直到排序完成,1 + 2 + ... + n - 1,所以数量级为n^2,这意味着,数据如果是倒序的,使用上面的实现,那么此时的时间复杂度就为O(n^2).

2.快速排序法不是稳定排序法。
可以考虑,当基准值index与arr[start]值进行交换时,假设arr[start - 1] == index时,那么可以看到,其确实是不稳定的。

3.快速排序法是平均运行时间最快的排序法。