ywrby

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

原文链接

快速排序

快速排序即每次从数组中找出一个切分元素,然后将数组中大于它的元素移动到它的后面,将小于它的元素移动到它的前面。由此就确定了这个元素的位置,接下来只需要不断对它前后数组执行同样操作,最终便能确定每个元素的位置。

package cn.ywrby.sorts;

import cn.ywrby.tools.StopWatch;
import edu.princeton.cs.algs4.StdRandom;

//快速排序

public class QuickSort {

    /*
    * 切分方法
    * 定义i,j变量作为辅助的操作字符(用来索引数组)
    * 定义v作为切分元素,通过它将数组分为大于,小于两部分
    * 进入到无线循环内部
    * 第一个循环语句是从lo开始向后判断,元素是否小于切分元素
    * 其中的if语句表示当i索引到数组尾端时表示切分结束,跳出当前循环
    * 第二个循环语句从hi向前判断,元素是否大于切分元素
    * 同样的,其中的if语句判断是否已经索引到尽头
    * 通过这两个while,我们最终会得到一对(i,j)这对数满足a[i]>v,a[j]<v
    * 所以我们交换这两个数的位置
    * 直到i==j表示索引结束此时i及其左边均小于v,右边均大于v
    * 最后跳出循环,再将v与中间值交换得到目标数组
    * */
    private static int partition(Comparable[] a, int lo, int hi) {
        int i = lo, j = hi + 1;
        Comparable v = a[lo];
        while (true) {
            while (less(a[++i], v)) if (i == hi) break;
            while (less(v, a[--j])) if (j == lo) break;
            if (i >= j) break;
            exch(a, i, j);
        }
        exch(a, lo, j);
        return j;
    }

    //打乱数组顺序,防止数组原先顺序对排序的影响
    public static void sort(Comparable[] a) {
        StdRandom.shuffle(a);
        sort(a, 0, a.length - 1);
    }

    /*
    * 利用切分与递归实现快速排序
    * 第一个语句为递归终止语句,当hi<=lo时,说明排序完成
    * 在快速排序过程中,每次切分都能确定一个元素的位置,即a[j]元素
    * 后续两个递归操作只会对它的左边和右边数组进行切分,不会再影响它的位置
    * */

    private static void sort(Comparable[] 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);
    }

    private static boolean less(Comparable v, Comparable w) {
        return v.compareTo(w) < 0;
    }

    private static void exch(Comparable[] a, int i, int j) {
        Comparable temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }

    private static void show(Comparable[] a) {
        System.out.print("After sort : ");
        for (int i = 0; i < a.length; i++) {
            System.out.print(a[i] + "  ");
        }
        System.out.println();
    }

    public static boolean isSorted(Comparable[] a) {
        for (int i = 0; i < a.length - 1; i++) {
            if (!less(a[i], a[i + 1])) {
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        int N = 1000;
        Comparable<Double>[] test = new Comparable[N];
        System.out.print("before sort : ");
        for (int i = 0; i < N; i++) {
            double data = Math.random();
            System.out.print(data + "  ");
            test[i] = data;
        }
        System.out.println();
        StopWatch watch = new StopWatch();
        sort(test);
        double time = watch.elapsedTime();
        /*
         * assert关键字:assert [boolean 表达式]
         * 如果[boolean表达式]为true,则程序继续执行。
         * 如果为false,则程序抛出AssertionError,并终止执行。
         */
        assert isSorted(test);
        show(test);
        System.out.println("time=" + time);
    }
}

快速排序优点

  1. 原地归并(只需要很小的辅助栈)
  2. 内循环比大多数排序算法都要少
  3. 排序所需时间为NlgN

快速排序的缺点是极其脆弱,许多错误都会导致性能降低到平方级别

保证快速排序稳定的条件

  • 原地切分
  • 避免越界,严格控制越界条件
  • 保持随机性,除了代码中的将数组随机,还可以每次切分时,随机决定切分元素
  • 终止循环,最常见的错误是没有考虑数组中可能存在与切分元素相同的元素
  • 递归的终止条件严格控制

算法分析

最好情况下,运行情况是每次数组都能被对半分开,这种情况下快速排序的比较次数满足类似于归并排序的:

C_N=2C_{N/2}+N

通过归并排序的分析易知这种情况下算法的运行时间也是NlgN级别的

虽然我们不能保证每次切分后都使其落到中间位置,但最终结果是类似的

算法改进

在数组长度较小时(5~15)切换到插入排序

三取样切分:使用子数组的一小部分元素(3个)的中位数来切分数组,代价是需要计算中位数

三向切分的快速排序,在切分时将数组分成三段,大于,小于和等于。这种提高适用于重复元素很多的数组进行排序

posted on 2020-04-05 18:43  ywrby  阅读(125)  评论(0编辑  收藏  举报