Java 快速排序算法

警示:老弟我对此算法还有待加强,如有问题欢迎各位大佬评论区交流指正,留下你对快排的优质解法。

本算法所用知识点:

Java二分查找算法icon-default.png?t=N7T8https://blog.csdn.net/ouhexie/article/details/140981942?spm=1001.2014.3001.5501Java递归算法icon-default.png?t=N7T8https://blog.csdn.net/ouhexie/article/details/140984060?spm=1001.2014.3001.5501

什么是快速排序?

快速排序(Quick Sort)是一种高效的排序算法,采用分治法(Divide and Conquer)策略来对数据进行排序。

快速排序的基本步骤:

  1. 选择支点(Pivot): 从待排序的数组中选择一个元素作为“支点”。支点的选择可以是数组的第一个元素、最后一个元素或随机选择。

  2. 分区(Partition): 将数组重新排列,使得所有小于支点的元素都位于支点的左侧,所有大于支点的元素都位于支点的右侧。这一步骤完成后,支点就处于其最终位置上。

  3. 递归排序: 对支点左侧和右侧的两个子数组分别应用步骤1和步骤2,直到整个数组有序。

源码案例:

import java.util.Arrays;
​
/**
 * @version 1.0
 * @ClassName QuickSort
 * @Description 快速排序
 * @Author NanJi
 * @Date 2024/8/19 - 15:29
 */
public class QuickSort {
    public static void main(String[] args) {
        Integer[] nums = {6, 1, 2, 7, 9, 3, 4, 5, 8};
        QuickSort.sort(nums);
        System.out.println(Arrays.toString(nums));
    }
​
    /**
     * 比较v元素是否小于w元素
     *
     * @param v 元素
     * @param w 元素
     * @return true: v元素小于w元素
     */
    private static boolean less(Comparable v, Comparable w) {
        return v.compareTo(w) < 0;
    }
​
    /**
     * 数组元素 i 和 j 交换位置
     *
     * @param a 数组
     * @param i 索引
     * @param j 索引
     */
    private static void exch(Comparable[] a, int i, int j) {
        Comparable t = a[i];
        a[i] = a[j];
        a[j] = t;
    }
​
    /**
     * 对数组内的元素进行排序
     *
     * @param a 数组
     */
    public static void sort(Comparable[] a) {
        int lo = 0;
        int hi = a.length - 1;
        sort(a, lo, hi);
    }
​
    /**
     * 对数组 a 中从索引 lo 到索引 hi 的元素进行排序
     *
     * @param a  数组
     * @param lo 索引
     * @param hi 索引
     */
    private static void sort(Comparable[] a, int lo, int hi) {
        // 安全性校验,避免越界
        if (hi <= lo) {
            return;
        }
        //需要对数组中 lo 索引到 hi 索引处的元素进行分组(左子组和右子组)
        int partition = partition(a, lo, hi);   //返回的是分组的分界值所在的索引,分界值位置变换后的索引
        //让左子组有序
        sort(a, lo, partition - 1);
        //让右子组有序
        sort(a, partition + 1, hi);
    }
​
    /**
     * 对数组 a 中从索引 lo 到索引 hi 之间的元素进行分组,并返回分组界限对应的索引
     *
     * @param a  数组
     * @param lo 索引
     * @param hi 索引
     * @return 分组界限对应的索引
     */
    public static int partition(Comparable[] a, int lo, int hi) {
        //确定分界值
        Comparable key = a[lo];
        //定义两个指针,分别指向待切分元素的最小索引处和最大索引处的下一个位置
        int left = lo;
        int right = hi + 1;
        //切分
        while (true) {
            //先从右往左扫描,移动 right 指针,找到一个比分界值小的元素,停止
            while (less(key, a[--right])) {
                //如果 right 指针移动到 lo 处,证明元素扫描完毕,结束循环
                if (right == lo) {
                    break;
                }
​
            }
            //再从左往右扫描,移动 left 指针,找到一个比分界值大的元素,停止
            while (less(a[++left], key)) {
                //如果 left 指针移动到 hi 处,证明元素扫描完毕,结束循环
                if (left == hi) {
                    break;
                }
            }
            //判断 left >= right,如果是,则证明元素扫描完毕,结束循环
            if (left >= right) {
                break;
            } else {
                exch(a, left, right);
            }
        }
        //交换分界值
        exch(a, lo, right);
        //返回right索引
        return right;
    }
}

代码详细解释:

1. less (Comparable v, Comparable w)
/**
 * 比较v元素是否小于w元素
 *
 * @param v 元素
 * @param w 元素
 * @return true: v元素小于w元素
 */
private static boolean less(Comparable v, Comparable w) {
    return v.compareTo(w) < 0;
}

功能: 比较两个可比较对象 vw 的大小关系。

实现: 使用 compareTo 方法比较两个对象。如果 v 小于 w,则返回 true,否则返回 false

2. exch (Comparable [] a, int i, int j)
/**
 * 数组元素 i 和 j 交换位置
 *
 * @param a 数组
 * @param i 索引
 * @param j 索引
 */
private static void exch(Comparable[] a, int i, int j) {
    Comparable t = a[i];
    a[i] = a[j];
    a[j] = t;
}

功能: 交换数组 a 中索引 ij 的两个元素。

实现: 使用一个临时变量 temp 存储 a[i],然后将 a[j] 赋值给 a[i],最后将 temp 赋值给 a[j]

