2.3.1 分治法

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2.3.1 分治法相关的知识,希望对你有一定的参考价值。


插入排序使用了增量方法:在排序子数组A[1..j-1]后,将单个元素A[j]插入子数组的适当位置,产生排序好的子数组A[1..j].


分治法,该算法的最坏情况运行时间比插入排序要少得多。分治算法的优点之一是,通过使用第4章介绍的技术往往很容易确定其运行时间。


分治法


许多有用的算法在结构上是递归的:为了解决一个给定的问题,算法一次或多次递归地调用其自身以解决紧密相关的若干子问题。这些算法典型地遵循分治法思想:将原问题分解为几个规模较小但类似于原问题的子问题,递归地求解这些子问题,然后再合并这些子问题的解来建立原问题的解。


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

分解原问题为若干子问题,这些子问题是原问题的规模较小的实例。

解决这些子问题,递归求解各子问题。然而,若干子问题的规模足够小,则直接求解。

合并这些子问题的解成原问题的解。


  归并排序算法完全遵循分治模式。直观上其操作如下:

  分解:分解待排序的n个元素的序列成各具n/2个元素的两个子序列。

  解决:使用归并排序递归地排序两个子序列

  合并:合并两个已排序的子序列以产生已排序的答案

  当待排序的序列长度为1时,递归"开始回升",在这种情况下不要做任何工作,因为长度为1的每个序列都已排好序。

  归并排序算法的关键操作是"合并"步骤中两个已排序列的合并。我们通过调用一个辅助过程MERGE(A,p,q,r)来完成合并,其中A是一个数组,p,q和r是数组下标,满足p<=q<r。该过程假设子数组A[p..q]和A[q+1..r]都已排好序。它合并这两个子数组形成单一的已排好序的子数组并代替当前的子数组A[p..r].

  过程MERGE需要O(n)的时间,其中n=r-p+1是待合并元素的总数。它按以下方式工作。回到我们玩扑克的例子,假设桌面上有两堆牌面朝上的牌,每堆都已排序,最小的牌在顶上。我们希望把这两堆牌合并成单一的排好序的输出堆,牌面朝下地放在桌上。我们的基本步骤包括在牌面朝上的两堆牌中选取较小的一张,将该牌从其堆中移开(该堆的顶上将显露一张新牌)并牌面朝下地将该牌放置到输出堆。因为我们只是比较顶上的两张牌,所以计算上每个基本步骤需要常量时间。因为我们最多执行n个基本步骤,所以合并需要O(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] = ∞

9   R[n2+1] = ∞

10  i = 1

11  j = 1

12  for k = p to r

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

14       A[k] = L[i]

15       i = i + 1

16     else A[k] = R[j]

17       j = j + 1


  过程MERGE的详细过程如下:第1行计算子数组A[p..q]的长度n1,第2行计算子数组A[q+1..r]的长度n2.在第3行,我们创建长度分别为n1+1和n2+1的数组L和R("左"和"右"),每个数组中额外的位置将保存哨兵。第4-5行的for循环将子数组A[p..q]复制到L[1..n1],第6-7行的for循环将子数组A[q+1..r]复制到R[1..n2].第8-9行将哨兵放在数组L和R的末尾。第10-17行,通过维持以下循环不变式,执行r-p+1个基本步骤:

  在开始第12-17行for循环的每次迭代时,子数组A[p..k-1]按从小到大的顺序包含L[1..n1+1]和R[1..n2+1]中的k-p个最小元素。进而,L[i]和R[i]是各自所在数组中未被复制回数组A的最小元素。

  我们必须证明第12-17行for循环的第一次迭代之前该循环不变式成立,该循环的每次迭代保持该不变式,并且循环终止时,该不变式提供了一种有用的性质来证明正确性。

  初始化:循环的第一次迭代之前,有k=p,所以子数组A[p..k-1]为空。这个空的子数组包含L和R的  k-p=0个最小元素。又因为i=j=1,所以L[i]和R[j]都是各自所在数组中未被复制回数组A的最小元素。

  保持:为了理解每次迭代都维持循环不变式,首先假设L[i]<=R[j].这时,L[i]是未被复制回数组A的最小元素。因为A[p..k-1]包含k-p个最小元素,所以在第14行将L[i]复制到A[k]之后,子数组A[p..k]将包含k-p+1个最小元素。增加k的值(在for循环中更新)和i的值(在第15行中)后,为下次迭代重新建立了该循环不变式。反之,若L[i]>R[i],则第16-17行执行适当的操作来维持该循环不变式。

  终止:终止时k=r+1.根据循环不变式,子数组A[p..k-1]就是A[p..r]且按从小到大顺序包含L[1..n1+1]和R[1..n2+1]中的k-p=r-p+1个最小元素。数组L和R一起包含n1+n2+2=r-p+3个元素。除两个最大的元素以外,其它所有元素都已被复制回数组A,这两个最大元素就是哨兵。

  



以上是关于2.3.1 分治法的主要内容,如果未能解决你的问题,请参考以下文章

分治法

算法---分治法

chatGPT教你算法——分治法

chatGPT教你算法——分治法

第三节.二叉树和分治法

分治法之二分查找