【小白必会算法】快速排序算法

本文详细介绍了快速排序算法,包括其基本思想(分治法、基准元素选择和分区),递归实现,以及优化策略(如随机选择和三数取中法)。着重讨论了如何通过改进基准数选取来提升算法性能,尤其在处理大规模和特定序列时.

快速排序(Quick Sort)是一种高效的排序算法,是对冒泡排序的优化。它采用分治法(Divide and Conquer)的思想,将待排序序列不断分割成较小的子序列,然后对每个子序列进行排序,最后合并得到有序的序列。快速排序在大多数情况下具有优异的性能,是许多常见排序算法中最快的之一。

基本思想

快速排序动画演示

这里的动画用大佬五分钟学算法的图,很清晰

  1. 选择一个基准元素(pivot)作为参考点。
  2. 将所有比基准元素小的元素移到基准元素的左边,将所有比基准元素大的元素移到基准元素的右边。此过程称为分区(partitioning)。
  3. 对基准元素左右两边的子序列递归地进行相同的快速排序,直到子序列的大小为1或0,表示子序列已经有序。

如上图所示,快速排序的基本思想就是选取一个基准数,将基准数小的都放在左边,大的都放在右边,也就是每次循环都会确定出基准数应该在的正确位置。

代码实现

对应在代码层面,需要使用到递归法来实现,对于快速排序来说,递归相对还是很容易想到的

/**
 * @author HelloCode
 * @blog https://www.hellocode.top
 * @date 2023年08月08日 21:14
 */
public class QuickSort {
    public static void quickSort(int[] arr) {
        // 特殊情况处理
        if (arr == null || arr.length == 0 || arr.length == 1) {
            return;
        }
        // 递归入口
        sort(arr, 0, arr.length - 1);
    }

    private static void sort(int[] arr, int left, int right) {
        // 递归出口
        if (left > right) {
            return;
        }
        // 初始化双指针
        int i = left, j = right;
        // 选取基准数
        int base = arr[left];
        while (i != j) {
            // (注意!!!!)从右边开始
            // 找比基准数小的
            while (i < j && arr[j] >= base) {
                j--;
            }
            // 从左边找比基准数大的
            while (i < j && arr[i] <= base) {
                i++;
            }
            // 交换i j
            if (i < j) {
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }
        // 基准数归位(此时i == j)
        arr[left] = arr[i];
        arr[i] = base;
        // 开始左右两边分别快排
        sort(arr, left, i - 1);
        sort(arr, i + 1, right);
    }
}

这里先移动j还是先移动i主要是需要看基准数选取的位置,如果选最左边的数,就需要先移动j(确保i == j 时对应的值是小于基准数的,再把基准数和该数交换,符合排序规则) 如果选取的基准数是最右边,则先移动i指针(确保 i == j 时对应的值是大于基准数的)

测试:

/**
 * @author HelloCode
 * @blog https://www.hellocode.top
 * @date 2023年08月08日 21:14
 */
public class Test {
    public static void main(String[] args) {
        int[] arr = {21, 13, 4, 10, 7, 65, 32, 15, 32, 19, 33, 65, 89, 72, 12};
        System.out.println("排序前:" + Arrays.toString(arr));
        QuickSort.quickSort(arr);
        System.out.println("排序后:" + Arrays.toString(arr));
    }
}

image-20230808213942941

优化

快排的优化主要需要关注基准数的选取,如果待排序数组整体偏降序,此时还选最左侧的为基准数的话,效率就相对慢一些,所以选取一个好的基准数可以让快排的效率更加稳定~

主要的方法有以下几种:

  1. 随机选择基准元素:选择随机的基准元素可以降低最坏情况的概率,进而提高算法性能。
  2. 三数取中法:在选取基准元素时,不是简单地选取第一个或最后一个元素,而是选择中间元素、第一个元素和最后一个元素中的中位数作为基准元素,也可以降低最坏情况的概率。

这里基准数的选取可以很巧妙,还有很多种其他方法,都可以降低最坏情况的发生,本文以三数取中法为例:

/**
 * @author HelloCode
 * @blog https://www.hellocode.top
 * @date 2023年08月08日 21:14
 */
public class QuickSort {
    public static void quickSort(int[] arr) {
        // 特殊情况处理
        if (arr == null || arr.length == 0 || arr.length == 1) {
            return;
        }
        // 递归入口
        sort(arr, 0, arr.length - 1);
    }

    private static void sort(int[] arr, int left, int right) {
        // 递归出口
        if (left > right) {
            return;
        }
        // 初始化双指针
        int i = left, j = right;
        // 选取基准数
        getBase(arr, left, right);
        int base = arr[left];
        while (i != j) {
            // (注意!!!!)从右边开始
            // 找比基准数小的
            while (i < j && arr[j] >= base) {
                j--;
            }
            // 从左边找比基准数大的
            while (i < j && arr[i] <= base) {
                i++;
            }
            // 交换i j
            if (i < j) {
                swap(arr, i, j);
            }
        }
        // 基准数归位(此时i == j)
        arr[left] = arr[i];
        arr[i] = base;
        // 开始左右两边分别快排
        sort(arr, left, i - 1);
        sort(arr, i + 1, right);
    }

