算法理解—— 快速排序 v2.0

Posted 极度迷惘

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法理解—— 快速排序 v2.0相关的知识,希望对你有一定的参考价值。

前言

各位同学大家好,依然是随机依然是天灾… 表情咳咳,走错片场了。
本博文主要是叙述了本人最近研究【快速排序】算法时的一些自己的理解,因为【快速排序】算法相对于其他排序算法而言比较复杂,所以今日针对该算法进行一个总结,同时也帮助自己进一步理解该算法。
好!直接进入正题。



算法源码

由于考虑到【快速排序】算法在不同书籍上的实现方式有略微的差距,又担心手敲代码可能由于疏忽打错,所以本人双手截图以示清白表情
算法源码
算法源码
话说上面代码的位置怎么不对呢?好吧,这是两张图,作者将两段代码分别放在两页纸中…表情
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

以上是关于算法理解—— 快速排序 v2.0的主要内容,如果未能解决你的问题,请参考以下文章

深入理解快速排序和 STL 的 sort 算法

快速排序算法的简单理解

java算法面试题:设计一个快速排序。双路快速排序,简单易于理解。

快速排序算法温习

快速排序和冒泡排序

白话经典算法系列之六 快速排序 快速搞定 转