分治算法——>分金块、求残缺棋盘问题

一、分治算法的介绍

将整个问题分解成若干个小问题后分而治之。如果分解得到的子问题相对来说还太大,则可反复使用分治策略将这些子问题分成更小的同类型子问题,直至产生出方便求解的子问题,必要时逐步合并这些子问题的解,从而得到问题的解。有时问题分解后,不必求解所有的子问题,也就不必作第三步的操作。比如折半查找,在判别出问题的解在某一个子问题中后,其它的子问题就不必求解了,问题的解就是最后(最小)的子问题的解。

二、用分治算法求解的相应例题

快速排序、归并排序、大整数乘法、残缺棋盘问题、线性时间的选择、最接近点对问题、汉诺塔问题等等

三、分治算法的设计步骤

  1. 分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;
  2. 解决:若子问题规模较小而容易被解决则直接解,否则再继续分解为更小的子问题,直到容易解决;
  3. 合并:将已求解的各个子问题的解,逐步合并为原问题的解。

四、分治算法的设计模式

分治法的一般递归算法设计模式如下:

 Divide-and-Conquer(int  n)   //n为问题规模
   
{      if(n≤n0)     //n0为可解子问题的规模
       {     
       		解子问题;
            return(子问题的解)}
       for (i=1 ;i<=k;i++)       //分解为较小子问题p1,p2,…pk          
			yi=Divide-and-Conquer(|Pi|);        //递归解决Pi      
   		T=MERGE(y1,y2,...,yk);                 //合并子问题 
       return(T);
   
}

五、典型例题一:分金块

1.问题描述

老板有一袋金块(共n块),最优秀的雇员得到其中最重的一块,最差的雇员得到其中最轻的一块。假设有一台比较重量的仪器,我们希望用最少的比较次数找出最重的金块。

2.思路分析

问题可简化为:在含n个元素的集合中寻找最大值和最小值。
用分治法(二分法)可用较少比较次数解决上述问题:
1)将数据等分为两组(两组数据可能差1),目的是分别选取其中的最大(小)值。
2)递归分解直到每组元素的个数≤2,可简单地找到最大(小)值。
3)回溯时,在分解的两组解中大者取大,小者取小,合并为当前问题的解。

3.伪码描述

float a[n];
maxmin (int  i, int j ,float &fmax, float &fmin)
{ 
    int mid;  
    float lmax, lmin, rmax, rmin;
    if (i=j)  
    {
        fmax= a[i];  
        fmin=a[i];
    }
    else if (i=j-1)
             if(a[i]<a[j])  
             { 
                 fmax=a[j];
                 fmin=a[i];
             }
             else 
             {
                 fmax=a[i]; 
                 fmin=a[j];
             }
          else 
          {  
              mid=(i+j)/2;                 
              maxmin (i,mid,lmax,lmin);                 
              maxmin (mid+1,j,rmax,rmin);                 
              if  (lmax>rmax)    
                   fmax=lmax;
              else   
                   fmax=rmax;                 
              if (lmin>rmin)     
                  fmin=rmin;
              else  fmin=lmin;} 
}

五、典型例题二: 残缺棋盘问题

1.问题描述

残缺棋盘是一个有2k×2k
(k≥1)个方格的棋盘,其中恰有一个方格残缺。
下图给出k=1时各种可能的残缺棋盘,其中残缺的方格用阴影表示。
在这里插入图片描述
这样的棋盘称作“三格板”,残缺棋盘问题就是用这四种三格板覆盖更大的残缺棋盘。在覆盖中要求:
1)两个三格板不能重叠
2)三格板不能覆盖残缺方格,但必须覆盖其他所有方格
在这种限制条件下,所需要的三格板总数为?
1)被覆盖板面积(除去残缺方格)为2k×2k-1
(k≥1) ;
2)三格板之间不能重叠;
3)三格板不能覆盖残缺方格,但必须覆盖其他所有方格。

2.思路分析

