Tromino谜题

Tromino 谜题 

问题描述:

Tromino是一个由棋盘上的三个邻接方块组成的L型瓦片。我们的问题是,如何用Tromino覆盖一个缺少了一个方块(可以在棋盘上的任何位置)的2^n*2^n棋盘。除了这个缺失的方块,Tromino应该覆盖棋盘上的所有方块,而且不能有重叠。


这也是一道很经典用分治法解决的题目,摘自《算法分析与设计基础》一书。

算法思想:

           可能我们一拿道题目就会思考要怎么放才能满足题目要求,甚至有亲自放一放的冲动,也许经过几次在纸上或者在大脑中的模拟摆放,我们可以得到这样一个规律对于一个4*4的棋盘只要把中间的三个棋盘块先摆上Tromino留出的一块空白是对应有缺块的2*2的区域(如下图),这个问题就变得很容易解答了

当然我们是否是可以把问题规模扩大或缩小再看是否同样可以解决如棋盘换成2*2或8*8,当然答案是肯定的,那么我们就知道2^n*2^n棋盘这个问题总可以解决,那么开始我们的划分之路,很显然分治法的思想是把一个大问题划分为数个同样结构的小问题再加以解决,然后合并。对于此题来说就是一个大棋盘要化成数个小棋盘,且每个小棋盘都要有一个缺块,说到这里我想一个划分方案依然出现在大家的脑海中了吧,那就是以大棋盘中心点为原点划分出等大的四个象限,每个象限为一个小棋盘,那小棋盘上的缺块呢?那就按上述解4*4的思想为没有缺块的小棋盘,分配一个缺块(缺块都是每个棋盘靠近中心点的那块,很明显这三个新划分的小缺块组成了Tromino),当然可能一次问题的划分并不能解决问题,那么我们可以对划分后的小棋盘在进行划分,使其变成更小的棋盘,直至划分成的棋盘到你所能轻松解决的大小,当然可以4*4,更简单的就是2*2了,此外对于分治法来说分开一方面,合并可能又是另一个问题,显然此题分开后并不需你做显示合并,我就在此提下这个问题。

算法伪代码:(声明:此算法我也自己写过思路和上面的算法思想不太相同,但经过比较后还是发现这个更加快速简单,易于理解,下面的算法转自百度百科)

//代码中原点指二维数组中心点,第一、二、三、四象限分指以原点建立坐标系的三,二,一,四象限

tromino(int **a,int x1,int y1,int length,int x,int y,int *flag)

//递归调用tromino来对棋盘(动态二维数组a[][])进行分割,后覆盖

//输入:一个动态二维数组的指针a、及棋盘左上角下标x1,y1、棋盘的长度length、缺块的下标x,y、及L型覆盖的块数的指针flag(当L覆盖棋盘上一个区域时会(*flag)++)

//输出:该函数返回类型为void,但会直接操作a所指向的二维数组,使二维数组最终被L型方格(L型方格用同一数字表示)覆盖

i=length/2

If  i-1==0

   If  缺块在第一象限

          二维数组的第二、三、四象限靠近原点的数组空间←(*flag)++

   else if 缺块在第二象限    

          二维数组的第一、三、四象限靠近原点的数组空间←(*flag)++

   else if 缺块在第三象限

          二维数组的第一、二、四象限靠近原点的数组空间←(*flag)++

   else

          二维数组的第一、二、三象限靠近原点的数组空间←(*flag)++

else

   If 缺块在第一象限   

          二维数组的第二、三、四象限靠近原点的数组空间←(*flag)++

          tromino(第一象限)//解第一象限

          tromino(第二象限)//解第二象限

          tromino(第三象限)//解第三象限

          tromino(第四象限)//解第四象限

   Else If 缺块在第二象限

          二维数组的第一、三、四象限靠近原点的数组空间←(*flag)++

          tromino(第一象限)//解第一象限

          tromino(第二象限)//解第二象限

          tromino(第三象限)//解第三象限

          tromino(第四象限)//解第四象限

  Else If 缺块在第三象限

          二维数组的第一、二、四象限靠近原点的数组空间←(*flag)++

          tromino(第一象限)//解第一象限

          tromino(第二象限)//解第二象限

          tromino(第三象限)//解第三象限

          tromino(第四象限)//解第四象限

  Else  缺块在第四象限

          二维数组的第一、二、三象限靠近原点的数组空间←(*flag)++

          tromino(第一象限)//解第一象限

          tromino(第二象限)//解第二象限

          tromino(第三象限)//解第三象限

          tromino(第四象限)//解第四象限

