分治法
分治法思想
将原问题分解为几个规模较小但类似于原问题的子问题,递归地求解这些子问题,然后再合并这些子问题的解来建立原问题的解。
分治模式在每层递归时都有三个步骤:
- 分解原问题为若干子问题,这些问题是原问题的规模较小的实例。
- 解决这些子问题,递归地求解各子问题。然而若子问题的规模足够小,则直接求解。
- 合并这些问题的解成为原问题的解。
归并排序算法
归并排序算法完全遵循分治法思想。其操作如下
- 分解:分解待排序的n个元素的序列成各具n/2个元素的两个子序列。
- 解决:使用归并排序递归地排序两个子序列。
- 合并:合并两个已排序的子序列已产生已排序的答案。
归并排序的最关键操作是合并步骤中两个已排序序列的合并。我们通过调用一个辅助过程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 是待合并元素的总数。它按以下工作方式工作。假设有两组已排序好的数据,数据按照从小到大的顺序排列。我们希望把这两组数据合并成一组排好顺序的数据,并按照从小到 的顺序排列。我们的基本步骤如下:
- 在两个数组中选取第一个数据(每组最小的那个),选取两个数据中较小的那个
- 将选取的数据从原来的数组中分开(该数据原来所在的数组中的原来第二个元素现在已经变成第一个元素)
MERGE(A, p, q, r) 1 n1 = p - q + 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[j] 14 A[k] = L[i] 15 i = i +1 16 else A[k] = R[j] 17 j = j + 1
为了理解过程MERGE的运行时间是Θ(n)其中n = r - p + 1,注意到第1-3行和第8-11行中的每行需要常量时间,第4-7行的 for循环需要Θ(n1 + n2) = Θ(n)的时间。并且,第12-17行的for循环有n次迭代,每次迭代需要常量时间。
现在我们可以把过程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,q,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)为了排序整个序列A = <A[1], A[2],...A[n]>,我们执行初始调用MERGE-SORT(A,1,A.length)。这里再次有A.length = n。 图2-4自底向上的说明了n为2的幂时,该过程的操作。算法由以下操作组成:
- 合并只含1项的序列对形成长度为2的排好序的序列
- 合并长队为2的序列对形成长度为4的排好序的序列
- 依次下去,直到长度为n/2的两个序列被合并成最终形成长度为n的排好序的序列
分析分治算法 分治算法运行时间的递归式来自于基本模式的三个步骤。假设T(n)是规模为n的一个问题的运行时间。若问题规模足够小,如对某个常量c,n <= c,则直接求解需要常量时间,我们将其写作Θ(1)。假设把原问题分成a个子问题,每个子问题的规模是原问题的1/b。(对归并排序,a和b都为2,然而我们将看到许多分治法中,a <>b)。为了求解一个规模为n/b的子问题,需要T(n/b)的时间,所以需要a T(n/b)的时间来解决a个子问题。如果分解问题成子问题需要时间为D(n),合并子问题的解成原问题的解需要时间C(n),那么得到的递归式:
归并排序算法的分析