探讨快速排序(Java实现)

快速排序(Java代码实现)

1. 思想

​ 快速排序是一种分治(Divide and conquer)递归算法。快速排序使用分治法把一个序列分割成两个子序列,其中一部分序列均比另一部分序列小,之后再递归地分别对这两部分序列继续进行排序。

2. 算法分析

​ 快速排序是实践中的一种快速的排序算法,在对C++和Java的基本类型的排序中特别有效。它的平均时间复杂度是O(n log n),虽然它最坏情况下的时间复杂度为 O(n²),但经过一些优化可以让这种情况很难出现。

排序算法平均时间复杂度最好时间复杂度最坏时间复杂度空间复杂度排序方式稳定性
快速排序O(n log n)O(n log n)O(n2)O(log n)内部排序不稳定

3. 实现步骤

  1. 从序列中选取一个元素作为 ”基准/枢轴“(pivot);
  2. 把序列中比基准值小的元素移动到基准左边,比基准值大的元素移动到基准的右边(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个操作称为分区/分割(partition);
  3. 递归地(recursive)把小于基准值的元素子序列和大于基准值的元素的子序列进行排序。

数据结构与算法分析(Java语言描述)书中的原话:
在这里插入图片描述

例子:
在这里插入图片描述

4. 具体步骤实现

通过前面的步骤分析我们知道第一步先选取元素作为基准,第二步再把大于基准的元素移到基准的左边,小于基准的元素移到基准的右边,那么我们在实际当中,在代码中应该怎么去实现这两步?

4.1 选取基准

怎么样选取基准?

  1. 一种错误的做法:选择固定位置作为基准

    在序列中选取第一个元素作为基准。
    在这里插入图片描述
    Java代码实现:

// 固定位置:选取数组的第一个元素作为基准
    public static int selectPivot(int array[], int low) {
        return array[low];
    }
  1. 一种安全的做法:随机选取基准

    在序列中随机选取一个元素作为基准。
    在这里插入图片描述
    Java代码实现:

//随机选取基准:选取序列中的随机一个元素作为基准
    public static int selectPivotRandom(int array[], int low, int high) {
        Random random = new Random();
        // nextInt(n)生成的随机数的范围不包括n,所以加1。
        int rd = random.nextInt(high - low + 1) + low;
        swap(array, high, rd);
        return array[high];
    }
  1. 一种较优的做法:三数中值法

    选取序列最左端、最右端和中间的元素的中值作为基准。
    在这里插入图片描述
    例子:

假设待排序列为:8,1, 4, 9, 6, 3, 5, 2, 7, 0,
它最最左端的元素为:8,最右端的元素为:0,中心位置 (left + right)/2 上的元素为:6。
这三个位置上的元素的中值为:6,所以选取6作为基准。

Java代码实现:

// 三数中值:选取序列中最左端,最右端,中间的元素的中值作为基准
   public static int medianOfThree(int[] array, int low, int high) {
       //序列中间的元素的下标
       int mid = low + ((high - low) >> 1);
       if (array[mid] > array[high]) {
           swap(array, mid, high);
       }
       if (array[low] > array[high]) {
           swap(array, low, high);
       }
       if (array[mid] > array[low]) {
           swap(array, mid, low);
       }
       //此时,array[mid] <= array[low] <= array[high]
       //low的位置上保存这三个位置中间的值
       //分割时可以直接使用low位置的元素作为枢轴,而不用改变分割函数
       return array[low];
   }

4.2 分区操作和递归

怎么将把序列中比基准值小的元素移动到基准左边,比基准值大的元素移动到基准的右边?

双向扫描:

从右往左扫描,第一个比基准值小的元素与基准值互换;

从右往左扫描,第一个比基准值大的元素与基准值互换。

第一个比基准值小的元素的位置开始从右往左扫描,扫描到第二个比基准值小的元素与基准值互换。

第一个比基准值小的元素的位置开始从右往左扫描,扫描到第二个比基准值大的元素与基准值互换。

······

第n个比基准值小的元素的位置开始从右往左扫描,扫描到第(n+1)个比基准值小的元素与基准值互换。

第n个比基准值小的元素的位置开始从右往左扫描,扫描到第(n+1)比基准值大的元素与基准值互换。

······

直到指针low = high,完成第一次分割操作。
最后再递归的重复以上步骤完成排序。

例子:
在这里插入图片描述

Java代码实现:

    // 快速排序
    public static void quickSort(int array[], int low, int high) {
        if (low < high) {
            //选取准基
            int pivot = medianOfThree(array, low, high);
            int i = low;
            int j = high;
            
            //分区
            while (i < j) {
                //从后往前(当元素大于基准时往前移动,直到元素小于基准)
                while ((i < j) && (array[j] > pivot)) {
                    j--;
                }
                swap(array, i, j);
                //从前往后(当元素小于基准时往前移,直到元素大于基准)
                while ((i < j) && (array[i] < pivot)) {
                    i++;
                }
                swap(array, i, j);
            }

            //递归
            if (i - 1 > low) {
                quickSort(array, low, j - 1);
            }
            if (j + 1 < high) {
                quickSort(array, j + 1, high);
            }
        }
    }

5.完整代码

public class QuickSort {
    public static void main(String[] args) {
        int[] array = {5, 3, 7, 6, 4, 1, 0, 2, 9, 10, 8};
        int low = 0;
        int high = array.length - 1;
        quickSort(array, low, high);
        System.out.println(Arrays.toString(array));
    }

    // 快速排序
    public static void quickSort(int array[], int low, int high) {
        if (low < high) {
            //选取准基
            int pivot = medianOfThree(array, low, high);
            int i = low;
            int j = high;
            
            //分区
            while (i < j) {
                //从后往前(当元素大于基准时往前移动,直到元素小于基准)
                while ((i < j) && (array[j] > pivot)) {
                    j--;
                }
                swap(array, i, j);
                //从前往后(当元素小于基准时往前移,直到元素大于基准)
                while ((i < j) && (array[i] < pivot)) {
                    i++;
                }
                swap(array, i, j);
            }
            
            //递归
            if (i - 1 > low) {
                quickSort(array, low, j - 1);
            }
            if (j + 1 < high) {
                quickSort(array, j + 1, high);
            }
        }
    }

    // 选择固定位置:选取数组的第一个元素作为基准。
    public static int selectPivot(int array[], int low) {
        return array[low];
    }

    // 随机选取基准:选取序列中的随机一个元素作为基准。
    public static int selectPivotRandom(int array[], int low, int high) {
        Random random = new Random();
        // nextInt(n)生成的随机数的范围不包括n,所以加1。
        int rd = random.nextInt(high - low + 1) + low;
        swap(array, high, rd);
        return array[high];
    }

    // 三数中值:选取序列中最左端,最右端,中间的元素的中值作为基准。
    public static int medianOfThree(int[] array, int low, int high) {
        //序列中间的元素的下标
        int mid = low + ((high - low) >> 1);
        if (array[mid] > array[high]) {
            swap(array, mid, high);
        }
        if (array[low] > array[high]) {
            swap(array, low, high);
        }
        if (array[mid] > array[low]) {
            swap(array, mid, low);
        }
        return array[low];
    }

    // swap
    public static void swap(int[] array, int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值