分而治之
在这里插入图片描述
当残缺方格在第1个子棋盘时,用①号三格板覆盖其余三个子棋盘的交界方格,可使另外三个子棋盘转化为独立子问题;
当残缺方格在第2个子棋盘时,则首先用②号三格板进行棋盘覆盖;
当残缺方格在第3个子棋盘时,则首先用③号三格板进行棋盘覆盖;
当残缺方格在第4个子棋盘时,则首先用④号三格板进行棋盘覆盖;
这样就使另外三个子棋盘转化为独立子问题。
递归地使用这种分割,直至棋盘简化为……,以方便解决。
a
直至棋盘简化为棋盘21×21;
在这里插入图片描述
解决:交汇处覆盖1块,其它子棋盘分别覆盖1块。
b 直至棋盘简化为棋盘1×1(20×20)
在这里插入图片描述
解决:交汇处覆盖1块,其它子棋盘无须处理。

3.伪码描述

int amount=0int Board[100][100];
main( )
{ 
   int size=1,x,y,k; 
   input(k);
   for (i=1;i<=k;i++)
      size=size*2; 
   //输入残缺块所在位置
   input(x,y); 
   Cover(0, 0, x, y, size); 
}
Cover(int tr, int tc, int dr, int dc, int size)
{  if (size<2)  return;
   int t = ++ amount ;// 使用三格板数目
   int s=size/2;        // 子问题棋盘大小
   if (dr<tr+s&&dc<tc+s)/ /情况一残缺方格位左上棋盘
   { Cover ( tr, tc, dr, dc, s); // 覆盖左上角棋盘
      Board[tr + s - 1][tc + s] = t;  // 覆盖1号三格板
      Board[tr + s][tc + s - 1] = t; 
      Board[tr + s][tc + s] = t;
      Cover (tr, tc+s, tr+s-1, tc+s, s); // 覆盖右上
      Cover(tr+s, tc, tr+s, tc+s-1, s);  // 覆盖左下
      Cover(tr+s, tc+s, tr+s, tc+s, s);  // 覆盖右下}
  if (dr<tr+s&&dc>=tc+s)//情况二残缺方格位右上棋盘
  {  Cover(tr, tc+s, dr, dc, s); // 覆盖左上角棋盘
      Board[tr+s-1][tc+s-1]=t;  // 覆盖2号三格板
      Board[tr+s][tc+s-1]=t; 
      Board[tr+s][tc+s]=t; 
      Cover(tr, tc, tr+s-1, tc+s-1, s);// 覆盖左上
      Cover(tr+s, tc, tr+s, tc+s-1, s); // 覆盖左下
      Cover(tr+s, tc+s, tr+s, tc+s, s); // 覆盖右下
 }
//情况三、残缺方格位于左下棋盘
 if (dr>=tr+s&&dc<tc+s)
{  Cover(tr+s, tc, dr, dc, s); // 覆盖3号 
    Board[tr+s-1][tc+s-1]=t; 
    Board[tr+s-1][tc+s]=t; 
    Board[tr+s][tc+s]=t; 
   Cover(tr, tc, tr+s-1, tc+s-1, s);
   Cover(tr, tc+s, tr+s-1, tc+s, s); 
   Cover(tr+s, tc+s, tr+s, tc+s, s); }
//情况四、残缺方格位于右下棋盘
 if (dr>=tr+s&&dc>=tc+s)
{  Cover(tr+s, tc+s, dr, dc, s); // 覆盖3号 
    Board[tr+s-1][tc+s-1]=t; 
    Board[tr+s-1][tc+s]=t; 
    Board[tr+s][tc+s-1]=t; 
   Cover(tr, tc, tr+s-1, tc+s-1, s);
   Cover(tr, tc+s, tr+s-1, tc+s, s); 
   Cover(tr+s, tc, tr+s, tc+s-1, s); }  }
Void OutputBoard(int size)
 { for(int i=0;i<size;i++)
     { for ( int j=0;j<size;j++)
           print(Board[i][j]);
        print(“换行符”);
      }
}
发布了28 篇原创文章 · 获赞 13 · 访问量 443

猜你喜欢

转载自blog.csdn.net/yo_u_niverse/article/details/105600111