我们在前面的快速排序(一)中已经阐述了快速排序的基本思想,实现以及优化。前面的排序思想适用于所有数据,前提是存储数据的数据结构支持双向遍历,否则不能进行快排,那么我们应该如何给单向数据结构进行快排呢?今天我们就学习单向快排,可以给单向链表等只支持单向遍历的数据结构进行快速排序。
文章目录:
一、单向扫描的快速排序思想
我们先来分析一下上一篇双向扫描的特点,以此来推导单向扫描:为了方便理解,我们将哨兵用 left 和right表示。
双向扫描: left指向开头,right指向结尾,我们以第一遍排序为例,我们将基准选择最左边的一位,就是arr[0],left,right出发:
- right - -,直到遇到小于基准6的值,定位到了5;
- left + +,直到遇到大于基准6的值,定位到了7;
- 交换数值,继续循环,直到相遇。
那么left,right将数据分为了三部分,从【1~left】这部分是小于基准的,【left~right】是还未划分的数据,【right~末尾】这部分是大于基准的:
那我们现在需要做的就是如何通过单向扫描也把数据划分为3部分,那么就可以双向扫描功能一样,实现一次划分了,从初始位置一次定义两个指针,我们肯定先想到的是通过快慢指针来做,那么我们还是定义left,right,我们利用快慢指针的思想来做,left定位比基准大的值,right继续走,直到right定位小于基准的值,找到后交换,那我们写出单向扫描思想:
单向扫描: 始终以最左边的值作为基准,那我们还是以第一轮排序来详细讲解,基准就是arr[0]的值,right,left指向arr[1]:
- arr[right]<基准,left++,right++,直到left指向7,大于基准;
- 那么left定位7的位置,right向后走right++,找小于基准的值,定位到3;
- 交换数值,left++,right++,循环继续,直到right出右边界。
我们可以看到只要小于基准,那么left,right一直走,大于基准,则left不动,right去找小于基准的。那么right和left同样将数据分为了3部分,【1~left】都是小于基准的,【left~right】都是大于基准的,【right~结尾】是未划分的。
我们可以看到单向和双向一轮的结果是一样的,所以我们只用改变划分函数实现即可,下来还是二分递归继续上述过程或者利用栈,那么我们单向扫描快速排序的思想就是:
- right,left一起走,判断right是否走到了结尾,没有就进入循环;
- 判断right指向的值是否小于基准,小于则交换arr[left]和arr[right],left++,right++;
- 如果right指向的值大于基准,则left不动,定位这个值,right继续往后走,直到找到小于基准的值,和left定位的值进行交换。
- 跳出循环后,将基准和left-1的值交换,将基准归位,一轮结束;
- 继续递归分为左右继续上述过程,直至元素全部归位。
我们画出一轮划分的详细过程:
二、实现单向快速排序
那我们就按照思路,写出单向快速排序,代码思路很明确,利用递归分为左右。我用最左边作为基准,当然也可以用我们上次讲过的三位取中,随机数得到基准。
# include