(SUB)快速排序时间、空间复杂度

  基本思想:

Quicksort是对归并排序算法的优化,继承了归并排序的优点,同样应用了分治思想。

优缺点:

排序算法的应用都需要结合具体环境来考虑,例如若给定序列部分有序,希尔算法最快

快速排序的”快“是建立在综合考虑的基础上,具体情况则不一定(排序效率取决增量序列)

快速排序也不是万能的,例如当给定序列规模很小时,选择排序/插入排序就要比快排好很多。

优:快,空间占用小

缺:不稳定

适用:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布,不要求稳定n较大,快速排序的平均时间最短;

时间avg时间min时间max空间avg稳定性

O(nlog(n))

O(nlogn)(每次划分都是对半分)

O(n²)(每次划分都是n-1和1两部分(元素基本有序且采用固定基准(边界)))

空间复杂度O( n )  
        
就地快速排序使用的空间是O(1),而真正消耗空间的就是递归调用,每次递归就要保持一些数据;
     最优的情况下空间复杂度为:O(logn)  ;每一次都平分数组的情况
     最差的情况下空间复杂度为:O( n )      ;退化为冒泡排序的情况
 
稳定性:不稳定

快速排序改进措施

  1. 小排序问题:划分为小序列,做直接插入排序,再采用快速排序(由《数据结构与算法分析》(Mark Allen Weiness所著)可知,当待排序列长度为5~20之间,此时使用插入排序能避免一些有害的退化情形)(代码示例中取10)
  2. 基准的选择:选择适合的切分元素(三数取中,固定基准,随机基准
  3. 对于大量重复元素:三向切分
  4. 尾递归优化:
    1. 原理:当编译器检测到一个函数调用是尾递归的时候,它就覆盖当前的活动记录而不是在栈中去创建一个新的。编译器可以做到这点,因为递归调用是当前活跃期内最后一条待执行的语句,于是当这个调用返回时栈帧中并没有其他事情可做,因此也就没有保存栈帧的必要了。通过覆盖当前的栈帧而不是在其之上重新添加一个,这样所使用的栈空间就大大缩减了,这使得实际的运行效率会变得更高。
  5. 处理切分元素值有重复的情况:左侧扫描最好遇到 >= 切分元素值 的元素时停下,右侧扫描则是遇到 <= 切分元素值的元素时停下。(尽管存在一些等值交换,但可以避免算法的运行时间变为n2级别
     
package main.Test;

import java.util.Random;
import java.util.Stack;

public class KuaiSuSort {
    /**
     * 三向切分-快速排序优化
     *
     * 插入排序 + 三取样切分 + Tukey's ninther + Bentley-McIlroy 三向切分
     */

    private static final int INSERTION_SORT_CUTOFF = 10;

    private static final int MEDIAN_OF_3_CUTOFF = 40;

//    非递归
    private static void sort(Comparable[] arr) {
        int start = 0;
        int end = arr.length-1;
        Stack<Integer> stack = new Stack<>();
        if(start < end)
        {
            stack.push(end);
            stack.push(start);
            while(!stack.isEmpty())
            {
                int l = stack.pop();
                int h = stack.pop();
                int index = partition(arr,l,h);
                if(l < index-1)
                {
                    stack.push(index-1);
                    stack.push(l);


                }
                if(h > index+1)
                {
                    stack.push(h);
                    stack.push(index+1);


                }
            }
        }
    }

    private static int partition(Comparable[] a, int start, int end)
    {
        Comparable point = a[start];
        while(start < end)
        {
            while(start < end && !less(a[end], point))
                end--;
            a[start] = a[end];
            while(start < end && !less(point, a[start]))
                start++;
            a[end] = a[start];
        }
        a[start] = point;
        return start;
    }

    public static void sort(Comparable[] arr, int type) {
        if (type == 0) {
            sort(arr);
        } else if (type == 1) {
            sort(arr, 0, arr.length - 1);
        }

    }

//    递归
    private static void sort(Comparable[] dst, int lo, int hi) {
        int arrLen = dst.length;

        // (1小排序问题)当子数组大小 <= 10 时,切换到插入排序
        if (arrLen <= INSERTION_SORT_CUTOFF) {
            insertionSort(dst, lo, hi);
            return;
        }

        // (基准的选择)当子数组大小 <= 40 时,使用三取样切分(median-of-3)选择切分元素median3
        else if (arrLen <= MEDIAN_OF_3_CUTOFF) {
            int median3 = median3(dst, lo, lo + arrLen / 2, hi);
            swap(dst, median3, lo);
        }

        // (2基准的选择)当子数组大小 > 40 时,使用 Tukey's ninther 方法选择切分元素ninther
        else {
            int ninther = getNinther(dst, lo, hi, arrLen);
            swap(dst, ninther, lo);
        }

        // (3对于大量重复元素) Bentley-McIlroy 3-way partitioning   https://blog.csdn.net/qq1175421841/article/details/50314427
        // 使数组 dst[lo...p-1] & dst[q+1...hi] == standard ; dst[p...i-1] < dst[lo] < dst[j+1...q]
        int i = lo, j = hi + 1;
        int p = lo, q = hi + 1;
        Comparable standard = dst[lo];
        while (true) {
            // 移动指针,使得 dst[p..i-1] < dst[lo] == standard,直到一个 >= standard 的元素a[i]
            while (less(dst[++i], standard))
                if (i == hi) break;
            // 移动指针,使得 dst[lo] == standard > dst[j+1...q],直到一个 <= standard 的元素a[j]
            while (less(standard, dst[--j]))
                if (j == lo) break;

            // 指针交叉时,刚好 dst[i] == standard 的情况下,交换以将 dst[i] 归位
            if (i == j && eq(dst[i], standard))
                swap(dst, ++p, i);
            // 排序完成,退出循环
            if (i >= j) break;

            // 交换 dst[i] & dst[j] 的值,使其归位
            swap(dst, i, j);
            // 如果 dst[i] == standard,交换 dst[p] & dst[i],使其归位
            if (eq(dst[i], standard)) swap(dst, ++p, i);
            // 如果 dst[j] == standard,交换 dst[q] & dst[i],使其归位
            if (eq(dst[j], standard)) swap(dst, --q, j);
        }


        // 在切分循环结束后,将和 standard 相等的元素交换到正确位置
        // 即使数组 dst[lo...j-1] < standard == dst[j...i] < dst[i+1...hi]
        i = j + 1;
        // 把 standard == dst[lo...p-1] 元素归位到 dst[j...i] 中
        for (int k = lo; k <= p; k++)
            swap(dst, k, j--);
        // 把 standard == dst[q+1...hi] 元素归位到 dst[j...i] 中
        for (int k = hi; k >= q; k--)
            swap(dst, k, i++);

        // 递归调用(4尾递归优化)
        sort(dst, lo, j);
        sort(dst, i, hi);
    }

    private static int getNinther(Comparable[] dst, int lo, int hi, int n) {
        int eps = n / 8;
        int mid = lo + n / 2;
        int m1 = median3(dst, lo, lo + eps, lo + eps + eps);
        int m2 = median3(dst, mid - eps, mid, mid + eps);
        int m3 = median3(dst, hi - eps - eps, hi - eps, hi);
        return median3(dst, m1, m2, m3);
    }

    private static void insertionSort(Comparable[] arr, int lo, int hi) {
        for (int indexI = lo; indexI <= hi; indexI++) {
            for (int indexJ = indexI; indexJ > lo && less(arr[indexJ], arr[indexJ - 1]); indexJ--) {
                swap(arr, indexJ, indexJ - 1);
            }
        }
    }

    /**
     * 比较两个元素的大小
     *
     * @param comparableA 待比较元素A
     * @param comparableB 待比较元素B
     * @return 若 A < B,返回 true,否则返回 false
     */
    private static boolean less(Comparable comparableA, Comparable comparableB) {
        return comparableA.compareTo(comparableB) < 0;
    }

    /**
     * 将两个元素交换位置
     *
     * @param arr    待交换元素所在的数组
     * @param indexI 第一个元素索引
     * @param indexJ 第二个元素索引
     */
    private static void swap(Comparable[] arr, int indexI, int indexJ) {
        Comparable temp = arr[indexI];
        arr[indexI] = arr[indexJ];
        arr[indexJ] = temp;
    }

    // 取 arr[i]  arr[j]   arr[k]  三个元素值的中间元素的下标
    private static int median3(Comparable[] arr, int i, int j, int k) {
        return (less(arr[i], arr[j]) ?
                (less(arr[j], arr[k]) ? j : less(arr[i], arr[k]) ? k : i) :
                (less(arr[k], arr[j]) ? j : less(arr[k], arr[i]) ? k : i));
    }

    // 判断两个元素是否相等
    private static boolean eq(Comparable v, Comparable w) {
        return v.compareTo(w) == 0;
    }
    /**
     * 打印数组的内容
     *
     * @param arr 待打印的数组
     */
    private static void show(Comparable[] arr) {
        for (int index = 0; index < arr.length; index++) {
            System.out.print(arr[index] + " ");
        }
        System.out.println();
    }

    /**
     * 判断数组是否有序
     *
     * @param arr 待判断数组
     * @return 若数组有序,返回 true,否则返回 false
     */
    public static boolean isSort(Comparable[] arr) {
        for (int index = 1; index < arr.length; index++) {
            if (less(arr[index], arr[index - 1])) {
                return false;
            }
        }
        return true;
    }

    // 打乱数组的方法
    private static void shuffle(Comparable[] arr) {
        int length = arr.length;
        Random random = new Random(System.currentTimeMillis());
        for (int index = 0; index < length; index++) {
            int temp = random.nextInt(length);
            swap(arr,index,temp);
        }
    }

    public static void main(String[] args) {
        Comparable[] a = {324,4,3,5, 324,2,7,123,3456,2345,234,7,89};
        shuffle(a);
        sort(a, 0);
        System.out.println(isSort(a));
        show(a);

    }
}


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值