作者:几冬雪来
时间:2023年4月9日
内容:数据结构排序内容讲解
目录
前言:
在上一篇博客中,我们再次讲解了数据结构的排序板块,同样的我们也对其板块重要的排序方法——快速排序进行了讲解和代码的书写。而在今天我们就在快速排序的基础上对它进行一个优化。
1.快速排序多趟排序实现:
在上一篇博客中我们讲解了我们的快速排序,就是选择最左边或者最右边的值作为我们的key,然后让两边的值与key进行比较,如果在右边找到一个小于key的数并且也在左边找到一个大于key的数,那么我们就将两数交换。最后再把我们的下标为key和下标left和right重叠处的值进行一个互换,这就是我们快速排序的单趟排序。
但是这种方法也就只是让交换后的key左边变得比它小,右边变得比它大而已,我们并不能将整个数组变得有序,在这里我们就需要用到递归来重复我们上面的操作。
类似上面这个图,因为在第一次快速排序的时候,我们已经将key放在了它正确要存在的位置,因此在第一次快速排序之后,我们就可以将这个值暂时去掉,将它左边看作一个数组,右边也看作一个数组,重新选择出两个数组中新的key,再分别递归进行快速排序。
这里的结束条件也是十分的好判断,当递归区间不存在(不存在的区间一定存在)的时候,我们的递归就结束了,这种递归方法十分的类似于二叉树。那么讲解了这么多,接下来就是需要我们动手写代码的时候了。
这里就是我们最简单的快速排序,这里我们就来运行我们的代码来看一下。
这里也可以看出来,我们的这个快速排序的代码成功的将我们的数组进行了一个排序,让它变得有序起来了。
2.快速排序的时间复杂度:
简单讲解完了我们的快速排序之后,接下来我们就来讲讲它的时间复杂度。
在这里我们的时间复杂度的量级是O(N*logN),下来我们就要对其进行分析。
这里就是我们快速排序时间复杂度的计算,计算完了快速排序的时间复杂度之后,这里我们就来分析它的每一趟排序的时间复杂度是多少。
在这里我们第一次快速排序的时候,每个值都要参与排序,所以这里为N,但是在第二次之后这里我们第一次的key就可以不用参加排序了,这里就为N-1。
那么这么好用的排序,它的时间复杂度最坏的情况是多少呢,又是在哪种情况下它的时间复杂度最坏呢?
当我们的数组为顺序或者为逆序的时候,这个时候我们的时间复杂度是最差的,为什么?
当我们的数组为顺序的时候,这里我们的right就会一个劲的往左走,直到碰到我们的left,就到我们的第一个值的地方,然后让它和自己进行一个交换,逆序则是反着来。每次都跑一遍,但是每次都只是去除头或者尾下标的值,对我们的数组没有多大的影响。
那么这里是key的值出现了问题,如果这里每次选择的key都选择的是我们排序后靠中间的值,这里就可以解决这一个问题。而这里最最最简单粗暴的方法就是给一个随机值。这里就要用到我们的——rand了。
这里就是我们找随机值的步骤,最需要注意的是在找了我们的随机值之后,要让它与我们下标为left的值进行互换,不然这个优化没有任何意义。
同样的我们的代码也可以运行起来,这种方法就是我们的随机选key法。它的原理就是不管我们的数组是有序是无序又或者是逆序,随机选key法统统将我们的数组变得无序。
除了这一种方法之外,我们还有另一种方法——三数取中法。
这种方法则更加的简单粗暴,就是通过我们left和right的下标相加的和除以2来找到它数组中间的值,然后将其3个下标所对应的数进行对比,如果某个数大于一个数,并且小于另一个数的话,我们就将这个数给设定为key。
那么下来就是书写我们的代码的时候了。
这就是我们的三数取中法,这里的代码依旧可以运行起来。
那么在这里我们都有将其余的排序区分出最好情况和最坏情况,那为什么在我们快速排序这里却只是讲解了它的最好情况呢?这并非是搞特殊,而且我们的快速排序的代码可以被优化,导致它的最坏情况不会出现,因此在这里快速排序的时间复杂度我们只看它的最好情况。
3.为什么快速排序相遇位置的值小于key:
在我们介绍快速排序的时候,我们曾经说过——如果选最右边作为我们的key,那么我们的快速排序要先从右边先找小。
这里就是我们相遇位置的值小于key的原因。
因为在这里右边先走,如果没有找到小于key的值,我们就在它的left处相遇,如果这里我们换右边先走的话,可能会导致相遇位置的值大于我们的key。
这里就记住结论就行了。
4.快速排序(挖坑法):
这里我们通过快速排序改造出来了另一种快速排序的排序方法——快速排序挖坑法。
那么这种方法是如何实现的呢?我们依旧画一个图来表示。
这就是我们挖坑法的实现方式,一开始先保留我们key的值,并且把它当成是坑。依旧是右边先走,找到小的值,这个时候我们就把这个值换到我们原来的坑上,同时将这个值的位置设置为我吗新的坑。最后当两个下标相遇的时候,我们再把我们的key放进去即可。
这就是我们挖坑法的代码,但是实际上我们的挖坑法对比我们原先的快速排序并没有什么实质上的变化。
5.前后指针法:
接下来讲解的是我们快速排序中一种十分有效的方法,这种方法并没有准确的名字,因此在这里我们就把它叫做——前后指针法。
那么这种方法是如何运行的?
这就是我们的操作原理,一开始我们就定义两个指针,cur在prev的前一个位置。接下来prev和cur都进行++操作,它们两个紧挨着走。
但是当我们的cur++后的值大于我们的key,这个时候cur就要不停的进行++,直到找到小于key的值的下标处,然后对prev进行++,接下来将我们的cur和prev位置的值进行一个交换,最后cur进行++再次去找小。
知道了这一些内容之后,接下来就需要我们将它的代码给写出来了。
这里就是我们的代码,取一个keyi作为我们第一个数的下标,接下来取两个变量作为它数组的第一个值和第二个值的下标。这里因为cur的情况只可能大于或者等于prev,所以这里我们拿cur示范小于等于right作为我们的结束条件,如果循环内我们找到一个下标cur所对应的值小于我们的keyi,并且++prev不等于我们的cur也就是二者不重叠的时候,我们就将两数进行交换,如果不满足的话我们的cur就一直++下去。
在最后循环结束之后,我们再将prev处所对应的值与我们的第一个值进行一个交换后,再将prev赋值给keyi,并且返回我们keyi,这就是我们的前后指针法。
最后通过我们的测试,也是成功的将代码排得有序了。
结尾:
到这里,我们的数据结构排序板块的快速排序的两次优化的为什么优化,优化过程和优化结果都已经将其书写出来了,接下来再过几次后我们的数据结构的排序板块就大概要完结了。最后希望这篇博客能能为各位正在学习排序的同学带来一定的帮助。