快速排序(挖坑法 前后指针法 非递归版本)

上一次我们说了快速排序的hoare的版本,但是该版本有很多问题,首先是需要控制很多边界,比较复杂一点

其次就是上一次的快速排序还是有很多的其他问题

我们试着想一下,如果我们用快速排序拍有序数组,那会怎么样?或者是重复特别多的会怎么做?

我们可以试着画一下图片

28c9fcc7abca444ebb5f2a315a707be1.png

如果是这样的一组顺序,那么首先我们取得key就是第一个位置,9 然后我们就要先从右边开始走,找比 9小的数字,我们直到找到9所属的位置才能停下来,然后我们把9和1交换,那么我们这一次的时间复杂度就是n

快速排序(挖坑法 前后指针法 非递归版本)_第1张图片

然后就会这样,时间复杂度会立马变成n²

后面可以自己试着画一下

那么要怎么解决目前的这个问题呢?

我们可以想一下,如果我们每一次选择的key都不是该数组中最大或者最小的数,那么就可以避免这种问题

解决方法

1.采用随机选数法,我们可以用rand( ) 函数随机选择数组中的某个数字,然后让他和最左边的数字交换就可以了,这样就可以解决有序数组用快排的问题了,但是由于随机数不确定,不免有时候也会随机到最大或者最小的数字,所以它还不是最优的

2.我们可以采用“三数取中”,正如他的名字一样,我们在三个数字中选择值为中间的数字,然后交换,这样的话,如果是有序数组的话,那么三数取中是最优的,其次三数取中,也可以避免选取到最大或者最小的数字

那么如何实现呢?

下面我们就直接看代码

快速排序(挖坑法 前后指针法 非递归版本)_第2张图片

这次我们稍微看一下整体的逻辑,请看下面的代码

快速排序(挖坑法 前后指针法 非递归版本)_第3张图片 

快速排序(挖坑法 前后指针法 非递归版本)_第4张图片 

这次我们直接在快排这个函数里面调用我们的hoare版本的快速排序,然后利用返回值,记录key的位置,然后继续递归,其中在hoare版本的快排中,我们中间使用了三数取中

上面这个我相信都能看明白

下面我们在介绍一个快排,挖坑法

挖坑法

快速排序(挖坑法 前后指针法 非递归版本)_第5张图片

我们还是先看一下思想,其实这个的思想和hoare差不多,但还是有一些区别的

下面我们就看一下

首先我们就是和上面图片是一样的,我们先把key这个值记录起来,然后先右边找小,然后再左边找大

看图片

然后右边找小,找到了,然后把r位置的数字放在坑位,然后坑位到了r下面

快速排序(挖坑法 前后指针法 非递归版本)_第6张图片 

 然后就是左边找大

快速排序(挖坑法 前后指针法 非递归版本)_第7张图片

 同样是找到了,放在坑位,然后坑位变化

快速排序(挖坑法 前后指针法 非递归版本)_第8张图片

 请继续看

快速排序(挖坑法 前后指针法 非递归版本)_第9张图片

 

快速排序(挖坑法 前后指针法 非递归版本)_第10张图片

快速排序(挖坑法 前后指针法 非递归版本)_第11张图片

 最后就把6的左边都是比他小的,6的右边都是比他大的,所以一趟排序就结束了,剩下的递归就是和hoare版本一样了

所以我们剩下就直接看代码

快速排序(挖坑法 前后指针法 非递归版本)_第12张图片

其中上面就是一趟排序

 我们先记录key的值,然后再记录一下坑位,然后我们就是右边找大,然后左边找小,放在坑位里,等到了最后相遇的时候我们就出了循环,但是坑位我们还没有吧key放进去,所以我们放进去,最后我们返回坑位置的下标就行

快速排序(挖坑法 前后指针法 非递归版本)_第13张图片

然后我们调用该函数,递归就行

 下面我们就讲一下前后指针法

前后指针法和前面两个版本的实现是有差别的,但是写起来是比较容易的

所以下面就来看一下前后指针法

我们还是看一趟排序的思路

快速排序(挖坑法 前后指针法 非递归版本)_第14张图片

刚开始的时候我们定义两个指针,一个指向第一个元素,还有一个指向第二个元素

然后我们就向下看

