上一次我们说了快速排序的hoare的版本,但是该版本有很多问题,首先是需要控制很多边界,比较复杂一点
其次就是上一次的快速排序还是有很多的其他问题
我们试着想一下,如果我们用快速排序拍有序数组,那会怎么样?或者是重复特别多的会怎么做?
我们可以试着画一下图片
如果是这样的一组顺序,那么首先我们取得key就是第一个位置,9 然后我们就要先从右边开始走,找比 9小的数字,我们直到找到9所属的位置才能停下来,然后我们把9和1交换,那么我们这一次的时间复杂度就是n
然后就会这样,时间复杂度会立马变成n²
后面可以自己试着画一下
那么要怎么解决目前的这个问题呢?
我们可以想一下,如果我们每一次选择的key都不是该数组中最大或者最小的数,那么就可以避免这种问题
解决方法
1.采用随机选数法,我们可以用rand( ) 函数随机选择数组中的某个数字,然后让他和最左边的数字交换就可以了,这样就可以解决有序数组用快排的问题了,但是由于随机数不确定,不免有时候也会随机到最大或者最小的数字,所以它还不是最优的
2.我们可以采用“三数取中”,正如他的名字一样,我们在三个数字中选择值为中间的数字,然后交换,这样的话,如果是有序数组的话,那么三数取中是最优的,其次三数取中,也可以避免选取到最大或者最小的数字
那么如何实现呢?
下面我们就直接看代码
这次我们稍微看一下整体的逻辑,请看下面的代码
这次我们直接在快排这个函数里面调用我们的hoare版本的快速排序,然后利用返回值,记录key的位置,然后继续递归,其中在hoare版本的快排中,我们中间使用了三数取中
上面这个我相信都能看明白
下面我们在介绍一个快排,挖坑法
挖坑法
我们还是先看一下思想,其实这个的思想和hoare差不多,但还是有一些区别的
下面我们就看一下
首先我们就是和上面图片是一样的,我们先把key这个值记录起来,然后先右边找小,然后再左边找大
看图片
然后右边找小,找到了,然后把r位置的数字放在坑位,然后坑位到了r下面
然后就是左边找大
同样是找到了,放在坑位,然后坑位变化
请继续看
👀
请 👀
最后就把6的左边都是比他小的,6的右边都是比他大的,所以一趟排序就结束了,剩下的递归就是和hoare版本一样了
所以我们剩下就直接看代码
其中上面就是一趟排序
我们先记录key的值,然后再记录一下坑位,然后我们就是右边找大,然后左边找小,放在坑位里,等到了最后相遇的时候我们就出了循环,但是坑位我们还没有吧key放进去,所以我们放进去,最后我们返回坑位置的下标就行
然后我们调用该函数,递归就行
下面我们就讲一下前后指针法
前后指针法和前面两个版本的实现是有差别的,但是写起来是比较容易的
所以下面就来看一下前后指针法
我们还是看一趟排序的思路
刚开始的时候我们定义两个指针,一个指向第一个元素,还有一个指向第二个元素
然后我们就向下看
我们看cur的值是否大于keyi的值,如果大于的话就让cur加加,如果cur和prev都小于的话就让她们两个同时加加,如果prev大于key的值,但是cur小于key的值,那么就交换
下面看图片
首先我们就到了cur的值大于key的值,然后就让cur++就可以了
然后就走到了3的位置,然后cur就和prev交换
就是上面这样
然后继续向前推进
直到最后,cur大于最右边这时候让key和prev位置交换就行了
下面我们就看一下代码
我们先定义key,然后再定义prev和cur的位置,这时候我们就进去循环,cur要小于right,这时候我们就开始判断,我们可以看到,我们不论怎么样cur都需要++那么我们就把cur++放在判断的外面,循环里面,这时候我们只需要判断prev什么时候++就可以了,如果我们的cur小于key的值,这时候我们的prev就++,如果这时候的prev 如果不等于cur的话(大于key),那么就交换
就是这样
下面我们就看一下非递归如何快排
我们先看思想
如果我们想非递归的话,那么我们要怎么做?
首先我们直接用循环肯定是不可能的,这时候我们就可以使用栈,或者队列都可以
那么我们应该怎么做呢?
下面来和我一起看一下
假设我们用栈来实现非递归,并且我们要排序的是十个数字
下面我们就来看一下
我们先想一下我们的递归是如何递归的,我们刚开始是一趟排序,等一趟排序后,我们有一个值就放在了固定的位置
假设是这样子的,我们第一趟已经拍好了,此时我们5就放在了固定的位置,接着我们就要排序0~4 和 6~9当然我们是先排序0~4的如果0~4排序好了以后就是这样
然后假设我们的2已经排序好了,然后我们继续排序
0~1 3~5
就是这样
后面也都是这样,递归起来就是这样,所以我们如果想要使用栈来实现非递归那么我们要怎么做?
这里我就直接说了
我们可以先把数组的头和尾下标0和10给push到栈中,然后我们对这一段进行一趟排序,然后我们把这两个给pop掉,然后我们排序好之后返回了一个key
下面请看图片
就像这样
先push进去,然后对这一段数据进行一趟排序,假设我们排序好后就是5是key
当然我们是需要把0和10是要pop掉的,然后我们继续把 0和4push进去 在把6和10push进去
然后继续
看图片
就像这样
然后就是取栈顶的两个元素,对这两个元素间的数组进行排序,然后返回key为2
然后继续push就像这样,我们后面的也是这样子做的,下面直接看代码
就像这样,我们当然需要先有一个栈,然后就是对栈进行初始化等....
下面我们先把数组的头和尾push进去,如果栈不为空的话,那么就继续
等进去循环之后我们就记录push进去的头尾,然后对这段数组一趟排序,然后我们判断key是否需要push,如key-1都小于等于begin了,那么说明只剩下一个值了,或者是没有一个值了,所以我们就不需要push这段区间了,如果key+1大于等于end说明也没必须push了
就是这样,知道栈为空了,说明我们的排序也就结束了