【算法】排序(五)快速排序

正文之前

快速排序(英语:Quicksort),又称划分交换排序(partition-exchange sort),一种排序算法,最早由东尼 * 霍尔提出。在平均状况下,排序n个项目要O(nlogn)次比较,在最坏情况下则需要O(n2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他O(nlogn)算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。              ——Wikipedia


本文将介绍以下内容

排序原理
算法实现(JAVA)
测试阶段
算法分析

正文

排序原理

和归并排序划清界限:

  • 归并排序:将一个数组分成两个字数组,分别排序两部分,将有序的子数组归并得到整个有序数组

  • 快速排序:将一个数组分成两个字数组,分别排序两部分,当两个子数组有序时,整个数组自然就有序了

快速排序使用一个切分元素来划分数组,切分元素左边的数组元素小于切分元素,右边的数组元素大于于切分元素,所以两个子数组有序就会得到整个有序的数组

算法实现

1. 切分数组
private static int partition(int[] a, int low, int high){
        int i = low, j = high + 1;              //由两边向中间扫描
        int p = a[low];                         //切分数组的元素
        while(true){
            while(a[++i] < p){                  // i 的值会一直递增至 a[++i] >= p
                if(i == high){                  //扫描结束
                    break;
                }
            }
            while(a[--j] > p){                  // j 的值会一直递减至 a[++i] >= p
                if(j == low){                   //扫描结束
                    break;
                }
            }

            if(i >= j){                         // j 的位置就是切分元素在有序数组中的位置
                break;
            }

            int temp = a[i];                    //交换元素顺序
            a[i] = a[j];
            a[j] = temp;
        }

        int temp = a[low];                      //将切分元素交换至中间位置
        a[low] = a[j];
        a[j] = temp;

        return j;
    }
2. 数组排序

这一部分的代码和归并排序的排序代码相似

private static void quickSort(int[] a){
        sort(a, 0, a.length - 1);
    }

    private static void sort(int[] a, int lo, int hi){
        if(hi <= lo){
            return ;
        }
        int j = partition(a, lo, hi);
        sort(a, lo, j - 1);
        sort(a, j + 1, hi);
    }

sort方法递归调用自己,将数组划分至最小,每次都将子序列中的第一个元素作为切分元素,来将数组排序

整个排序过程的代码:
public class QuickSort {

    private static int partition(int[] a, int low, int high){
        int i = low, j = high + 1;              //由两边向中间扫描
        int p = a[low];                         //切分数组的元素
        while(true){
            while(a[++i] < p){                  // i 的值会一直递增至 a[++i] >= p
                if(i == high){                  //扫描结束
                    break;
                }
            }
            while(a[--j] > p){                  // j 的值会一直递减至 a[++i] >= p
                if(j == low){                   //扫描结束
                    break;
                }
            }

            if(i >= j){                         // j 的位置就是切分元素在有序数组中的位置
                break;
            }

            int temp = a[i];                    //交换元素顺序
            a[i] = a[j];
            a[j] = temp;
        }

        int temp = a[low];                      //将切分元素交换至中间位置
        a[low] = a[j];
        a[j] = temp;

        return j;
    }

    private static void quickSort(int[] a){
        sort(a, 0, a.length - 1);
    }

    private static void sort(int[] a, int lo, int hi){
        if(hi <= lo){
            return ;
        }
        int j = partition(a, lo, hi);
        sort(a, lo, j - 1);
        sort(a, j + 1, hi);
    }
}

测试阶段

public static void main(String[] args){
        int[] a = new int[10];
        for (int i = 0; i < 10; i++) {
            a[i] = (int)(Math.random() * 100);
            System.out.print(a[i] + " ");
        }
        quickSort(a);
        System.out.println();
        for (int i = 0; i < 10; i++) {
            System.out.print(a[i] + " ");
        }
    }

生成10个随机数字并调用快速排序方法,最后输出结果:

【算法】排序(五)快速排序

【算法】排序(五)快速排序

【算法】排序(五)快速排序

算法分析

1. 特点

快速排序实现简单,应用广泛,比一般算法要快很多,并且是原地排序(借助辅助栈)

2. 时间复杂度
  1. 设数组的长度为N,用 T(N) 表示排序一个长度为N的数组所需要的比较次数,可想而知,T(0) 和 T(1) 为0

  2. 切分的成本为N + 1,左子数组排序的平均成本为(C1 + C2 + ... + CN - 2 + CN - 1)/N,右子数组排序的平均成本为(CN - 1 + CN - 2 + ... + C2 + CN)/N

  3. CN = N + 1 + (C1 + C2 + ... + CN - 2 + CN - 1)/N + (CN - 1 + CN - 2 + ... + C2 + CN)/N

  4. 经过化简之后得到:CN ~ 2(N + 1)(1/3 + 1/4 + ... + 1/(N + 1))

  5. 积分得到CN ~ 2NlogN

所以,算法复杂度为 O(nlogn)

3. 稳定性

举个例子,一个数组a = [6,4,3,2,3,7,8,7,9],a[0]和a[4]交换,第二个 3 就移动到了第一个 3 的前面,所以是不稳定的,不稳定性体现在切分元素交换的时候

关于快速排序的介绍就到这里了,谢谢!