分治法

分治法的思想,将原问题分解为几个规模较小但类似于原问题的子问题,递归求解这些子问题,然后再合并这些子问题的解来建立原问题的解。

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

  1. 分解原问题为若干子问题;
  2. 解决这些子问题,递归地求解各个子问题;
  3. 合并这些子问题的解。

在算法导论中,以归并排序举例。直观上其操作为:

  1. 分解待排序的n个元素序列成各有n/2个元素的两个子序列;
  2. 使用归并排序递归地排序两个子序列;
  3. 合并两个已经排好序的子序列产生已排序的答案。

下面我将使用一个Merge(A,p,q,r)来完成合并操作。其中A为数组,p、q、r分别为数组下标,满足条件p<=q

n=r-p+1是待合并元素的总数,以下举例说明合并操作:

假设桌面上有两堆已经排好序的扑克牌,最小的牌在顶端,我们进行以下几步操作将两堆扑克合并成单一的排好序的输出堆。

  1. 从两个堆的顶端选取一张较小的,放入输出堆;
  2. 重复操作【1】;
  3. 直到其中一个堆为空,或者两个堆同时为空;
  4. 当一个堆不为空时,我们将这个堆里面剩余的扑克牌依次放置到输出堆;
  5. 完成。

但是我们需要考虑一个问题,如何避免在每个基本步骤中必须检查是否有堆为空。为了解决这个问题,我们在每个堆中加入哨兵(guard),我们使用&作为哨兵牌,将其放在每个堆的最底部,当我们拿到这个牌的时候我们就知道这个堆已经为空。

下面是Merge(A,p,q,r)的伪代码:

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[i]=A[q+j]
8   L[n1+1]=&
9   R[n2+1]=&
10  i=1
11  j=1
12  for k=p to r-q  
13      if L[i]<=R[j]
14	    A[k]=L[i]
15  	i=i+1;
16  else A[k]=R[j]
17      j=j+1

下面我对伪代码每一行的功能进行叙述:

第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中;

第6~7行,for循环将子数组A[q+1……r]复制到R中;

第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[j]是各自所在数组中未被复制回数组A的最小元素。

我们可以将Merge当做归并排序算法中的一个子程序用。下面的Merge-Sort(A,p,r)排序子数组A[p……r]中的元素。若p>=r,则该数组最多有一个元素,所以已经排好序了,否则,分解步骤简单地计算一个下标q,将A[p……r]分成两个子数组A[p……q]和A[q+1……r],前者包含[n/2]个元素,后者包含[n/2]个元素。

伪代码如下:
 

Merge-Sort(A,p,r)
1    if p

 

你可能感兴趣的:(算法,C++)