WANG LH , Research & Development

快速排序

2014.12.16 22:51

快速排序算法

author kingsea 2014.8.27 10:37am

基本思想

快速排序(Quicksort)是对冒泡排序的一种改进。由C. A. R. Hoare在1962年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

算法介绍

设要排序的数组是A[0]……A[N-1],首先任意选取一个数据(通常选用数组的第一个数)作为关键数据,然后将所有比它小的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一趟快速排序。值得注意的是,快速排序不是一种稳定的排序算法,也就是说,多个相同的值的相对位置也许会在算法结束时产生变动。
一趟快速排序的算法是:

  1. 设置两个变量i、j,排序开始的时候:i=0,j=N-1;
  2. 以第一个数组元素作为关键数据,赋值给key,即key=A[0];
  3. 从j开始向前搜索,即由后开始向前搜索(j--),找到第一个小于key的值A[j],将A[j]和A[i]互换;
  4. 从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的A[i],将A[i]和A[j]互换;
  5. 重复第3、4步,直到i=j; (3,4步中,没找到符合条件的值,即3中A[j]不小于key,4中A[i]不大于key的时候改变j、i的值,使得j=j-1,i=i+1,直至找到为止。找到符合条件的值,进行交换的时候i, j指针位置不变。另外,i==j这一过程一定正好是i+或j-完成的时候,此时令循环结束)。

排序演示

假设用户输入了如下数组:

下标 0 1 2 3 4 5
数据 6 2 7 3 8 9

创建变量i=0(指向第一个数据), j=5(指向最后一个数据), k=6(赋值为第一个数据的值)。
我们取走了下标0的数据,于是,我们需要找到一个数字来替换他。由于我们要把所有比6小的数移动到左面,所以我们可以开始寻找比6小的数并从右往左找。别急,我们要按顺序找哦。不断递减j的值,我们发现下标3的数据比6小,于是把3移到下标0(实际是i指向的位置。代码中要用i,因为后面还会循环这个步骤,不用i的话第二次循环:

下标 0 1 2 3 4 5
数据 3 2 7 6 8 9

i=0 j=3 k=6

由于变量k已经储存了下标0的数据,所以我们可以放心的把下标0覆盖了。如此一来,下标3虽然有数据,但是相当于没有了,因为数据已经复制到别的地方了。于是我们再找一个数据来替换他。这次要变成找比k大的了,而且要从前往后找了。递加变量i,发现下标2是第一个比k大的,于是用下标2的数据7替换j指向的下标3的数据,数据状态变成下表:

下标 0 1 2 3 4 5
数据 3 2 6 7 8 9

i=2 j=3 k=6

重复上面的步骤,递减变量j。这时,我们发现i和j“碰头”了:他们都指向了下标2。于是,循环结束,把k填回下标2里,即得到结果。
如果i和j没有碰头的话,就递加i找大的,还没有,就再递减j找小的,如此反复,不断循环。注意判断和寻找是同时进行的。

注意:快速排序不会直接得到最终结果,只会把比k大和比k小的数分到k的两边。(你可以想象一下i和j是两个机器人,数据就是大小不一的石头,先取走i前面的石头留出回旋的空间,然后他们轮流分别挑选比k大和比k小的石头扔给对面,最后在他们中间把取走的那块石头放回去,于是比这块石头大的全扔给了j那一边,小的全扔给了i那一边。只是这次运气好,扔完一次刚好排整齐。)为了得到最后结果,需要再次对下标2两边的数组分别执行此步骤,然后再分解数组,直到数组不能再分解为止(只有一个数据),才能得到正确结果。

上代码

    package com.sea.sort.demo;
    /**
     * quick sort
     * 
     * @author sea
     * 
     */
    public class QuickSort {

    public void sort(int arr[], int low, int high) {
        
        int i = low;
        int j = high;
        int key = arr[low];
        
        //end the loop when i == j
        while( i != j){
            //end the loop when i == j
            while (j > 0) {
                if (i == j) {
                    break;
                }
                //find the number which is lower than key from the right
                if (arr[j] < key) {
                    arr[i] = arr[j];
                    arr[j] = key;
                    break;
                }
                j--;
            }
            //end the loop when i == j
            while (i < arr.length) {
                if (i == j) {
                    break;
                }
                //find the number which is greater than key from the left
                if (arr[i] > key) {
                    arr[j] = arr[i];
                    arr[i] = key;
                    break;
                }
                i++;
            }
        }
        
        if (i > low) {
            sort(arr,low,i-1);
        }
        if (i < high) {
            sort(arr,i+1,high);
        }
    }
    

    public static void main(String[] args) {
        int arrLength = 100000;
        int[] arr = new int[arrLength];
        for (int i = 0; i < arrLength; i++) {
            arr[i] = (int) (Math.random() * arrLength);
        }
        System.out.println("The generated arr order is:");
        System.out.println(Arrays.toString(arr));

        QuickSort qs = new QuickSort();

        /**
         * Start sorting at: 1478188841345
         * Complete sorting at: 1478188841346
         */
        long startTime = new Date().getTime();
        System.out.println("Start sorting at:    " + startTime);
        qs.sort(arr, 0, arr.length - 1);
        long endTime = new Date().getTime();
        System.out.println("Complete sorting at: " + endTime);
        System.out.println("The total used time is: " + (endTime - startTime));

        System.out.println("After sorting, the new order is: ");
        System.out.println(Arrays.toString(arr));
    }

性能测试

单位是毫秒级

数量\排序 冒泡排序 快速排序 插入排序 选择排序
一万 403 7 45 237
十万 41475 65 3232 17101
一百万 412
一千万 3490