关灯游戏 Lights out (二)(首行枚举+位运算,搜索全部解)

         关灯游戏的简介就不介绍了,还不了解的朋友可点击笔者的上一篇博文----关灯游戏 Lights out (一)(极速求解)

         在这一篇给大家介绍“关灯游戏”的枚举算法。

         枚举算法很简单,就是枚举所有开关操作,然后逐一检索经过各种操作后局面的最终状态,当灯全灭时输出解。

         对一个m×n的灯阵,并不需要枚举整个局面2^(m×n)种开关组合。因为任意一种操作,首行的操作就决定了后面所有行的操作都是唯一确定的,不存在其它可能。

      原因很简单,因为能影响第一行灯的亮/灭状态,只可能是第二行的操作组合,并且能让第一行灯全灭的操作是唯一的;接下来,能影响第二行灯的亮/灭状态,只可能是第三行的操作组合,并且能让第二行灯全灭的操作是唯一的...依此类推。所以,当第一行的开关操作确定了,为了使得每一行的灯全灭,后面所有行的开关操作也都确定了。

         请看下面图示例子:

关灯游戏 Lights out (二)(首行枚举+位运算,搜索全部解)_第1张图片

         一个5×5的灯阵,假设一开始全部灯全亮,如上图所示。

关灯游戏 Lights out (二)(首行枚举+位运算,搜索全部解)_第2张图片

        假如某一种开关操作,它的第一行操作如上图所示(1代表对该灯开关操作一次,下同)。

关灯游戏 Lights out (二)(首行枚举+位运算,搜索全部解)_第3张图片

 第一行开关操作完毕,局面灯的亮/灭状态如上图所示。

       此时我们看到,第一行两端盏两盏灯灭了,中间三盏灯还是亮的。为了使第一行灯全灭,接下来对第二行必须对中间三盏灯开关操作一次,并且不可以操作第二行两端两盏灯,第二行的操作是唯一确定的。(对以后所有行的操作依此类推)

关灯游戏 Lights out (二)(首行枚举+位运算,搜索全部解)_第4张图片

操作完第二行后,局面灯的亮灭状态如上图所示。

关灯游戏 Lights out (二)(首行枚举+位运算,搜索全部解)_第5张图片

操作完第三行后,局面灯的亮灭状态如上图所示。

关灯游戏 Lights out (二)(首行枚举+位运算,搜索全部解)_第6张图片

 操作完第四行后,局面灯的亮灭状态如上图所示。

关灯游戏 Lights out (二)(首行枚举+位运算,搜索全部解)_第7张图片

操作完第五行后,局面灯的亮灭状态如上图所示。

        当所有行都操作完毕后,检查一下局面,如果局面的灯全灭,则输出该解。如果局面还有灯亮,说明从一开始,第一行的操作就是错的。

        上面图示例子,到最后局面的灯全灭了,表明我们找到了解法,就是最后一个图标出的操作组合。

        通过上面分析,枚举时,只需要枚举第一行所有的操作组合,对一个m行n列灯阵,枚举量为2^n。时间复杂度是O(2^n),猛一看上去似乎这种算法效率太差。其实不然,因为开/关操作和灯的亮/灭可以对应计算机的二进制数据,这样一来,整个过程可以用位运算来完成,速度极快,求解20×20的灯阵是毫秒内的事情,应付游戏娱乐足够了。

     枚举算法非常简单,易于编程实现,求解小关卡速度也很快,所以枚举算法是个不错的选择,没有必要动不动就用线性代数算法求解。

     当第一行的开/关操作确定下来后,后面所有行的操作,都必须确保前一行的灯全灭,那么最后检查局面灯是否全灭时,只需要检查最后一行灯是否全灭即可。

     所有行开关操作完毕时,如果局面灯全灭,输出该解。

     枚举完第一行2^n种不同操作,如果每种操作,最后都无法使得灯全灭,则说明初始局面无解。

        灯亮用1表示,灯灭用0表示;对任意一盏灯,有开关操作用1表示,没有操作用0表示(开关操作偶数次等价于0次,开关操作奇数次等价于1次)。

     m行n列灯阵,可以用数组Lights[m]来表示灯的亮/灭状态,数组中的每一个数据表示一整行灯的亮/灭状态;用数组press[m]来表示开关操作,数组中的每一个数据表示一整行的开关操作情况。为了读入初始化局面,再增加一个initializtion[m]数组记录初始化灯阵的亮/灭状态。以上三个数组的数据,二进制长度都是n位。

      编程很简单,核心代码不过十行左右。下面是首行枚举+位运算完整C++代码( 程序默认初始状态的灯全亮,搜索目标状态为灯全灭;可以读入初始化局面,自动判断有没有解,如果有解,将输出所有解):

