第一部分--基础知识--第2章:算法入门--2.3分治法及合并排序

说明:该系列博客整理自《算法导论(原书第二版)》,但更偏重于实用,所以晦涩偏理论的内容未整理,请见谅。另外本人能力有限,如有问题,恳请指正!

1、本节摘要

    算法设计的方法有很多。插入排序 使用的是 增量 (incremental)方法:在排好子数组 A [ 1 .. j - 1 ]后,将元素 A [ j ]插入,形成排好序的子数组 A [ 1 .. j ]。

    在本节中,要介绍另一种设计策略,叫做“分治法”。下面要用分治法来设计一个排序算法,使其性能比插入排序好的多。学过第4章就可以知道,分治类算法的优点之一是可以利用在第4章中介绍的技术,很容易的确定其运行时间。

2、分治法思想介绍

    有很多算法在结构上是 递归 的:为了解决一个给定的问题,算法要一次或多次地递归调用其自身来解决相关子问题。这些算法通常采用 分治策略 (divide-and-conquer):将原问题划分成 n 个规模较小而结构与原问题相似的子问题;递归地解决这些子问题,然后再合并其结果,就得到原问题的解。

    归并排序、动态规划算法、贪心算法这些常用的算法都是在“分治法”思想的基础上分析出来的,他们是分治法思想的体现。

    分治模式在每一层递归上都有三个步骤:

            分解(Divide):将原问题分解成一系列子问题;

            解决(Conquer):递归地解各个子问题。若子问题足够小,则直接求解;

            合并(Combine):将子问题的结果合并成原问题的解。

3、归并排序(合并排序)

    归并排序(merge sort)完全依照了 分治策略,直观的操作如下:

            分解:将 n 个元素分成各含 n / 2个元素的子序列;

            解决:对两个子序列递归地排序;若子问题足够小,则直接求解(对子序列排序时,其长度为1时递归结束,因为单个元素被视为是已排好序的);

            合并:合并两个已排序的子序列以得到排序结果。

    归并排序的关键步骤在于合并两个已排序的子序列。这里引入一个辅助过程 MERGE(A, p, q, r) ,其中 A 为数组, p , q 和 r 都是下标,有 p <= q < r 。该过程假设子数组 A [ p .. q ]和 A [ q + 1 .. r ]都已排好序,并将它们合并成一个已排好序的子数组代替当前子数组 A [ p .. r ]。

    MERGE过程的时间代价为Θ(n),其中n = r - p + 1是待合并的元素个数。下面通过扑克牌的例子说明算法的工作过程:假设两堆牌都面朝上的放在桌上,每一堆都是已排序的,且最小的牌在最上面。我们希望把两堆牌合并成一个排好序的输出堆,且面朝下的放在桌上。合并过程为,在面朝上的两堆牌中,选取顶上两张中较小的一张,将其取出后面朝下的放到输出堆中。重复这个步骤,直到某一输入堆为空时为止。这时把输入堆中余下的牌 面朝下的放入输入堆中即可。从计算的角度来看,每一个基本步骤所花时间是个常亮,因为我们只是查看并比较顶上的两张牌,又因至多进行n次比较,所以MERGE过程的时间复杂度为Θ(n)。

    下面的伪代码实现了上述思想,但有一个小的优化,即在两个子堆中增加了∞这个哨兵,来避免检查两个子堆是否为空。一旦出现两个哨兵牌同时出现时,说明两堆牌中非哨兵的牌都已经被放到输出堆中去了,因为预先直到只有r - p + 1张牌会被放到输出堆中去,因此此时也是执行了r - p + 1个基本步骤了,循环可以停止了。

MERGE(A, p, q, r)

1      n1 = q - p + 1

2      n2 = r - q

3      //let L[1 .. n1 + 1] and R[1 .. n2 + 1] be new arrays

4      for i = 1 to n1

5          L[i] = A[p + i - 1]

6      for j = 1 to n2

7          R[j] = A[q + j]

8      L[n1 + 1] = MAX

9      R[n2 + 1] = MAX

10     i = 1

11     j = 1

12     for k = p to r

13         if L[i] <= R[j]

14             A[k] = L[i]

15             i = i + 1

16         else

17             A[k] = R[j]

18             j = j + 1

    下面说明完整的归并排序过程MERGE-SORT,其中 MERGE 过程作为归并排序中的一个子程序来使用。MERGE-SORT(A, p, r) 对子数组 A [ p .. r ]排序。如果 p >= r ,则该子数组中至多只有一个元素,视为已排序。否则,分解步骤就计算出一个下标 q ,将 A [ p .. r ]分成 A [ p .. q ]和 A [ q + 1 .. r ],各含 FLOOR(n / 2) 1 个元素。

MERGE-SORT(A, p, r)

1     if p < r

2         q = (p + r) / 2

3         MERGE-SORT(A, p, q)

4         MERGE-SORT(A, q + 1, r)

5         MERGE(A, p, q, r)

    下图自底向上地展示了当 n 为2的幂时,整个过程中的操作。算法将两个长度为1的序列合并成已排好序的、长度为2的序列,接着又将长度为2的序列合并成长度为4的序列,直到最终形成排好序的 n 的序列。

第一部分--基础知识--第2章:算法入门--2.3分治法及合并排序_第1张图片

4、归并排序时间复杂度分析

    这里只说明分析结果,分析过程请参考《算法导论(原书第二版)》。归并排序 的总代价为cn(lgn + 1) = cn * lgn + cn,忽略低阶项和常亮c,即得到结果Θ(n * lgn)。

    需要说明的是,忽略低阶项和常亮c的前提是输入规模n很大。使用归并排序时一般输入规模都很大,输入规模很小时一般采用的是插入排序。

5、练习

二分查找伪码:

BINARY-SEARCH(A, v)

1  front = 1

2  end = A.length

3  while front < end

4      middle = (front + end) / 2

5      if A[middle] < v

6          front = middle + 1

7      else if A[middle] > v

8          end = middle - 1

9      else

10        return middle

11 return -1

6、参考文档

    算法导论读书笔记(2)

    合并排序,该文章中的两点改进很好:1)、分割为合适长度的小序列后使用插入排序,然后将经过插入排序的小序列进行合并排序;2)、算法中对两个小序列进行合并操作之前,先对其中值点进行比对,如果符合期望的升/降序目标,则不用再经过合并操作,直接连接起来即可。

    C语言合并排序及实例代码

你可能感兴趣的:(第一部分--基础知识--第2章:算法入门--2.3分治法及合并排序)