排序算法之 快速排序

本文详细介绍了快速排序的原理和步骤,包括如何选择基准点,如何进行数组的划分,以及如何通过递归实现整个数组的排序。快速排序是一种平均时间复杂度为O(nlogn)的不稳定性排序算法。文中通过实例演示了快速排序的过程,并提供了升序排列的算法描述,同时也指出降序排列的调整方式。
摘要由CSDN通过智能技术生成

快速排序 是对冒泡排序的改进算法,其平均复杂度是O(nlogn),是一种不稳定的排序算法,其基本流程如下:

与归并排序类似,用分支的思想,假设有三个数 1 0 -1 需要升序排列,约定,从中随机选择一个数作为 基准点,将大于基准点的数放在基准点的右侧,小于基准点的数放在左侧,则能得到:

  • 若选择 1 作为基准点,则有:0,-1,1 和 -1,0,1 两种可能的情况,其中有一种(-1,0,1)是已经排好序的情况,另一种则需要在 0,-1之间再选择一个基准点进行排序才能排好
  • 若选择 0 作为基准点,则只有 -1,0,1这一种情况,即排好序的情况
  • 若选择 -1 作为基准点,则也有两种情况

而快速排序正是这样的一个大流程:选择基准点,将大于基准点的数放至右侧,小于基准点的数放置左侧,然后在左侧重新选择基准点,重复进行,因此,选择基准点就尤为重要,如果选择的不好,则会出现像上面三个数却要选择两次基准点的情况;如果选择的好,即最优情况,每次选择的基准点都是当前要排列的部分的中间值,此时快速排序算法达到最优。一般情况下,基准点不会选择当前需要排列的部分的最值,但也不太可能每次都选择的是中间值,因为选择基准点不可能比较所有的数然后找出中间值(那样还不如直接排序),所以,只要保证基准点不是最值就能保证快速排序不会到它的最差解(最差就是冒泡排序的O(n^2),相当于每次的基准点都是最大值或最小值,此时每一轮只排了一个数),做法是比较要比较的部分的第一个数、中间数以及最后一个数的大小,把这三个数中第二大的数交换到这一部分的开头即可(放到开头是保证交换完之后这个基准点会处于中间的某一位置),详细流程如下:

初始数组为 1, -1, 2, 5, -4, 0, -2, -3, 3, 4
选择基准点:
比较整个数组第 0 个、第 (0+9)/ 2 = 4 个,第 9 个数的大小,即 1,-4,4 三者大小,选择 1 作为基准点,并将其放到左端,即第 0 的位置,将这三个数交换成1,-4,4的位置(但在原数组还是处于这三个位置):
数组变为 1, -1, 2, 5, -4, 0, -2, -3, 3, 4,1 是基准点,按如下方式进行交换:
先从尾部开始比较,如果大于基准点则不用动,小于基准点则将它覆盖到左端,然后从头部开始,如果小于基准点则不用动,大于基准点则将它覆盖到右端,然后再从尾部…这样循环:
1, -1, 2, 5, -4, 0, -2, -3, 3, 4,1 为基准点,首先比较尾部,大于 1 的不用动,第一个小于 1 的数是 -3,将其覆盖左端得到数组 :
-3, -1, 2, 5, -4, 0, -2, -3, 3, 4
然后从头部开始,找到第一个大于 1 的数 2,将其覆盖到右端(此时右端为原 -3 所在处):
-3, -1, 2, 5, -4, 0, -2, 2, 3, 4
然后从右端继续,找到 -2,进行覆盖:
-3, -1, -2, 5, -4, 0, -2, 2, 3, 4 (左端的 2 变为 -2)
接着找左端的 5,覆盖到右端:
-3, -1, -2, 5, -4, 0, 5, 2, 3, 4 (右端的 -2 变为 5)
找右端的 0,覆盖到左端:
-3, -1, -2, 0, -4, 0, 5, 2, 3, 4 (左端的 5 变为 0)
然后发现左端已经没有数大于基准点 1 了,此时比较就完成了,最后把基准点的值赋到最后应该修改的位置,即 右端的 0 位置应该变为 基准点 1:
-3, -1, -2, 0, -4, 1, 5, 2, 3, 4

这样,一次快速排序的划分就结束了,可以发现,上面过程其实是左右交叉进行,其中,左边的 x 变为 y 的话,下一次必定是右边的 y 变为 z,然后又是 左边的 z 变为 a…最终数组变为 -3, -1, -2, 0, -4, 1, 5, 2, 3, 4,其中,基准点 1 左边的数都比 1 小,右边的数都比 1 大,接下来将数组分为 1 左侧和 1 右侧分别重复进行上述操作即可完成整个数组的排序

根据以上描述可以写出升序排列的算法:

/**
 * 交换数组中的两个元素
 * 由于选择基准点时要常常用到交换两个数,
 * 因此另外封装一个函数
 * @param nums 要交换的数组
 * @param index1 要交换的第一个元素下标
 * @param index2 要交换的第二个元素下标
 */
void swap(int nums[], int index1, int index2) {
   
    int tmp = nums[index1];
    nums[index1] = nums[index2];
    nums[index2] = tmp;
}

/**
 * 选择一个基准点,把大于基准点的数放一边,小于它的放另一边
 * 优化基准点的选择,比较左端、中间以及右端的值,
 * 将第二小的放在左端并将作为基准点,最小的放在中间,最大的放在右端,
 * 这样,当一轮比较结束之后,原来左端的值会跑到偏中间的位置
 * @param nums 要排序的数组
 * @param left 要选择基准点那一部分数组的左端点下标
 * @param right 要选择基准点那一部分数组的右端点下标
 */
void middle(int nums[], int left, int right) {
   
    int m = (left+right) / 2;
    /**
     * 前两个交换可以将右端点的元素变为三个中最大,
     * 第三次交换可以将左端点的值变为第二大,即 
     * 将 m 处的元素值变为最小
     */
    if(nums[left] > nums[right]) swap(nums, left, right);
    if(nums[m] > nums[right]) swap(nums, m, right)
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值