#include
using namespace std;
long Row,Column,guide,statistic=0;

void specification()
{
	int i;
	cout<<"\n                       关灯游戏(Lights out)解题程序\n\n";
	cout<<"    本程序用于求解智力难题----“关灯游戏”,可以求解任意初始局面的灯阵,默认初始";
	cout<<"状态灯全亮,求解的目标状态是灯全灭。\n";
	cout<<"    解题开始前要设置行数和列数。行数不限,但列数受数值位数限制,不可以大于30。如";
	cout<<"果列数不等于行数,那么最好设置行数大于列数,因为本程序行数没有限制,并且,求解一";
	cout<<"个30行2列的局面,远远快于2行30列的局面。\n";
	cout<<"    程序允许选择手动输入自定义初始布局。\n";
	cout<<"    程序会自动判断局面有无解,如果有解,将给出所有解!\n\n";
	for(i=0; i<80; i++)cout<<"=";
}

int choose(long *initializtion)
{
	long i,j,opening,check=1;
	char C;
	cout<<"请选择,是否输入自定义初始局面?  (Y/N):";
	cin>>C;
	if((C=='Y')||(C=='y'))
	{
		cout<<"\n请输入初始局面,1代表亮灯,0代表灭灯。灯之间用空格隔开,输完一行后用回车换行。\n";
		cout<<"例如,输入一个2行3列,并且有两盏亮灯的初始局面为:\n0 0 1\n0 1 0\n";
		for(i=0; i<80; i++)cout<<"-";
		cout<<"\n";
		for(i=0; i>opening;
				initializtion[i]<<=1;
				initializtion[i]+=opening;
			}
			check|=initializtion[i];
		}
		if(check==0)                                       //检查用户输入的局面,如果灯已经全灭,则程序结束
			cout<<"\n灯已经全灭。\n";
	}
	return check;
}

void solve(long *lights,long *press,long *initializtion)
{
	long i,j,L,R,permutation,temp;
	for(permutation=0; permutation>1;
			lights[i]^=R;
		}
		if(lights[Row]==0)                                  //检查局面,如果灯全灭,则输出解
		{
			cout<<"\n\n";
			statistic++;                                //解数量计数器
			for(i=1; i<=Row; i++)                       //输出解
			{
				temp=guide>>1;
				for(j=0; j>=1;
				}
				cout<>Row;
	cout<<"请输入列数:";
	cin>>Column;
	long *lights=new long [Row+2];           //lights[]用于保存灯的局面状态
	long *press=new long [Row+2];            //press[]用于保存按灯位置
	long *initializtion=new long[Row+2];     //initializtion[]用于保存用户输入初始化局面
	if(Column>30||Row*Column==0)             //这里限制用户输入列不可以大于30,或者行数和列数不可以等于0
	{
		if(Column>30)cout<<"\n列数不可超过30\n";
		goto end;
	}
	guide=1<

       上面程序,因为输入输出以及各种判断比较繁琐,导致代码量巨大。笔者曾打算为程序写一个图形界面,但考虑到这样使用起来也不是很复杂,而写GUI会很麻烦,于是作罢。

       下面这是笔者用上面程序得到的统计表(独立解和最少操作次数的统计,需要对程序进行改动)

关灯游戏 Lights out (二)(首行枚举+位运算,搜索全部解)_第8张图片

你可能感兴趣的:(益智游戏问题)