一、分治算法的介绍
将整个问题分解成若干个小问题后分而治之。如果分解得到的子问题相对来说还太大,则可反复使用分治策略将这些子问题分成更小的同类型子问题,直至产生出方便求解的子问题,必要时逐步合并这些子问题的解,从而得到问题的解。有时问题分解后,不必求解所有的子问题,也就不必作第三步的操作。比如折半查找,在判别出问题的解在某一个子问题中后,其它的子问题就不必求解了,问题的解就是最后(最小)的子问题的解。
二、用分治算法求解的相应例题
快速排序、归并排序、大整数乘法、残缺棋盘问题、线性时间的选择、最接近点对问题、汉诺塔问题等等
三、分治算法的设计步骤
- 分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;
- 解决:若子问题规模较小而容易被解决则直接解,否则再继续分解为更小的子问题,直到容易解决;
- 合并:将已求解的各个子问题的解,逐步合并为原问题的解。
四、分治算法的设计模式
分治法的一般递归算法设计模式如下:
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=0;
int 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(“换行符”);
}
}