    // 取三点中间值(最终会移动到left位置,这样可以不改变原有代码)
    private static void getBase(int[] arr, int left, int right) {
        // 这里可以防止溢出且使用 >> 效率相对能高一些
        // 等价于 (left + right) / 2
        int mid = left + ((right - left) >> 1);
        // 确保第一个元素最小
        if (arr[left] > arr[right]) {
            swap(arr, left, right);
        }
        // 确保最后一个元素最大
        if (arr[mid] > arr[right]) {
            swap(arr, mid, right);
        }
        // 确定mid就是中间值,交换到最左边
        if (arr[left] < arr[mid]) {
            swap(arr, left, mid);
        }
        System.out.println("基准数为:" + arr[left]);
    }

    // 交换数组两下标元素位置
    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

测试:

/**
 * @author HelloCode
 * @blog https://www.hellocode.top
 * @date 2023年08月07日 21:02
 */
public class Test {
    public static void main(String[] args) {
        int[] arr = {21, 13, 4, 10, 7, 65, 32, 15, 32, 19};
        System.out.println("排序前:" + Arrays.toString(arr));
        BubbleSort.bubbleSortOptimized1(arr);
        System.out.println("排序后:" + Arrays.toString(arr));
    }
}

排序前:[21, 13, 4, 10, 7, 65, 32, 15, 32, 19, 33, 65, 89, 72, 12]
基准数为:15
基准数为:7
基准数为:4
基准数为:12
基准数为:10
基准数为:13
基准数为:32
基准数为:21
基准数为:19
基准数为:32
基准数为:65
基准数为:33
基准数为:65
基准数为:72
基准数为:89
排序后:[4, 7, 10, 12, 13, 15, 19, 21, 32, 32, 33, 65, 65, 72, 89]

总结

优点

  1. 高效性:快速排序是一种高效的排序算法,在大多数实际情况下,它的性能通常比其他常见排序算法(如冒泡排序、插入排序)更好。
  2. 原地排序:快速排序是原地排序算法,不需要额外的辅助空间,只需在原始数组上进行交换操作。

缺点

  1. 最坏情况下的性能:当待排序序列已经基本有序或完全逆序时,快速排序的时间复杂度退化为 O(n^2),导致性能下降。这是因为基准元素的选择可能导致分区不平衡,使得分区操作不再能有效地减少问题规模。
  2. 不稳定性:快速排序是一种不稳定的排序算法,意味着相等元素的相对顺序在排序后可能发生变化。这在某些情况下可能导致问题,特别是对于复杂对象的排序,需要额外的处理来保持稳定性。

复杂度

  • 时间复杂度:快速排序的平均时间复杂度为O(n log n),最坏情况下为O(n^2)。
  • 空间复杂度:快速排序是原地排序算法,空间复杂度为O(log n)。

适用于处理大规模数据集的排序,尤其在平均情况下,快速排序的性能较优。但在处理已经有序或近乎有序的数据集时,快速排序的性能可能会下降,这时候其他稳定的排序算法可能更合适。

---------------------------END---------------------------

题外话

“不是只有程序员才要学编程?!”

认真查了一下招聘网站,发现它其实早已变成一项全民的基本技能了。

连国企都纷纷要求大家学Python!
在这里插入图片描述

世界飞速发展,互联网、大数据冲击着一切,各行各业对数据分析能力的要求越来越高,这便是工资差距的原因,学习编程顺应了时代的潮流。

在这个大数据时代,从来没有哪一种语言可以像Python一样,在自动化办公、爬虫、数据分析等领域都有众多应用。

更没有哪一种语言,语法如此简洁易读,消除了普通人对于“编程”这一行为的恐惧,从小学生到老奶奶都可以学会。

《2020年职场学习趋势报告》显示,在2020年最受欢迎的技能排行榜,Python排在第一。
在这里插入图片描述

它的角色类似于现在Office,成了进入职场的第一项必备技能。

如果你也想增强自己的竞争力,分一笔时代的红利,我的建议是,少加点班,把时间腾出来,去学一学Python。

因为,被誉为“未来十年的职场红利”的Python,赚钱、省钱、找工作、升职加薪简直无所不能!

目前,Python人才需求增速高达**174%,人才缺口高达50万,**部分领域如人工智能、大数据开发, 年薪30万都招不到人!在这里插入图片描述

感兴趣的小伙伴,赠送全套Python学习资料,包含面试题、简历资料等具体看下方。

👉CSDN大礼包🎁:全网最全《Python学习资料》免费赠送🆓!(安全链接,放心点击)

一、Python所有方向的学习路线

Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照下面的知识点去找对应的学习资源,保证自己学得较为全面。

img
img

二、Python必备开发工具

工具都帮大家整理好了,安装就可直接上手!img

三、最新Python学习笔记

当我学到一定基础,有自己的理解能力的时候,会去阅读一些前辈整理的书籍或者手写的笔记资料,这些笔记详细记载了他们对一些技术点的理解,这些理解是比较独到,可以学到不一样的思路。

img

四、Python视频合集

观看全面零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。

img

五、实战案例

纸上得来终觉浅,要学会跟着视频一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。img

六、面试宝典

在这里插入图片描述

在这里插入图片描述

简历模板在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值