【算法】--分治法

一、分治法简介


分治策略 (divide-and-conquer):将原问题划分成 n 个规模较小而结构与原问题相似的子问题;递归地解决这些子问题,然后再合并其结果,就得到原问题的解。

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

  1. 分解原问题为若干子问题,这些子问题是原问题的规模较小的实例。
  2. 解决这些子问题,递归地求解各子问题。然而,若子问题的规模足够小,则直接求解。
  3. 合并这些子问题的解成原问题的解。

二、分治法应用——归并排序


归并排序完全依照了分治法的思想:
分解:将个预排列的数分成两个各含n/2个数的子列;
解决:用归并排序法对两个子序列递归地排序;
合并:合并两个已排序的子序列以得到排序结果。
对子序列排序时,其长度为1时递归结束。单个元素被视为是已排好序的。

归并排序的重要步骤就是归并,即将已经排序好的两个子序列合并;
下面引入MERGE过程完成合并:
子数组A1[p…q],A2[q+1…r]合并为A[p…r];

//伪代码
MERGE(A, p, q, r)
  n1 = q - p + 1
  n2 = r - q
  let L[1 .. n1 + 1] and R[1 .. n2 + 1] be new arrays
  for i = 1 to n1
      L[i] = A[p + i - 1]
  for j = 1 to n2
      R[j] = A[q + j]
  L[n1 + 1] = MAX
  R[n2 + 1] = MAX
  i = 1
  j = 1
  for k = p to r
     if L[i] <= R[j]
         A[k] = L[i]
         i = i + 1
     else 
         A[k] = R[j]
         j = j + 1

再就是分解子问题的过程,对于数组A[p…r],只有当p>=r(即只有一个元素)时才视为一个已排序的序列;否则就将该数组分解为子数组A1[p…q],A2[q+1…r];

MERGE-SORT(A, p, r)
 if p < r
     q = (p + r) / 2
     MERGE-SORT(A, p, q)
     MERGE-SORT(A, q + 1, r)
     MERGE(A, p, q, r)

归并排序Java实现:

/*归并排序
*@author WaverlyW
*/
class MergeSort{
    private int p,r;
    private int[] arry;
    MergeSort(int[] arry){
        this.arry=arry;
        p=0;
        r=arry.length-1;
    }
    public static int[] sort(){
        mergeSort(arry,p,r);
        return arry;
    }
    private static void merge(int[] arry,int p,int q,int  r){
        int n1=q-p+1;
        int n2=r-q;
        int[] L=new int[n1];
        int[] R=new int[n2];
        for(int i=0;i<n1;i++){
            L[i]=A[p+i];
        }
        for(int j=1;j<n2;j++){
            R[j]=A[q+j];
        }
        int i=0,j=0;
        for(k=p;k<r;K++){
            if(L[i]<=R[j]){
                A[k]=L[i];
                i++;
            }
            if(L[i]>R[j]){
                A[k]=R[j];
                j++;
            }
        }
    }
    private static int[] mergeSort(int[] arry,int p,int r){
        if(p<r){
            int q=(p+r)/2;
            mergeSort(arry,p,q);
            mergeSort(arry,q+1,r);
            merge(arry,p,q,r);
        }
    }
}

三、算法分析


当一个算法中含有对其自身的递归调用时,其运行时间可以用一个 递归方程 (或 递归式 )来表示。该方程通过描述子问题与原问题的关系,来给出总的运行时间。

设T(n)为一个规模为n的问题的运行时间。如果问题的规模足够小,如 n<=c (c为一个常量),则得到它的直接解的时间为常量,写作Θ(1)。假设我们把原问题分解成 a 个子问题,每一个的大小是原来的1/b 。如果分解该问题和合并解的时间各为 D(n)和C(n),则得到递归式:

归并排序算法分析:

为简化分析,假定原问题的规模是2的幂,这样每次分解产生的子序列长度就恰好为 n / 2。

以下给出了归并排序 n 个数的运行时间。归并排序一个元素的时间是常量。当 n > 1时,将运行时间分解如下:

分解:
计算出子数组的中间位置,需要常量时间,因而D(n)=Θ(1)。
解决:
递归地解两个规模为n/2的子问题,时间为2T(n/2)。
合并:
MERGE 过程的运行时间为Θ(n),则C(n)=Θ(n)。
如此得到归并排序最坏情况下运行时间T(n)的递归表示:

递归式1

此处可以直观地看出 T ( n ) = Θ ( n lg n),重写递归式如下:

递归式2

其中常量 c 代表规模为1的问题所需的时间。

下图说明了如何解递归式2。它将T(n)扩展一种等价树形式。cn是树根(即顶层递归的代价),根的两棵子树是两个更小一点的递归式T(n/2),它们的代价都是cn/2.继续扩展直到问题的规模降到了1,此时每个问题的代价为c。

接下来将树的每一层代价相加。一般来说,最顶层之下的第 i 层有 2i 个结点,每个的代价都是c(n/2i ),于是,第 i 层的总代价为2ic(n/2i)。

要计算递归式的总代价,只要将递归树中各层的代价加起来就可以。在该树中,共有lgn+1层,每一层的代价都是cn,于是,树的总代价为cn(lgn+1) = cnlgn + cn 。忽略低阶项和常量,得到结果 Θ(nlgn )。

p>c(n/2i)。

要计算递归式的总代价,只要将递归树中各层的代价加起来就可以。在该树中,共有lgn+1层,每一层的代价都是cn,于是,树的总代价为cn(lgn+1) = cnlgn + cn 。忽略低阶项和常量,得到结果 Θ(nlgn )。

发布了5 篇原创文章 · 获赞 0 · 访问量 33

猜你喜欢

转载自blog.csdn.net/qq_41160771/article/details/104891317