算法设计与分析_铺地砖
问题
有一个游泳池底,划分成了 2 n × 2 n 2^n \times 2^n 2n×2n(其中 n n n是正整数)的网格,每一小格都是正方形,其中有一小格是排水口。
下图展示了当 n = 4 n=4 n=4时,在坐标 ( 11 , 11 ) (11,11) (11,11)上有一个排水口的情景。
现允许用4种形状(如果考虑旋转和反转,实质上是同一种)的地砖进行铺设,要求排水口不能被覆盖,其他小格恰被一块地砖覆盖。
请问任务能否完成,为什么。
如果能完成,请设计算法实现上述要求的覆盖。
问题分析与论证过程
(如果能完成,请给出严格的证明。如果不能完成,请举反例。)
问题分析
铺砖问题分治递归的算法可以通过以下步骤进行分析:
1.定义问题:铺砖问题是一个将给定的游泳池区域铺满砖块的问题。游泳池区域被分成若干个2x2的方格,每3个方格可以选择铺一块砖。其中有一块排水口,不用铺砖,目标是找到一种铺砖方式,使得整个游泳池区域被完全铺满。
2.设计算法:具体铺砖的方式可以分为普通铺砖和中心铺砖两种,普通铺砖即2x2方格中有一个1,其余为0 (用1选择方式去铺剩余3个0),中心铺砖2x2方格中有一个2其他为1 (用2选择方式取铺剩余3个1)即分别用函数puzh()和zhxin()表示。设计函数zxhf()来划分中心区域,确定实际排水口位置,并将该区域分成四个相同的子区域,并在这四个相同的区域分别设置一个伪排水口。使用递归的方式,将铺砖问题不断分解成子问题,直到最小范围的子问题可以直接处理。铺砖过程以遍历每一块砖为方法,若遍历到为1的方格,采用普通铺砖去铺2x2方格中剩余的3个0;遍历到为2的方格,采用中心铺砖,去铺2x2剩余的3个1。注意特殊情况,即真排水口在某一层中心点上的情况,原本为1,因为是中心四块方格也是距离排水口最近的中心方格成为了2 。这时除了要采用中心铺砖,去铺一个2x2区域剩余的3个1,还要采用普通铺砖用2去铺另一个区域剩下的3个0;
3.分析复杂度:铺砖问题的复杂度主要取决于游泳池区域的大小,假设游泳池区域的边长为n。
在划分中心区域时,需要计算每个中心方格与排水口的距离,这个计算时间复杂度为O(1)。
递归的过程中,问题被分解为四个子问题,每个子问题的规模为原问题的四分之一,因此递归的时间复杂度为O(log n)。
综上所述,铺砖问题分治递归算法的总体时间复杂度为O(n^2 log n),空间复杂度为O(n^2)(用来存储游泳池区域的二维数组)。
论证过程
接下来以8x8的方格为例。证明其实现过程:
以下是一个8x8个方格的游泳池,其中设(2,6)为排水口。
1.初始状态,剩余63个方格为0,(2,6)方格为1。
2.找到8x8方格的四个中心方格,离真排水口最近的设置为2,其余三个方格设置为1,即当作递归后四个区域的伪排水口。
3.以8/2=4为方格宽度,分治划分为四个4x4方格的小区域,并以四个区域内的数字1为排水口执行在四个4x4的方格中执行与第二步相同的操作。注意右上角部分的橙色二,此时出现了问题分析中出现的特殊情况:即真排水口在某一层中心点上的情况,原本为1,因为其也是中心四块方格,也是距离排水口最近的中心方格成为了2 。
4.重复执行2、3步直至已经分治为了全部为2x2的方格。(本例此时已经成为了2x2的方格)
5.从第一块方格开始遍历 ,若遍历到值为1的方格,通过判断值为它的方格在2x2的区域的位置,去为2x2区域中剩下的3个方格挑选合适的砖型铺砖,即普通铺砖;若遍历值为2的方格,通过判断值为它的方格在2x2的区域的位置,去为2x2区域中剩下的3个方格(每层中心位置的四个方格)挑选合适的砖型铺砖,即中心铺砖;面对上述出现的特殊情况,如本例上图所示,当遍历到橙色2时,其既要执行中心铺砖去铺其所对左下角的三个方格,也要执行普通铺砖,通过判断值为它的方格在2x2的区域的位置,去为左上角剩下的三个方格挑选合适的砖型铺砖。铺砖完成后效果图如下:
6.如上图所示,对于该问题的算法分析,是可以实现本题要求的。
算法设计
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
int num[1027][1027]={0};//二维数组用来表示游泳池方格
void puzh(int x,int y);//普通铺砖:2*2方格中有一个1,其余为0 (用1铺0)
void zhxin(int x,int y);//中心铺砖:2*2方格中有一个2其他为1 (用2铺1)
void zxhf(int num[1027][1027],int zx,int zy,int x,int y,int width);//中心划分
void huafen(int num[1027][1027],int ax,int ay,int x,int y,int width);//总划分
void puzh(int x,int y)//普通铺砖:2*2方格中有一个1,其余为0 (用1铺0)
{ if (x % 2 == 0 && y % 2 == 0)//1在2*2的右下角其他为0选方式一铺砖
{
printf("1 %d %d %d %d %d %d\n", x - 1, y - 1, x - 1, y, x, y - 1);
}
if (x % 2 == 0 && y % 2 != 0)//1在2*2的左下角其他为0选方式二铺砖
{
printf("2 %d %d %d %d %d %d\n", x - 1, y + 1, x - 1, y, x, y + 1);
}
if (x % 2 != 0 && y % 2 != 0)//1在2*2的左上角其他为0选方式三铺砖
{
printf("3 %d %d %d %d %d %d\n", x + 1, y + 1, x + 1, y, x, y + 1);
}
if (x % 2 != 0 && y % 2 == 0)//1在2*2的右上角其他为0选方式四铺砖
{
printf("4 %d %d %d %d %d %d\n", x + 1, y - 1, x + 1, y, x, y - 1);
}
}
void zhxin(int x, int y)//中心铺砖:2*2方格中有一个2其他为1 (用2铺1)
{
if (x % 2 != 0 && y % 2 != 0)//2在每层中心2*2的右下角其他为1选方式一铺砖
{
printf("1 %d %d %d %d %d %d\n", x - 1, y - 1, x - 1, y, x, y - 1);
}
if (x % 2 != 0 && y % 2 == 0)//2在每层中心2*2的左下角其他为1选方式二铺砖
{
printf("2 %d %d %d %d %d %d\n", x - 1, y + 1, x - 1, y, x, y + 1);
}
if (x % 2 == 0 && y % 2 == 0)//2在每层中心2*2的左上角其他为1选方式三铺砖
{
printf("3 %d %d %d %d %d %d\n", x + 1, y + 1, x + 1, y, x, y + 1);
}
if (x % 2 == 0 && y % 2 != 0)//2在每层中心2*2的右上角其他为1选方式四铺砖
{
printf("4 %d %d %d %d %d %d\n", x + 1, y - 1, x + 1, y, x, y - 1);
}
}
void zxhf(int num[1027][1027],int zx,int zy,int x,int y,int width)// 中心划分
{ float dis1,dis2,dis3,dis4;
//以下计算中心四块方格与排水口的距离
dis1=sqrt(pow(zx-x,2)+pow(zy-y,2)); //中心左上角与排水口距离
dis2=sqrt(pow(zx-x,2)+pow((zy+1)-y,2));//中心右上角与排水口距离
dis3=sqrt(pow((zx+1)-x,2)+pow(zy-y,2));//中心左下角与排水口距离
dis4=sqrt(pow((zx+1)-x,2)+pow((zy+1)-y,2));//中心右下角与排水口距离
//以下为若中心左上角方格距离排水口最近,中心左上角方格设置为2,表示为排水口在这一部分
//其他中心三块方格设置为1,作为伪排水口,以便分治的四部分面对的问题相同
//然后分支递归huafen函数,将原来的大区域划分为四个相同的小区域
if((dis1<dis2)&&(dis1<dis3)&&(dis1<dis4))
{
num[zx][zy]=2;//中心左上角设置为2
num[zx][zy+1]=1;//中心右上角设置为1
num[zx+1][zy]=1;//中心左下角设置为1
num[zx+1][zy+1]=1;//中心右下角设置为1
width=width/2;//宽度除2
huafen(num,zx,zy,x,y,width);//以(x,y)为排水口,递归左上部分,(zx,zy)为其右下角方格坐标
huafen(num,zx,zy+width,zx,zy+1,width);//以(zx,zy+1)为排水口,递归右上部分,(zx,zy+width)为其右下角方格坐标
huafen(num,zx+width,zy,zx+1,zy,width);//以(zx+1,zy)为排水口,递归左下部分,(zx+width,zy)为其右下角方格坐标
huafen(num,zx+width,zy+width,zx+1,zy+1,width);//以(zx+1,zy+1)为排水口,递归右下部分,(zx+width,zy+width)为其右下角方格坐标
}
//以下为若中心右上角方格距离排水口最近,中心右上角方格设置为2,表示为排水口在这一部分
//其他中心三块方格设置为1,作为伪排水口,以便分治的四部分面对的问题相同
//然后分支递归huafen函数,将原来的大区域划分为四个相同的小区域
if((dis2<dis1)&&(dis2<dis3)&&(dis2<dis4))
{
num[zx][zy]=1;//中心左上角设置为1
num[zx][zy+1]=2;//中心右上角设置为2
num[zx+1][zy]=1;//中心左下角设置为1
num[zx+1][zy+1]=1;//中心右下角设置为1
width=width/2;//宽度除2
huafen(num,zx,zy,zx,zy,width);//以(zx,zy)为排水口,递归左上部分,(zx,zy)为其右下角方格坐标
huafen(num,zx,zy+width,x,y,width);//以(x,y)为排水口,递归右上部分,(zx,zy+width)为其右下角方格坐标
huafen(num,zx+width,zy,zx+1,zy,width);//以(zx+1,zy)为排水口,递归左下部分,(zx+width,zy)为其右下角方格坐标
huafen(num,zx+width,zy+width,zx+1,zy+1,width);//以(zx+1,zy+1)为排水口,递归右下部分,(zx+width,zy+width)为其右下角方格坐标
}
//以下为若中心左下角方格距离排水口最近,中心左下角方格设置为2,表示为排水口在这一部分
//其他中心三块方格设置为1,作为伪排水口,以便分治的四部分面对的问题相同
//然后分支递归huafen函数,将原来的大区域划分为四个相同的小区域
if((dis3<dis1)&&(dis3<dis2)&&(dis3<dis4))
{
num[zx][zy]=1;//中心左上角设置为1
num[zx][zy+1]=1;//中心右上角设置为1
num[zx+1][zy]=2;//中心左下角设置为2
num[zx+1][zy+1]=1;//中心右下角设置为1
width=width/2;//宽度除2
huafen(num,zx,zy,zx,zy,width);//以(zx,zy)为排水口,递归左上部分,(zx,zy)为其右下角方格坐标
huafen(num,zx,zy+width,zx,zy+1,width);//以(zx,zy+1)为排水口,递归右上部分,(zx,zy+width)为其右下角方格坐标
huafen(num,zx+width,zy,x,y,width);//以(x,y)为排水口,递归左下部分,(zx+width,zy)为其右下角方格坐标
huafen(num,zx+width,zy+width,zx+1,zy+1,width);//以(zx+1,zy+1)为排水口,递归右下部分,(zx+width,zy+width)为其右下角方格坐标
}
//以下为若中心右下角方格距离排水口最近,中心右下角方格设置为2,表示为排水口在这一部分
//其他中心三块方格设置为1,作为伪排水口,以便分治的四部分面对的问题相同
//然后分支递归huafen函数,将原来的大区域划分为四个相同的小区域
if((dis4<dis1)&&(dis4<dis2)&&(dis4<dis3))
{
num[zx][zy]=1;//中心左上角设置为1
num[zx][zy+1]=1;//中心右上角设置为1
num[zx+1][zy]=1;//中心左下角设置为1
num[zx+1][zy+1]=2;//中心右下角设置为2
width=width/2;//宽度除2
huafen(num,zx,zy,zx,zy,width);//以(zx,zy)为排水口,递归左上部分,(zx,zy)为其右下角方格坐标
huafen(num,zx,zy+width,zx,zy+1,width);//以(zx,zy+1)为排水口,递归右上部分,(zx,zy+width)为其右下角方格坐标
huafen(num,zx+width,zy,zx+1,zy,width);//以(zx+1,zy)为排水口,递归左下部分,(zx+width,zy)为其右下角方格坐标
huafen(num,zx+width,zy+width,x,y,width);//以(x,y)为排水口,递归右下部分,(zx+width,zy+width)为其右下角方格坐标
}
}
void huafen(int num[1027][1027],int ax,int ay,int x,int y,int width)//总划分
{
if(width/2==1)//当划分为2*2的问题时停止,因为2*2的格子已经只能容纳一块砖
return;
else
{
int zx=(2*ax-width)/2;//中心点横坐标
int zy=(2*ay-width)/2;//中心点纵坐标
zxhf(num,zx,zy,x,y,width);//通过中心划分,从中心开始将原来的大区域划分为等同的四个小区域
}
}
int main()
{
int n,x,y;//n:有2的n次方*2的n次方个方格,(x,y):真排水口坐标
scanf("%d%d%d",&n,&x,&y);
int gnum=pow(2,n);//gnum:每行或每列的方格数
for(int i=1;i<=gnum;i++)
for(int j=1;j<=gnum;j++)
num[i][j]=0;//需要铺地砖的方格二维数组中初始为0,表示未铺砖。
num[x][y]=1;//排水口初始化为1,表示不需要铺砖
int ax=gnum,ay=gnum;//最右下角地砖坐标
int width=gnum;//当前宽度(方格每行多少个)
huafen(num,ax,ay,x,y,width); //进入划分函数,分治递归将方格最终划分为2*2的小问题
//以下为铺砖过程
for(int i=1;i<=gnum;i++)
{for(int j=1;j<=gnum;j++)
{ if(num[i][j]==1)//2*2方格中有一个1,其余为0 (用1铺0)
{
puzh(i,j);}
if(num[i][j]==2)//2*2方格中有一个2其他为1 (用2铺1)
{ zhxin(i,j);
if(i==x&&j==y)//真排水口在某一层中心点上的情况,原本为1,因为其也是距离排水口最近的中心方格成为了2
puzh(i,j);
}
}
}
return 0;
}