内部排序(五)快速排序(上)- 概述与选择枢轴

本文介绍了快速排序的基本思想和实现过程,包括如何选择枢轴元素,以及通过递归将序列分为两部分进行排序。讨论了枢轴选择对排序效率的影响,提出使用序列中三个数的中位数作为枢轴的优化策略,以避免最坏情况下的时间复杂度达到O(N²)。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

快速排序终于要来了,快速排序也是交换排序的一种,其实讲快速排序前应该先讲讲冒泡排序,因为快速排序是对冒泡排序的一种改进,那就先来用最快速度复习一下冒泡排序:

冒泡排序:假设要对待排序了做升序排序,那么首先从待排序列第一个元素开始,和第二个元素作比较,如果第一个元素大于第二个元素,就把它们的位置交换,接着再对序列第二个元素和第三个元素作比较,如果第二个元素比第三个元素大,就交换位置,一直重复直到长度为N的序列的第N-1个元素和第N个元素做完比较后,当前序列中最大的元素就会被放到序列的最后一个位置,这就完成了一趟的冒泡排序。

第二趟冒泡排序就是从头开始对N-1个元素继续做两两之间的交换,直到完成第N-2个元素和第N-1个元素的交换就完成了第二趟排序,依此类推下去直到最后剩下一个元素后完成。完整实现代码可以看我代码云:

https://gitee.com/justinzeng/codes/qovyh7ga1bkfi30lrptem42

好的回到快速排序,快速排序的概述,和归并排序也有点类似,都采用了分而治之的思想,也就是用递归解决问题。具体举个例子来看看快速排序的递归思想是怎样的:

假设待排序列为:44、12、59、36、62、43、94、7、35、52、85。

  1. 首先从待排序列中选择一个元素作为枢轴(也可以叫中枢或者主元),例如这里选择43,和序列中最有一个元素位置互换。然后设置两个指针分别指向待排序列的头和尾。

  1. 从Low指针开始往右遍历,如果遇到比枢轴大的元素,Low指针就停止,指向遇到的第一个比枢轴大的元素的下标。然后High指针开始往左遍历,如果遇到比枢轴小的元素,High指针就停止遍历,指向遇到的第一个比枢轴小的元素。

  1. 这时判断Low和High两个指针有没有错位(也就是说Low的下标大于High的下标),如果没有错位,就把Low和High两个指针所指的元素互换。

  1. 然后一直重复1-3三步步骤,直到Low和High下标错位后,就把枢轴和Low指针所指的元素互换。

(继续扫描,Low找到比枢轴大的元素,High找到比枢轴小的元素)

(互换位置)

(重复,直到Low和Higt错位)

(枢轴元素和Low位置互换,此时Low的位置前一个元素一定比枢轴小,后一个元素一定比枢轴大)

这样就完成了一趟的快速排序,注意是一趟!!!!

  1. 一趟快速排序后,待排序列就会被分成以枢轴为中心的左右两部分,左边部分全部元素全部比枢轴小,右边部分元素全部比枢轴大。接着继续把左右两部分做递归操作即可。递归的结束条件就是递归到序列只有一个元素时,就停止递归。

 

以上是其中一种递归快速排序的方法,还有第二种递归的思路方法是:

  1. 第一步是一样的,选好一个枢轴,不过这里枢轴选序列的第一个元素,然后设置两个指针Low和High分别指向待排序列的头和尾。

  1. 首先是High指针往左遍历,直到找到第一个比枢轴小的元素,就把High指针指向的元素赋值给Low指针指向的元素:

然后再从Low指针开始往右遍历,直到找到第一个比枢轴大的元素,就把Low指针指向的元素赋值给High指针指向的元素互换:

  1. Low指针遍历完后又轮到High指针,直到Low指针和High指针相等后,就停止:

  1. 最后把枢轴复制到Low的位置,此时两个指针指向的是枢轴正确的位置。

 

快速排序的平均复杂度是?我们想一想,快速排序的方法和归并排序一样是分而治之的思想,归并排序每次一趟排序都是先把序列的首尾下标相加后除以2得到中间位置来把待排序列进行等分,然后再用同样的方法分别递归左右两个子序列,每次都是等分,所以归并排序的平均时间复杂度是O(NlogN)。有归并排序我们可以想到,快速排序采用的也是分而治之的思想,排序过程也是每次把序列分成两部分后递归解决左右两边的子序列,所以如果快速排序每次都能把序列等分的话,最好的时间复杂度是为O(NlogN)。

那么快速排序的思路过程没问题,不过有一个问题没有解决的是,那个枢轴要怎么选?如果枢轴选的不好的话,快速排序的效率就会变低,甚至不如其他很多的排序法。上面快速排序的第二种思路是每次选择序列中的第一个元素作为枢轴,这样的做法其实不太好,我们想想,快速排序最好的时间复杂度为O(NlogN),上一段已经解释为什么了,那么快速排序的最坏时间复杂度是?是O(N²),为什么?我们来想一想,假设我们的待排序列一开始就是完全有序的,假设我们在做快速排序时,每次选择枢轴都是选择序列的第一个元素,那么会出现怎样的情况?

我们一开始选择了序列的第一个元素作为枢轴,Low和High指针做完遍历后,都没有一次移动,所以最后做赋值时枢轴元素还是赋值给了Low,也就是一开始的位置。子序列划分后,枢轴元素的左边为空,右边的N-1个元素做递归,第一趟快速排序的时间复杂度为O(N)+O(N-1),也就是遍历完一遍N个元素后递归遍历右边N-1个元素,随着递归的进行,时间复杂度最终是O(N)+O(N-1)+O(N-2)+ … +O(1)。也就是O(N²)。

      所以选择枢轴方法很重要,选择枢轴的方法有很多,一种比较好的选枢轴方法是讲序列中第一个元素,中间的元素和最后一个元素三者作比较,选择三个数的中位数。例如第一个元素为44,中间元素为43,最后一个元素为85。那么这三个元素的中位数就是43,43就作为这一个序列的枢轴。这个选择枢轴的代码很容易实现,这里先给出吧!

首先定义一个变量Center来保存中位数的位置,也就是(Left+Right)/2,然后三步判断来交换三个数的位置以达到Left、Center和Right三个下标位置的元素按升序来排序。最后把排序后的中位数移到Right-1的位置,因为在做完上面的换位动作后,Right位置的数就一定比Center位置,也就是枢轴元素大,所以可以说Right的位置一定是对的,比枢轴大且在枢轴右边嘛,所以Left的位置也是对的,所以下一步我们只需要对Left+1到Right-2这一段序列做快速排序操作即可。代码最后return出去枢轴,也就是P->arr[ Right-1 ]位置的元素。

      这篇日志先讲到这里,这次讲到了快速排序的概述,也就是实现思路和过程,好了,下一篇日志见!!!!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值