分治算法 简析及举例

来源:互联网 发布:卡蒙刷q币软件 编辑:程序博客网 时间:2024/06/18 04:09

算法思想

1.分解:将一个难以直接解决的问题,分割成一些规模较小的相同问题,各个击破,分而治之。另外,处于平衡思想,子问题的规模大致相同时效率最优。
2.合并:将子问题的解决结果合并,得到该问题的解。

适用条件

1.缩小到一定规模可以解决(拿到问题先思考规模最小时的解决方法,再推而广之)
2.分解出的规模小、类型相同的子问题具有最优子结构性质。(递归思想的应用)
3.子问题的解可以合并为该问题的解。(若不满足这一点,则考虑贪心算法或动态规划算法)
4.子问题具有独立性(若不满足这一点,则算法会做很多重复工作,效率不高,最好用动态规划算法)

基本步骤

divide-and-conquer(P)  {    if ( | P | <= n0) adhoc(P);   //解决小规模的问题    divide P into smaller subinstances P1,P2,...,Pk;//分解问题    for (i=1,i<=k,i++)      yi=divide-and-conquer(Pi);  //递归的解各子问题    return merge(y1,...,yk);  //将各子问题的解合并为原问题的解  }

经典问题应用

1.二分搜索:给定升序排列的n个元素,要在n个元素中找出指定元素x。没一次执行while循环,问题规模少一半,且循环体内复杂度O(1),因此O(logn)。
类似的问题还有求数组的最大值。

//非递归实现int BinarySearch(Type a[], const Type& x, int l, int r){     while (r >= l){         int m = (l+r)/2;        if (x == a[m]) return m;        if (x < a[m]) r = m-1; else l = m+1;        }    return -1;} 

2.合并排序:将待排序元素分成大小大致相同的2个子集合,分别对2个子集合进行排序,最终将排好序的子集合合并成为所要求的排好序的集合。 当n<=1,T(n)=(1);当n>1,T(n)=2T(n/2)+O(n);T(n)=O(nlogn)

void MergeSort(Type a[], int left, int right)   {      if (left<right) {//至少有2个元素      int i=(left+right)/2;  //取中点      mergeSort(a, left, i);      mergeSort(a, i+1, right);      merge(a, b, left, i, right);  //合并到数组b      copy(a, b, left, right);    //复制回数组a      }   }

3.棋盘覆盖问题:在一个2k×2k 个方格组成的棋盘中,恰有一个方格与其它方格不同,称该方格为一特殊方格,且称该棋盘为一特殊棋盘。在棋盘覆盖问题中,要用图示的4种不同形态的L型骨牌覆盖给定的特殊棋盘上除特殊方格以外的所有方格,且任何2个L型骨牌不得重叠覆盖。
解法:当k>0时,将2k×2k棋盘分割为4个2k-1×2k-1 子棋盘。
特殊方格必位于4个较小子棋盘之一中,其余3个子棋盘中无特殊方格。为了将这3个无特殊方格的子棋盘转化为特殊棋盘,可以用一个L型骨牌覆盖这3个较小棋盘的会合处,从而将原问题转化为4个较小规模的棋盘覆盖问题。递归地使用这种分割,直至棋盘简化为棋盘1×1。

void chessBoard(int tr, int tc, int dr, int dc, int size)   {      if (size == 1) return;      int t = tile++,  // L型骨牌号      int s = size/2;  // 分割棋盘      // 覆盖左上角子棋盘      if (dr < tr + s && dc < tc + s) // 特殊方格在此棋盘中         chessBoard(tr, tc, dr, dc, s);       else {// 此棋盘中无特殊方格         // 用 t 号L型骨牌覆盖右下角         board[tr + s - 1][tc + s - 1] = t;         // 覆盖其余方格         chessBoard(tr, tc, tr+s-1, tc+s-1, s);}      // 覆盖右上角子棋盘      if (dr < tr + s && dc >= tc + s)// 特殊方格在此棋盘中         chessBoard(tr, tc+s, dr, dc, s);       else {// 此棋盘中无特殊方格         // 用 t 号L型骨牌覆盖左下角              board[tr + s - 1][tc + s] = t;         // 覆盖其余方格         chessBoard(tr, tc+s, tr+s-1, tc+s, s);}        // 覆盖左下角子棋盘      if (dr >= tr + s && dc < tc + s)         // 特殊方格在此棋盘中         chessBoard(tr+s, tc, dr, dc, s);      else {// 用 t 号L型骨牌覆盖右上角         board[tr + s][tc + s - 1] = t;         // 覆盖其余方格         chessBoard(tr+s, tc, tr+s, tc+s-1, s);}      // 覆盖右下角子棋盘      if (dr >= tr + s && dc >= tc + s)         // 特殊方格在此棋盘中         chessBoard(tr+s, tc+s, dr, dc, s);      else {// 用 t 号L型骨牌覆盖左上角         board[tr + s][tc + s] = t;         // 覆盖其余方格         chessBoard(tr+s, tc+s, tr+s, tc+s, s);}   }

4.循环赛日程表:设计一个满足以下要求的比赛日程表:
(1)每个选手必须与其他n-1个选手各赛一次;
(2)每个选手一天只能赛一次;
(3)循环赛一共进行n-1天。
解法:按分治策略,将所有的选手分为两半,n个选手的比赛日程表就可以通过为n/2个选手设计的比赛日程表来决定。递归地用对选手进行分割,直到只剩下2个选手时,比赛日程表的制定就变得很简单。这时只要让这2个选手进行比赛就可以了。