算法实现c++:(此算法摘自百度百科,我又加了些注释)

/*   

 a为二维数组名

 x1,y1为分块的左上角元素下标

 length为分块的大小

 x,y为缺块的下标   

      棋盘象限

2 | 3

一 一

1 | 4

*/

void tromino(int **a,int x1,int y1,int length,int x,int y,int *flag) { int i=length/2; if(i-1==0) //当棋盘的长度的一半为1时开始解决问题 { if(x<=x1+i-1&&y>y1+i-1) //缺块在第一象限 a[x1+i-1][y1+i-1]=a[x1+i][y1+i-1]=a[x1+i][y1+i]=(*flag)++; //分别为2,3,4 else if(x<=x1+i-1&&y<=y1+i-1) //缺块在第二象限 a[x1+i-1][y1+i]=a[x1+i][y1+i]=a[x1+i][y1+i-1]=(*flag)++; //分别为1,4,3 else if(x>x1+i-1&&y<=y1+i-1) //缺块在第三象限 a[x1+i-1][y1+i-1]=a[x1+i-1][y1+i]=a[x1+i][y1+i]=(*flag)++; //分别为2,1,4 else a[x1+i-1][y1+i-1]=a[x1+i-1][y1+i]=a[x1+i][y1+i-1]=(*flag)++;//分别为2,1,3 } else { if(x<=x1+i-1&&y>y1+i-1) //缺块在第一象限 { a[x1+i-1][y1+i-1]=a[x1+i][y1+i-1]=a[x1+i][y1+i]=(*flag)++; //分别为2,3,4 象限靠近棋盘中心点的3个棋盘格置1(组成L型),意为该象限棋盘的缺块 tromino(a,x1,y1+i,i,x,y,flag); //解第一象限 tromino(a,x1,y1,i,x1+i-1,y1+i-1,flag); //解第二象限 tromino(a,x1+i,y1,i,x1+i,y1+i-1,flag); //解第三象限 tromino(a,x1+i,y1+i,i,x1+i,y1+i,flag); //解第四象限 } else if(x<=x1+i-1&&y<=y1+i-1) //缺块在第二象限 { a[x1+i-1][y1+i]=a[x1+i][y1+i]=a[x1+i][y1+i-1]=(*flag)++; //分别为1,3,4 象限靠近棋盘中心点的3个棋盘格置1(组成L型),意为该象限棋盘的缺块 tromino(a,x1,y1+i,i,x1+i-1,y1+i,flag); //解第一象限 tromino(a,x1,y1,i,x,y,flag); //解第二象限 tromino(a,x1+i,y1,i,x1+i,y1+i-1,flag); //解第三象限 tromino(a,x1+i,y1+i,i,x1+i,y1+i,flag); //解第四象限 } else if(x>x1+i-1&&y<=y1+i-1) //缺块在第三象限 { a[x1+i-1][y1+i-1]=a[x1+i-1][y1+i]=a[x1+i][y1+i]=(*flag)++; tromino(a,x1,y1+i,i,x1+i-1,y1+i,flag); //解第一象限 tromino(a,x1,y1,i,x1+i-1,y1+i-1,flag); //解第二象限 tromino(a,x1+i,y1,i,x,y,flag); //解第三象限 tromino(a,x1+i,y1+i,i,x1+i,y1+i,flag); //解第四象限 } else //缺块在第四象限 { a[x1+i-1][y1+i-1]=a[x1+i-1][y1+i]=a[x1+i][y1+i-1]=(*flag)++;//分别为2,1,3 tromino(a,x1,y1+i,i,x1+i-1,y1+i,flag); //解第一象限 tromino(a,x1,y1,i,x1+i-1,y1+i-1,flag); //解第二象限 tromino(a,x1+i,y1,i,x1+i,y1+i-1,flag); //解第三象限 tromino(a,x1+i,y1+i,i,x,y,flag); //解第四象限 } } }
欢迎大家批评指正。

ps:解法2(姑且称为解法2吧,仅作我当时想法的记录,切勿当真)

算法思想:

        这种解法也同样是从小问题着手,但是是自底而上的解决问题,我们知道棋盘可以分成同等大小同等类型的4块,也就意味着这样一个大问题可以分成4个小问题,解决一个小问题也就相当于解决了问题的1/4,然后4个小问题又可以分成8个更多小的问题,当然还可以继续分下去,但我们知道当问题变成一个在2X2按一个缺块的棋盘(如图3)上覆盖L型时,这个问题便非常好解决。那么我们同样可以知道这样一个大问题按上述分法总可以分成个图3这样的小问题,那么每个这样的小问题的解决,便意味着这个大问题的解决。这个算法的执行过程是这样的先将棋盘按照上述分法一一划分出来,并不断记录原缺块在划分后的棋盘中的象限,最终定这个含有缺块最的小棋盘(如图3)在上一被划分后棋盘中的象限,并依此在其余3个象限中设置缺块并使这三个缺块组成L型然后再调用解决(图3)这个简单问题的算法依次解决。

算法伪代码:

/代码中原点指二维数组中心点,第一、二、三、四象限分指以原点建立坐标系的二,三,一,四象限

resolve_tromino(int**chessboard,size_t start_x,size_t end_x,size_t start_y,size_t end_y,size_t misspoint_x,size_t misspoint_y,int i,int *acount,size_t length)

//递归调用resolve_tromino来对棋盘(动态二维数组a[][])进行分割,确定含有缺块最的小棋盘(如图3)在上一被划分后棋盘中的象限,后调用解决图3问题的算法依次解决

//输入:一个动态二维数组的指针a、及棋盘横坐标的起始start_x,终止end_x、棋盘纵坐标起始start_y,终止end_y、缺块的下标misspoint_x,misspoint_y、该缺块在被划分后的棋盘上的第i象限、及L型覆盖的块数的指针acount(当L覆盖棋盘上一个区域时会(*acount)++)、棋盘的长度length

//输出:该函数返回类型为void,但会直接操作a所指向的二维数组,使二维数组最终被L型方格(L型方格用同一数字表示)覆盖

If 棋盘长度和宽度>2

      If 缺块在第一象限

         Resolve_tromino(第一象限)

      Else if 缺块在第二象限

         Resolve_tromino(第二象限)

      Else if  缺块在第三象限

         Resolve_tromino(第三象限)

      Else   

         Resolve_tromino(第四象限)

If 棋盘长度和宽度length

     If i=1

           If 第二、三、四象限靠近原点的数组空间=0

                 第二、三、四象限靠近原点的数组空间←(*acount)++

                 Resolve_tromino(第二象限)

                 Resolve_tromino(第三象限)

                 Resolve_tromino(第四象限)

   

    If i=2

         If 第一、三、四象限靠近原点的数组空间=0

               第一、三、四象限靠近原点的数组空间←(*acount)++

               Resolve_tromino(第一象限)

               Resolve_tromino(第三象限)

               Resolve_tromino(第四象限)

    If i=3

         If 第一、二、四象限靠近原点的数组空间=0

               第一、二、四象限靠近原点的数组空间←(*acount)++

               Resolve_tromino(第一象限)

               Resolve_tromino(第二象限)

               Resolve_tromino(第四象限)

    If i=4

           If 第一、二、三象限靠近原点的数组空间=0

                 第一、二、三象限靠近原点的数组空间←(*acount)++

                 Resolve_tromino(第一象限)

                 Resolve_tromino(第二象限)

                 Resolve_tromino(第三象限)

If 棋盘长宽=2

      棋盘非缺块←(*acount)++

算法实现c++:

   /*
	    resolve_tromino()函数参数

        size_t start_x  棋盘横轴的起始坐标
		size_t end_x    棋盘横轴的终止坐标
		size_t start_y  棋盘纵轴的起始坐标
		size_t end_y    棋盘纵轴的终止坐标
		size_t misspoint_x  缺块横轴坐标
		size_t misspoint_y  缺块纵轴坐标
		int i               缺块在当前棋盘被分成4个象限中处于第i象限
		int *acount         Tromino块数 从2开始

	    棋盘象限
		1 | 3
		一 一
		2 | 4
	*/
void resolve_tromino(int **chessboard,size_t start_x,size_t end_x,size_t start_y,size_t end_y,size_t misspoint_x,size_t misspoint_y,int i,int *acount,size_t length)
{

	if(end_x-start_x+1>2&&end_y-start_y+1>2)//当棋盘长度大于2时划分成4个象限作为4个小棋盘
	{
		if(misspoint_x<=(end_x-start_x-1)/2+start_x && misspoint_y<=(end_y-start_y-1)/2+start_y)   //一
			resolve_tromino(chessboard,start_x,(end_x-start_x-1)/2+start_x,start_y,(end_y-start_y-1)/2+start_y,misspoint_x,misspoint_y,1,acount,length);

		else if(misspoint_x<=(end_x-start_x-1)/2+start_x && misspoint_y>=(end_y-start_y+1)/2+start_y) //二
			resolve_tromino(chessboard,start_x,(end_x-start_x-1)/2+start_x,(end_y-start_y+1)/2+start_y,end_y,misspoint_x,misspoint_y,2,acount,length);

		else if(misspoint_x>=(end_x-start_x+1)/2+start_x && misspoint_y<=(end_y-start_y-1)/2+start_y) //三
			resolve_tromino(chessboard,(end_x-start_x+1)/2+start_x,end_x,start_y,(end_y-start_y-1)/2+start_y,misspoint_x,misspoint_y,3,acount,length);

		else if(misspoint_x>=(end_x-start_x+1)/2+start_x && misspoint_y>=(end_y-start_y+1)/2+start_y) //四
			resolve_tromino(chessboard,(end_x-start_x+1)/2+start_x,end_x,(end_y-start_y+1)/2+start_y,end_y,misspoint_x,misspoint_y,4,acount,length);
	}
	
	if((end_x-start_x+1)!=length &&(end_y-start_y+1)!=length)//当棋盘不是第一块棋盘时(不是原始问题时)对棋盘进行分象限处理
	{
		switch(i)
		{
		case 1:  //缺块所属第一象限
			if(chessboard[end_y][end_x+1]==0&&chessboard[end_y+1][end_x]==0&&chessboard[end_y+1][end_x+1]==0)//判断棋盘出缺块所在象限其他三个象限靠近棋盘中心点的三个棋盘方格是否被覆盖
			{
			    //覆盖这三个方格
			chessboard[end_y][end_x+1]=*acount;
			chessboard[end_y+1][end_x]=*acount;
			chessboard[end_y+1][end_x+1]=*acount;
			(*acount)++;

			//以其他三个象限作为带缺块的棋盘再次求解

			//第三
			//misspoint_y=end_y;
			//misspoint_x=end_x+1;
			resolve_tromino(chessboard,end_x+1,end_x+end_x-start_x+1,start_y,end_y,end_x+1,end_y,3,acount,length);
			//第二
			//misspoint_y=end_y+1;
			//misspoint_x=end_x;
			resolve_tromino(chessboard,start_x,end_x,end_y+1,end_y+end_y-start_y+1,end_x,end_y+1,2,acount,length);
			//第四
			//misspoint_y=end_y+1;
			//misspoint_x=end_x+1;
			resolve_tromino(chessboard,end_x+1,end_x+end_x-start_x+1,end_y+1,end_y+end_y-start_y+1,end_x+1,end_y+1,4,acount,length);
			}break;

			case 2://缺块所属第二象限
			if(chessboard[start_y-1][end_x]==0&&chessboard[start_y-1][end_x+1]==0&&chessboard[start_y][end_x+1]==0)
			{
			chessboard[start_y-1][end_x]=*acount;
			chessboard[start_y-1][end_x+1]=*acount;
			chessboard[start_y][end_x+1]=*acount;
			(*acount)++;

			//以其他三个象限作为带缺块的棋盘再次求解

			//第一
			//misspoint_y=start_y-1;
			//misspoint_x=end_x;
			resolve_tromino(chessboard,start_x,end_x,start_y-(end_y-start_y+1),start_y-1,end_x,start_y-1,1,acount,length);
			//第三
			//misspoint_y=end_y+1;
			//misspoint_x=end_x;
			resolve_tromino(chessboard,end_x+1,end_x+end_x-start_x+1,start_y-(end_y-start_y+1),start_y-1,end_x,end_y+1,3,acount,length);
			//第四
			//misspoint_y=start_y;
			//misspoint_x=end_x+1;
			resolve_tromino(chessboard,end_x+1,end_x+end_x-start_x+1,start_y,end_y,end_x+1,start_y,4,acount,length);
			}break;
			
			case 3://缺块所属第三象限
			if(chessboard[end_y][start_x-1]==0&&chessboard[end_y+1][start_x-1]==0&&chessboard[end_y+1][start_x]==0)
			{
			chessboard[end_y][start_x-1]=*acount;
			chessboard[end_y+1][start_x-1]=*acount;
			chessboard[end_y+1][start_x]=*acount;
			(*acount)++;

			//以其他三个象限作为带缺块的棋盘再次求解

			//第一
			//misspoint_y=end_y;
			//misspoint_x=start_x-1;
			resolve_tromino(chessboard,start_x-(end_x-start_x+1),start_x-1,start_y,end_y,start_x-1,end_y,1,acount,length);
			//第二
			//misspoint_y=end_y+1;
			//misspoint_x=start_x-1;
			resolve_tromino(chessboard,start_x-(end_x-start_x+1),start_x-1,end_y+1,end_y+(end_y-start_y+1),start_x-1,end_y+1,2,acount,length);
			//第四
			//misspoint_y=end_y+1;
			//misspoint_x=start_x;
			resolve_tromino(chessboard,start_x,end_x,end_y+1,end_y+end_y-start_y+1,start_x,end_y+1,4,acount,length);
			}break;

			case 4://缺块所属第四象限
			if(chessboard[start_y-1][start_x]==0&&chessboard[start_y-1][start_x-1]==0&&chessboard[start_y][start_x-1]==0)
			{
			chessboard[start_y-1][start_x]=*acount;
			chessboard[start_y-1][start_x-1]=*acount;
			chessboard[start_y][start_x-1]=*acount;
			(*acount)++;

			//以其他三个象限作为带缺块的棋盘再次求解

			//第一
			//misspoint_y=start_y-1;
			//misspoint_x=start_x-1;
			resolve_tromino(chessboard,start_x-(end_x-start_x+1),start_x-1,start_y-(end_y-start_y+1),start_y-1,start_x-1,start_y-1,1,acount,length);
			//第二
			//misspoint_y=start_y;
			//misspoint_x=start_x-1;
			resolve_tromino(chessboard,start_x-(end_x-start_x+1),start_x-1,start_y,end_y,start_x-1,start_y,2,acount,length);
			//第三
			//misspoint_y=start_y-1;
			//misspoint_x=start_x;
			resolve_tromino(chessboard,start_x,end_x,start_y-(end_y-start_y+1),start_y-1,start_x,start_y-1,3,acount,length);
			}break;


		}
	
		
	}
     if((end_x-start_x+1)==2 &&(end_y-start_y+1)==2)//如果棋盘长度化为2开始求解
	 {
		 //将长度为2的棋盘未为缺块的地方覆盖上Tromino
		for(size_t i=start_y;i<=end_y;i++)
		{
			for(size_t j=start_x;j<=end_x;j++)
			{
				if(chessboard[i][j]==0)
				{
					chessboard[i][j]=*acount;
					
				}
			}
		}
		(*acount)++;
	 }
		
}




你可能感兴趣的:(算法,算法,百度,c)