我们看cur的值是否大于keyi的值,如果大于的话就让cur加加,如果cur和prev都小于的话就让她们两个同时加加,如果prev大于key的值,但是cur小于key的值,那么就交换

下面看图片

 快速排序(挖坑法 前后指针法 非递归版本)_第15张图片

首先我们就到了cur的值大于key的值,然后就让cur++就可以了

快速排序(挖坑法 前后指针法 非递归版本)_第16张图片 

然后就走到了3的位置,然后cur就和prev交换

就是上面这样

快速排序(挖坑法 前后指针法 非递归版本)_第17张图片 

然后继续向前推进

快速排序(挖坑法 前后指针法 非递归版本)_第18张图片 

快速排序(挖坑法 前后指针法 非递归版本)_第19张图片 

快速排序(挖坑法 前后指针法 非递归版本)_第20张图片 

直到最后,cur大于最右边这时候让key和prev位置交换就行了

下面我们就看一下代码

快速排序(挖坑法 前后指针法 非递归版本)_第21张图片 

我们先定义key,然后再定义prev和cur的位置,这时候我们就进去循环,cur要小于right,这时候我们就开始判断,我们可以看到,我们不论怎么样cur都需要++那么我们就把cur++放在判断的外面,循环里面,这时候我们只需要判断prev什么时候++就可以了,如果我们的cur小于key的值,这时候我们的prev就++,如果这时候的prev 如果不等于cur的话(大于key),那么就交换

就是这样

下面我们就看一下非递归如何快排

我们先看思想

如果我们想非递归的话,那么我们要怎么做?

首先我们直接用循环肯定是不可能的,这时候我们就可以使用栈,或者队列都可以

那么我们应该怎么做呢?

下面来和我一起看一下

快速排序(挖坑法 前后指针法 非递归版本)_第22张图片

假设我们用栈来实现非递归,并且我们要排序的是十个数字

下面我们就来看一下

我们先想一下我们的递归是如何递归的,我们刚开始是一趟排序,等一趟排序后,我们有一个值就放在了固定的位置

快速排序(挖坑法 前后指针法 非递归版本)_第23张图片 

假设是这样子的,我们第一趟已经拍好了,此时我们5就放在了固定的位置,接着我们就要排序0~4 和 6~9当然我们是先排序0~4的如果0~4排序好了以后就是这样

快速排序(挖坑法 前后指针法 非递归版本)_第24张图片 

然后假设我们的2已经排序好了,然后我们继续排序

0~1    3~5  

就是这样

后面也都是这样,递归起来就是这样,所以我们如果想要使用栈来实现非递归那么我们要怎么做?

这里我就直接说了

我们可以先把数组的头和尾下标0和10给push到栈中,然后我们对这一段进行一趟排序,然后我们把这两个给pop掉,然后我们排序好之后返回了一个key

下面请看图片

就像这样

快速排序(挖坑法 前后指针法 非递归版本)_第25张图片

先push进去,然后对这一段数据进行一趟排序,假设我们排序好后就是5是key

当然我们是需要把0和10是要pop掉的,然后我们继续把 0和4push进去 在把6和10push进去

然后继续

看图片

快速排序(挖坑法 前后指针法 非递归版本)_第26张图片

就像这样

快速排序(挖坑法 前后指针法 非递归版本)_第27张图片 

然后就是取栈顶的两个元素,对这两个元素间的数组进行排序,然后返回key为2

快速排序(挖坑法 前后指针法 非递归版本)_第28张图片 

然后继续push就像这样,我们后面的也是这样子做的,下面直接看代码

快速排序(挖坑法 前后指针法 非递归版本)_第29张图片 

就像这样,我们当然需要先有一个栈,然后就是对栈进行初始化等....

下面我们先把数组的头和尾push进去,如果栈不为空的话,那么就继续

等进去循环之后我们就记录push进去的头尾,然后对这段数组一趟排序,然后我们判断key是否需要push,如key-1都小于等于begin了,那么说明只剩下一个值了,或者是没有一个值了,所以我们就不需要push这段区间了,如果key+1大于等于end说明也没必须push了

就是这样,知道栈为空了,说明我们的排序也就结束了

 

 

你可能感兴趣的:(数据结构,排序算法,排序算法,算法,数据结构)