算法初接触 | 排序[归并排序、快速排序]

115 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第9天

归并排序

把序列分成长度相同的两个子序列,当无法继续往下分时(也就是每个子序列中只有一个数据时),就对子序列进行归并。归并指的是把两个排好序的子序列合并成一个有序序列。该操作会一直重复执行,直到所有子序列都归并为一个整体为止

图解

01

1.jpg
首先,把序列对半分隔

02

2.jpg
分成两段

03

3.jpg
继续分

04

4.jpg
分割完毕。对分割后元素合并

05

5.jpg
把6和4合并,合并后的顺序为[4,6]

06

6.jpg
把3和7合并,合并后的顺序为[3,7]

07

7.jpg
下面,合并[4, 6]和 [3, 7]。合并这种含有多个数字的子序列时,要先比较首位数字,再移动较小的数字

08

8.jpg
4>3,移动3

09

9.jpg
同样的,再次比较剩下的首位数字

10

10.jpg
4<7,移动4

11

11.jpg
6<7,移动6

12

12.jpg
最后移动剩下的7

13

13.jpg
递归执行以上操作,直至合成整体

14

14.jpg
继续比较两个序列首位数字

15

15.jpg
3>1,移动1,继续操作...

16

16.jpg
合并完成,排序完成

解说
在合并两个已排好序的子序列时,只需重复比较首位数据的大小,然后移动较小的数据,因此只需花费和两个子序列的长度相应的运行时间。
将长度为n的序列对半分割直到只有一个数据为止时,可以分成log₂n行,也就是说,总的运行时间为O(nlogn),这与前面讲到的堆排序相同。

快速排序

首先在序列中随机选择一个基准值(pivot),然后将除了基准值以外的数分为“比基准值小的数”和“比基准值大的数”这两个类别,再将其排列成以下形式。
[比基准值小的数]基准值[比基准值大的数]
接着,对两个“[]”中的数据进行排序之后,整体的排序便完成了。对“[]”里面的数据进行排序时同样也会使用快速排序

图解

01

1.jpg
快速排序

02

2.jpg
在序列随机选择一个基准值,这里选4

03

3.jpg
将其他数字和基准值比较,小于基准值左移,大于基准值右移

04

4.jpg
首先,比较3和基准值4

05

5.jpg
3<4,3左移

06

6.jpg
接下来,比较5和基准值4

07

7.jpg
5>4,5右移

08

8.jpg
继续操作,最终结果如图

09

9.jpg
把基准值4插入序列。左边比它小,右边比它大

10

10.jpg
分别对左右数据进行排序

11

11.jpg
两边操作与之前相同

12

12.jpg
随机选择基准值,这次选6

13

13.jpg
重复之前操作...

14

14.jpg
重复之前操作...

15

15.jpg
重复之前操作...

16

16.jpg
选8为基准值

17

17.jpg
重复之前操作...

18

18.jpg
由于7、8、9完成排序,所以5、6、7、8、9也完成排序

19

19.jpg
于是,最初选择的基准值4的右边排序完成

20

20.jpg
左边同理,整体排序完成

补充说明
快速排序是一种“分治法”。它将原本的问题分成两个子问题(比基准值小的数和比基准值大的数),然后再分别解决这两个问题。子问题,也就是子序列完成排序后,再像一开始说明的那样,把他们合并成一个序列,那么对原始序列的排序也就完成了。
不过,解决子问题的时候会再次使用快速排序,甚至在这个快速排序里仍然要使用快速排序。只有在子问题里只剩一个数字的时候,排序才算完成。
像这样,在算法内部继续使用该算法的现象被称为“递归”。归并排序也可看作是一种递归的分治法。

解说
分割子序列时需要选择基准值,如果每次选择的基准值都能使得两个子序列的长度为原本的一半,那么快速排序的运行时间和归并排序的一样,都为O(nlogn)。和归并排序类似,将序列对半分割log₂n次之后,子序列里便只剩下一个数据,这时子序列的排序也就完成了。因此,如果像下图这样一行行地展现根据基准值分割序列的过程,那么总共会有log₂n行。

0973b2a88cac133076a482450df5b88.jpg