3. sort (Comparable [] a)
/**
 * 对数组内的元素进行排序
 *
 * @param a 数组
 */
public static void sort(Comparable[] a) {
    int lo = 0;
    int hi = a.length - 1;
    sort(a, lo, hi);
}

功能: 公开的排序方法,接受一个数组 a

实现: 调用私有的 sort 方法,传递整个数组的范围 0a.length - 1

4. sort (Comparable [] a, int lo, int hi)
/**
 * 对数组 a 中从索引 lo 到索引 hi 的元素进行排序
 *
 * @param a  数组
 * @param lo 索引
 * @param hi 索引
 */
private static void sort(Comparable[] a, int lo, int hi) {
    // 安全性校验,避免越界
    if (hi <= lo) {
        return;
    }
    //需要对数组中 lo 索引到 hi 索引处的元素进行分组(左子组和右子组)
    int partition = partition(a, lo, hi);   //返回的是分组的分界值所在的索引,分界值位置变换后的索引
    //让左子组有序
    sort(a, lo, partition - 1);
    //让右子组有序
    sort(a, partition + 1, hi);
}

功能: 对数组 a 从索引 lohi 的部分进行排序。

实现:

  • 基线条件: 如果 hi 小于或等于 lo,则返回(即子数组的长度为零或负数时无需排序)。

  • 分区操作: 调用 partition 方法获取分界点 j

  • 递归排序: 对分界点左侧和右侧的子数组进行递归排序。

5. partition (Comparable [] a, int lo, int hi)
/**
 * 对数组 a 中从索引 lo 到索引 hi 之间的元素进行分组,并返回分组界限对应的索引
 *
 * @param a  数组
 * @param lo 索引
 * @param hi 索引
 * @return 分组界限对应的索引
 */
public static int partition(Comparable[] a, int lo, int hi) {
    //确定分界值
    Comparable key = a[lo];
    //定义两个指针,分别指向待切分元素的最小索引处和最大索引处的下一个位置
    int left = lo;
    int right = hi + 1;
    //切分
    while (true) {
        //先从右往左扫描,移动 right 指针,找到一个比分界值小的元素,停止
        while (less(key, a[--right])) {
            //如果 right 指针移动到 lo 处,证明元素扫描完毕,结束循环
            if (right == lo) {
                break;
            }
​
        }
        //再从左往右扫描,移动 left 指针,找到一个比分界值大的元素,停止
        while (less(a[++left], key)) {
            //如果 left 指针移动到 hi 处,证明元素扫描完毕,结束循环
            if (left == hi) {
                break;
            }
        }
        //判断 left >= right,如果是,则证明元素扫描完毕,结束循环
        if (left >= right) {
            break;
        } else {
            //交换 left 和 right 元素
            exch(a, left, right);
        }
    }
    //交换分界值
    exch(a, lo, right);
    //返回right索引
    return right;
}

功能: 将数组 a 从索引 lohi 进行分区,并返回分界点的索引。

实现:

  • 选择支点: 选取当前部分的第一个元素 a[lo] 作为支点。

  • 初始化指针: left 指向 lo + 1right 指向 hi

  • 扫描与交换:

    • 向右移动 left 指针,直到找到第一个大于支点的元素。

    • 向左移动 right 指针,直到找到第一个小于支点的元素。

    • 如果 left 指针小于或等于 right 指针,交换 a[left]a[right],并移动指针。

  • 结束条件: 当 left 指针大于 right 指针时,停止扫描。

  • 交换支点: 将支点 a[lo] 交换到正确的位置,即 right 索引。

  • 返回分界点: 返回分界点的索引 right

运行过程举例

假设我们有一个数组 [6, 1, 2, 7, 9, 3, 4, 5, 8] 进行排序:

初次调用 sort:

  • 调用 sort(a, 0, 8),即处理整个数组。

  • partition 选择支点 6,并将数组分为 [1, 2, 3, 4, 5][9, 7, 8],支点 6 移到位置 4

递归处理左子数组 [1, 2, 3, 4, 5]:

  • 调用 sort(a, 0, 4)

  • partition 选择支点 1,支点 1 移到位置 0,分为空数组和 [2, 3, 4, 5]

  • 递归处理 [2, 3, 4, 5],重复以上步骤,最终排序为 [1, 2, 3, 4, 5]

递归处理右子数组 [9, 7, 8]:

  • 调用 sort(a, 5, 8)

  • partition 选择支点 9,支点 9 移到位置 7,分为 [7, 8] 和 空数组。

  • 递归处理 [7, 8],排序完成后得到 [7, 8]

最终结果:

  • 合并所有部分,得到排序后的数组 [1, 2, 3, 4, 5, 6, 7, 8, 9]

打印排序后的数组,结果为:

[1, 2, 3, 4, 5, 6, 7, 8, 9]

时间复杂度

  • 平均时间复杂度: O(n log n)

  • 最坏时间复杂度: O(n²)(当数组已经是有序的或逆序的,且每次选择的支点都是最小或最大元素时)

  • 最好时间复杂度: O(n log n)

空间复杂度

快速排序的空间复杂度为 O(log n),这主要是由于递归调用栈的空间消耗。

优缺点

优点:

  • 平均情况下性能优秀。

  • 不需要额外的存储空间(原地排序)。

缺点:

  • 在最坏情况下效率低下。

  • 不稳定排序(相同元素的相对位置可能会改变)。

欧了,到这里我应该解释的差不多啦,我是南极,大胆做自己,活出精彩的人生👊👊👊

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值