首页 > 学院 > 开发设计 > 正文

关灯游戏 Lights out (三)(线性代数+高斯消元,搜索全部解)

2019-11-11 06:35:29
字体:
来源:转载
供稿:网友

        关灯游戏和线性代数联系紧密,对于一个 的灯阵,用线性方程组+高斯消元法求解,时间复杂度为O(m×n)^3。相对于首行枚举算法复杂度O(2^n) ,线代算法的时间复杂度低很多。用线性代数求解关灯游戏是个很不错的选择。

        本文用最通俗的语言介绍线代求解关灯游戏原理。由于解释通俗化,细节之处难以严谨表述,说得不好的地方,请大神轻拍。

线性方程组求解关灯游戏的原理

问题:

        一个2×2灯阵,初始局面右下方3盏灯亮,左上角一盏灯不亮,如下图所示。请用线性代数求解,找到可以使得所有灯都熄灭的开关操作。

解:

 %20 %20 %20 %20我们对四盏灯的开/关操作分别记为%20x1、x2%20、x3%20、x4%20,如下图所示:

       我们用1代表操作,用0代表未操作,即 的取值为0或1。对四盏灯的亮/灭状态分别记为 L1、L2 、L3 、L4 ,如下图所示:

       我们用1代表亮着的灯,用0代表熄灭的灯,根据局面的初始状态,很显然有

接下来观察局面,

能影响L1灯的亮/灭状态的操作只有 x1、x2 、x3 ;

能影响L2灯的亮/灭状态的操作只有 x1、x2 、x4 ;

能影响L3灯的亮/灭状态的操作只有 x1、x3 、x4 ;

能影响L4灯的亮/灭状态的操作只有 x2、x3 、x4 ;

定理1:对同一盏灯开/关操作偶数次,等于操作0次;对同一盏灯操作奇数次,等于操作1次。

所以我们将 x1、x2、x3、x4 及其系取值限定在0或者1两个数值,以后不再说明(不怕蛋疼的话,可以对操作次数不限定,这样一来将有无穷多个解,这些解都是在基础解上对每盏灯再重复操作偶数次,本质上相同)。

        假如一开始所有灯都熄灭,并且假设将 x1、x2、x3、x4 全体操作一遍后,灯阵的亮/灭状态为当前状态。根据定理1,那么将 x1、x2、x3、x4 全体操作一遍,灯阵就又回到全熄灭状态。x1、x2、x3、x4 就是我们想要的答案,于是列方程组:

       游戏问题转换成了求解方程组问题。上面方程组不难求解,用初中的知识足够了。有一点要注意,方程组是在二元域{0,1}内求解,也就是运算过程中,所有数值的取值范围都只能是0或者1。二元域运算规则和常规运算规则有所不同,具体如下(不要问我二元域运算规则为什么是这样的,要问就问什么是灯?或者灯为什么会亮?之类的问题):

加法:0+0=0,0+1=1,1+0=1,1+1=0

减法:0-0=0,0-1=1,1-0=1,1-1=0

乘法:0×0=0,0×1=0,1×0=0,1×1=1

除法:0/0=无意义,0/1=0,1/0=无意义,1/1=1

        注:有限域上的运算符号并不是上面那个样子,而是一些圈、叉之类的奇怪符号。这里一方面不方便打出这些符号,另一方面也为了让读者便于理解,所以仍旧采用“+、-、×、/”形式。我们看到,加减法都等价于二进制位运算中的“亦或”运算;乘法等价于二进制位运算中的“与”运算;除法在这里用不到,直接无视。

方程组求解结果为:

        这是方程组唯一的解,所以这个游戏局面解法是唯一的,标记在游戏局面见下图:

        到这里,线代原理就介绍完毕了。怎么样,线代解法原理很简单吧,无非就是求解线性方程组问题。而计算机求解线性方程组,我们在增广矩阵中用高斯消元法,时间复杂度为O(m×n)^3,判断游戏有没有解,可以用矩阵的秩来判断方程组有没有解,有多少个解。

用线性代数搜索所有解法的C/C++代码如下:

#include<iostream>using namespace std;int Row,Column,MAXN;void init(int **matrix)                                            //初始化系数矩阵{	int i,j,k;	for(i=0; i<Row; i++)		for(j=0; j<Column; j++)		{			k=i*Column+j;			matrix[k][k]=1;			if(i>0)matrix[(i-1)*Column+j][k]=1;			if(i<Row-1)matrix[(i+1)*Column+j][k]=1;			if(j>0)matrix[i*Column+j-1][k]=1;			if(j<Column-1)matrix[i*Column+j+1][k]=1;		}}void  Gauss(int **matrix,int *solution)                            //高斯消元{	int i,j,k,x,y=0,rand_coefficient,rand_matrix,rank1,rank2,inc=0;	for(k=0; k<MAXN&&y<MAXN; k++,y++)                          //增广矩阵转换为阶梯矩阵	{		x=k;		for(i=k+1; i<MAXN; i++)			if(matrix[i][y]>matrix[x][y])x=i;		if(x-k)                                            //交换矩阵行数据			for(j=y; j<MAXN+1; j++)			{				matrix[k][j]=matrix[k][j]^matrix[x][j];				matrix[x][j]=matrix[k][j]^matrix[x][j];				matrix[k][j]=matrix[k][j]^matrix[x][j];			}		if(matrix[k][y]==0)		{			k--;			continue;		}		for(i=k+1; i<MAXN; i++)                             //消元			if(matrix[i][y])				for(j=y; j<MAXN+1; j++)matrix[i][j]^=matrix[k][j];	}	for(rand_coefficient=rand_matrix=i=0; i<MAXN; i++)          //计算系数矩阵的秩和增广矩阵的秩	{		for(rank1=rank2=j=0; j<=MAXN; j++)		{			if(j<MAXN)rank1|=matrix[i][j];			rank2|=matrix[i][j];		}		rand_coefficient+=rank1;		rand_matrix+=rank2;	}	if(rand_coefficient<rand_matrix)                            //系数矩阵秩小于增广矩阵秩,局面无解		cout<<"此局面无解!/n/n";	else                                                        //枚举并回代求解	{		for(k=1<<(MAXN-rand_coefficient); k>0; k--)		{			for(i=MAXN-1; i>rand_coefficient; i--)			{				matrix[i-1][MAXN]+=matrix[i][MAXN]>>1;				matrix[i][MAXN]&=1;			}			for(i=0; i<MAXN; i++)solution[i]=matrix[i][MAXN];			for(i=MAXN-1; i>=0; i--)                    //回代				for(j=i-1; j>=0; j--)					if(matrix[j][i])solution[j]^=solution[i];			for(i=0; i<MAXN; i++)                       //输出答案				(i%Column)?cout<<solution[i]<<" ":cout<<endl<<solution[i]<<" ";			inc++;                                      //解数量计数器			PRintf("/n");			matrix[MAXN-1][MAXN]++;		}		cout<<"/n共计"<<inc<<"个解/n";	}}int main(void){	int i,j;	char C;	cout<<"/n                    关灯游戏(Lights out)解题程序(线代解法)/n";	cout<<"请输入行数:";	cin>>Row;	cout<<"请输入列数:";	cin>>Column;	MAXN=Column*Row;	int **matrix=new int*[MAXN+4];                            //增广矩阵,二维数组	for(i=0; i<MAXN+4; i++)matrix[i]=new int [MAXN+4];	int *solution=new int[MAXN+2];                            //解数组	for(i=0; i<MAXN+4; i++)		for(j=0; j<MAXN+4; j++)matrix[i][j]=0;	init(matrix);                                             //初始化系数矩阵	cout<<"请选择,是否输入指定初始局面?(Y/N):",cin>>C;	if((C=='Y')||(C=='y'))	{		cout<<"请输入初始局面(1代表亮灯,0代表灭灯,灯与灯之间用空格隔开,换行用回车):/n";		for(i=0; i<MAXN; i++)cin>>matrix[i][MAXN];	}	else for(i=0; i<MAXN; i++)matrix[i][MAXN]=1;              //默认初始化局面为灯全亮  	Gauss(matrix,solution);	for(int i=0; i<MAXN+4; i++)delete []matrix[i];	delete []matrix;	delete []solution;	cout<<"/n/n求解完毕,请按任意键退出程序。";	cin.ignore(),cin.ignore();                                //暂停显示	return 0;}    上面代码可以搜索任意局面的全部解,允许用户输入任意初始局面,已经非常方便使用了。如果还觉得使用上面代码太麻烦,笔者将其制作成软件,截图如下:

软件在这里下载:关灯游戏解题程序1.0版 https://pan.baidu.com/s/1geNMxcZ


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表