排序-快速排序

快速排序介绍

  • 它的基本思想是: 选择一个基准数,通过一趟排序将要排序的数据分割成独立的两部分;

  • 其中一部分的所有数据都比另外一部分的所有数据都要小。然后,再按此方法对这两部分数据分别进行快速排序,

  • 整个排序过程可以递归进行,以此达到整个数据变成有序序列。

  • 注意:开始一定要从基准数的对面开始检索,要不然i==j时会把比基准数大的值放在了基准数位置上

  • (会把数组中的一个数当做基准数,一般会把数组中最左边的树当做基准数。然后从两边进行检索。先从右边检索比基准数小的。再从左边检索比基准数大的。如果检索到了,就停下,然后交换这两个元素。然后再继续检索。左右检索相遇,就停止检索,把基准数和相遇位置的元素交换。)

快速排序时间复杂度和稳定性

快速排序稳定性
  • 快速排序是不稳定的算法,它不满足稳定算法的定义。
  • 算法稳定性 – 假设在数列中存在a[i]=a[j],若在排序之前,a[i]在a[j]前面;并且排序之后,a[i]仍然在a[j]前面。则这个排序算法是稳定的!
快速排序时间复杂度
  • 快速排序的时间复杂度在最坏情况下是O(N2),平均的时间复杂度是O(N*lgN)。

  • 这句话很好理解: 假设被排序的数列中有N个数。遍历一次的时间复杂度是O(N),需要遍历多少次呢? 至少lg(N+1)次,最多N次。

  • 为什么最少是lg(N+1)次? 快速排序是采用的分治法进行遍历的,我们将它看作一棵二叉树,它需要遍历的次数就是二叉树的深度,

  • 而根据完全二叉树的定义,它的深度至少是lg(N+1)。因此,快速排序的遍历次数最少是lg(N+1)次。

  • 为什么最多是N次? 这个应该非常简单,还是将快速排序看作一棵二叉树,它的深度最大是N。因此,快读排序的遍历次数最多是N次。

  • 问题一:
    如果已是有序或基本有序,为何用快速排序会慢很多?
    因为基本有序作为一个二叉树,最大深度是N;每次分块又是N, 就是NN=N2
    无序可以作为完全二叉树,至少lg(N+1),每分块又是N,就是 N
    lgN=lg(N+1)

public class QuickSort {

    public static void main(String[] args) {

        int[] a = {40, 30, 10, 60, 50, 20};

        System.out.printf("quickSortLeft-排序之前:%s", Arrays.toString(a));
        System.out.println();

        quickSortLeft(a, 0, a.length - 1);

        System.out.printf("quickSortLeft-排序之后:%s", Arrays.toString(a));
        System.out.println();


        // 测试方式不准确
        int[] arr = new int[10000];
        Random random = new Random();
        for (int i = 0; i < arr.length; i++) {
            arr[i] = random.nextInt();
        }

        int[] arr1 = Arrays.stream(arr).toArray();
        int[] arr2 = Arrays.stream(arr).toArray();

        test(arr1);
        test3(arr2);


//        test1();
//        test2();


    }

    // 测试随机顺序  耗时
    private static void test(int[] arr) {
        long start = System.currentTimeMillis();
        quickSortLeft(arr, 0, arr.length - 1);
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }

    // 测试从低到高  顺序时耗时
    private static void test1() {
        int[] arr = new int[10000];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = i;
        }

        long start = System.currentTimeMillis();
        quickSortLeft(arr, 0, arr.length - 1);
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }


    // 测试从高到低  顺序时耗时
    private static void test2() {
        int[] arr = new int[10000];
        for (int i = arr.length-1; i >=0; i--) {
            arr[i] = i;
        }

        long start = System.currentTimeMillis();
        quickSortLeft(arr, 0, arr.length - 1);
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }

    // 测试从高到低  顺序时耗时
    private static void test3(int[] arr) {
        long start = System.currentTimeMillis();
        quickSort(arr, 0, arr.length - 1);
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }


    /**
     * 【基准数从左】
     *
     * @param a
     * @param left
     * @param right
     */
    private static void quickSortLeft(int[] a, int left, int right) {
        // 左右相遇直接返回
        if (left > right) {
            return;
        }

        // 保存基准数
        int base = a[left];//------------------------------>和【基准数从右】就这换一下right
        int i = left;
        int j = right;

        while (i != j) {
            // 从右往左检索
            while (a[j] >= base && i < j) {//------------------------------>(1)  和【基准数从右】就(1)(2)这换一下顺序
                j--;
            }
            // 从左往右检索
            while (a[i] <= base && i < j) {//------------------------------>(2)
                i++;
            }
            // 代码走到这里,都暂停了交换
            int temp = a[i];
            a[i] = a[j];
            a[j] = temp;
        }
        // 如果i和j相遇,停止检索,交换基准数和相遇位置数
        a[left] = a[i];//------------------------------>和【基准数从右】就这换一下right    a[right] = a[i];
        a[i] = base;

        // 基准数归位,左边比他小,右边比他大
        quickSortLeft(a, left, i - 1);
        quickSortLeft(a, i + 1, right);

    }


    /**
     * 优化版本
     * @param arr
     * @param low
     * @param high
     */
    private static void quickSort(int[] arr, int low, int high) {

        if (low < high) {
            // 找寻基准数据的正确索引
            int index = getIndex(arr, low, high);

            // 进行迭代对index之前和之后的数组进行相同的操作使整个数组变成有序
            //quickSort(arr, 0, index - 1); 之前的版本,这种姿势有很大的性能问题
            quickSort(arr, low, index - 1);
            quickSort(arr, index + 1, high);
        }

    }

    private static int getIndex(int[] arr, int low, int high) {
        // 基准数据
        int base = arr[low];
        while (low < high) {
            // 当队尾的元素大于等于基准数据时,向前挪动high指针
            while (low < high && arr[high] >= base) {
                high--;
            }
            // 如果队尾元素小于base了,需要将其赋值给low
            arr[low] = arr[high];
            // 当队首元素小于等于base时,向前挪动low指针
            while (low < high && arr[low] <= base) {
                low++;
            }
            // 当队首元素大于base时,需要将其赋值给high
            arr[high] = arr[low];

        }
        // 跳出循环时low和high相等,此时的low或high就是base的正确索引位置
        // 由原理部分可以很清楚的知道low位置的值并不是base,所以需要将base赋值给arr[low]
        arr[low] = base;
        return low; // 返回base的正确位置
    }

}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值