前言
各位同学大家好,依然是随机依然是天灾… 咳咳,走错片场了。
本博文主要是叙述了本人最近研究【快速排序】算法时的一些自己的理解,因为【快速排序】算法相对于其他排序算法而言比较复杂,所以今日针对该算法进行一个总结,同时也帮助自己进一步理解该算法。
好!直接进入正题。
算法源码
由于考虑到【快速排序】算法在不同书籍上的实现方式有略微的差距,又担心手敲代码可能由于疏忽打错,所以本人双手截图以示清白。
话说上面代码的位置怎么不对呢?好吧,这是两张图,作者将两段代码分别放在两页纸中…
OK,截图完毕,本算法是【图灵程序设计丛书 算法 第4版】上的源码,总共三个函数,代码量并不多,但是比较难以理解。先别急着看源代码,让我们来一步一步进行分析。
分步理解
1、第一个函数
该函数,仅仅就是为了让别人使用起来比较方便,针对于数组arr的0 ~ length - 1的位置进行排序
2、第二个函数
代码内容如下:
很明显,使用了分治的理念,使用迭代从大数组到小数组进行排序。从函数partition中返回一个下标j,然后再分别sort下标j左边的数组和下标j右边的数组。
从这里可以得出:下标j左边的元素恒比arr[j]要小;而下标j右边的元素恒比arr[j]要大。
因为从上图可以发现,arr[j]不参与接下来的排序,如果不符合刚刚得出的结论,则该算法无法达到排序效果。
3、第三个函数
这个函数也就是实现排序,并返回下标j的具体实现。有点小激动,让咱们继续探索其中的奥秘吧。
a、首先记录左右指针的下标,左右指针?干嘛用的?先别急,咱们向下看先;
b、然后使用数组的第一个元素作为参照物(切分元素),这又是干嘛的?继续向下看;
c、开始循环扫描
i、当左指针元素比参照物小,则左指针向右移并继续循环。
也就是说,左指针负责向右找第一个比参照物大的元素
(if语句中,如果遍历到数组允许的最大位置hi还没有找到比参照物大的元素时,则跳出循环,也就是指向hi);
ii、当参照物比右指针元素小,则右指针向左移并继续循环。
也就是说,右指针负责向左找第一个比参照物小的元素
(if语句中,如果遍历到数组允许的最小位置lo还没有找到比参照物小的元素时,则跳出循环,也就是指向lo);
iii、如果i >= j的时候,则直接跳出循环,也就是左指针最终的下标,比右指针最终的下标大,则跳出循环;
iv、如果i < j的时候,则交换左右指针所指向元素的位置。
d、交换参照物的下标lo与右指针j的位置;
e、最终返回右指针j的下标值。
说到这里,整个算法就讲完了。
完了?完了?! 噼里啪啦*&……%¥%#@ 猴赛给! 啊罗列啊给痛!啊通!啊同!
各位大侠先别着急,下面,开始对上面所说内容进行分析。
举例说明
我们用一个简单的例子带入进行分析该算法,便于我们理解。
{ 5, 7, 9, 4, 1, 3, 6, 8, 2 }
接下来咱们按照上面所给的流程走:
【a】、左指针i,右指针j
i j
5 7 9 4 1 3 6 8 2
【b】、参照物v
i j
v
5 7 9 4 1 3 6 8 2
【c】、开始循环扫描
——————————第一次扫描——————————
【i】、左指针i向右找第一个比参照物v大的值,找到了!就是7!
i j
v
5 7 9 4 1 3 6 8 2
【ii】、右指针向右找第一个比参照物v小的值,找到了!就是2!
i j
v
5 7 9 4 1 3 6 8 2
【iii】、如果i比j大,则跳出循环。这里i并没有j大,继续;
【iv】、交换左右指针所指向的元素的位置。
i j
v
5 2 9 4 1 3 6 8 7
——————————第二次扫描——————————
【i】、左指针i向右找第一个比参照物v大的值,找到了!就是9!
i j
v
5 2 9 4 1 3 6 8 7
【ii】、右指针向右找第一个比参照物v小的值,找到了!就是3!
i j
v
5 2 9 4 1 3 6 8 7
【iii】、如果i比j大,则跳出循环。这里i并没有j大,继续;
【iv】、交换左右指针所指向的元素的位置。
i j
v
5 2 3 4 1 9 6 8 7
——————————第三次扫描——————————
【i】、左指针i向右找第一个比参照物v大的值,找到了!就是9!
ij
v
5 2 3 4 1 9 6 8 7
【ii】、右指针向右找第一个比参照物v小的值,找到了!就是1!
j i
v
5 2 3 4 1 9 6 8 7
【iii】、如果i比j大,则跳出循环。这里i比j大,跳出循环,循环结束;
【d】、交换参照物的下标lo与右指针j的位置;
j i
v
5 2 3 4 1 9 6 8 7
j i
v
1 2 3 4 5 9 6 8 7
【e】、最后返回下标j,然后到sort函数中去,分别对 begin ~ j-1 和 j+1 ~ end 进行排序。
到这里,你会发现,上图中右指针j的左侧的内容(包括指针j),恒比参照物小;而右指针j右侧的内容,恒比参照物大…惊讶
那么【c】流程的目的就很明确了:将数字进行分类。
分析
1、因为左指针i是找比参照物大的,右指针j是找比参照物小的,所以【iv】流程的目的就是:
交换大于参照物的数值与小于参照物的数值。
也就是说,将“比参照物小的数值”尽量放在数组的左边,而将“参照物大的数值”尽量放在数组的右边。
2、问:那为什么i >= j的时候,不需要进行交换并跳出循环呢?
答:
a、当i == j的时候,两个元素在同一位置,所以不需要交换;
b、当i > j的时候,分两种情况:
i、左指针没有找到比参照物大的,或者右指针没有找到比参照物小的,再或者两者皆是;
出现这种情况,说明参照物大于、小于或者等于数组中的所有内容,则不需要分类。
ii、左指针找到了比参照物大的,而且右指针找到了比参照物小的。
首先arr[j] < arr[i]这个不用多说(如果不理解,则买块豆腐自吻谢罪可怜),然后i > j…
分类本来就是将大元素的放在右端,而将小元素放在左端,上面情况已经是正常分类。
将数字进行分类之后的结果如下图所示:
j i
v
5 2 3 4 1 9 6 8 7
也就是说,除了参照物之外的数组区域,比参照物小的都在左端,比参照物大的都在右端。
所以【d】流程的目的也很明确了:
就是将参照物插入“比参照物小的值”和“比参照物大的值”两种类型的中间,最终完成数字的分类。
插入(位置替换)之后如下图所示:
j i
v
1 2 3 4 5 9 6 8 7
那么有人会问:为什么将参照物和j进行位置替换呐?
因为:
a、j是右指针,跳出循环的同时,说明j的右端全部是大于参照物的元素;
b、而j的左端全部是小于参照物的元素(包括j),因为右指针j负责找第一个比参照物小的数值。
所以将参照物和j进行位置替换,也就是将小元素(j)放到第一个位置,然后将参照物放到“比参照物小的值”和“比参照物大的值”两种类型的中间。
到这里,快速排序算法的奥秘已经全部揭开。
总结
很多朋友到现在估计已经一脸懵逼,现在来帮大家总结一下快速排序算法的流程。
1、首先,将数组进行“分类”处理;
a、将数组的第一个元素作为参照物;
b、除了参照物以外,将剩余的数组进行“分类”操作;
c、最后将参照物插入“较小元素”和“较大元素”两种类型的中间。
2、将分类后的左端和右端分别再次进行分类(分类到只剩2~3个元素时的“分类”操作,实际上就是排序);
3、分类到只剩一个元素时,直接不进行操作。
*、大家以为到了这里,算法就完了。错!算法最最重要的地方到了!
这一段的代码的含义是什么呢??
哼哼!這段代码可腻害了!
这段代码的的意义是:跟本算法并没有任何关系。
噼里啪啦*&……%¥%#@ 猴赛给! 啊罗列啊给痛!啊通!啊同!啊痛!痛!痛!通!
作者感言
到这里,整个算法就分析完了。
其实,这里我举得例子是比较均衡的例子,并没有分析比较极端的情况(参照物大于、小于和等于所有的元素)。本博文的目的就是为了分析【快速算法】的原理及流程,其实比较极端的情况,自己分析一下也能理解,这里就不多做说明了。
再一个,由于本人不怎么会用CSDN上面的编辑器,所以该博文时纯手工打造,绿色、无毒、纯天然…
上面的举例图都是用空格排版的,如果有格式上的问题,可以致电我的邮箱,我这有Word的版本,格式是正常的,我可以发给你。
以上便是本人对【快速排序】算法的个人理解及分析,如果有什么问题,欢迎来本人的邮箱吐槽和讨论。
邮箱地址:444208472@qq.com