来看,Futoshiki数独游戏的代码实现

本文介绍了如何通过编程实现Futoshiki数独游戏,包括设计数据结构存储文本内容、解析文件、判断矩阵完整性和有效性,以及使用递归和回溯寻找可能的解。同时讨论了内存释放的问题。
摘要由CSDN通过智能技术生成

一、什么是Futoshiki数独游戏

首先,我们需要了解Futoshiki数独游戏的规则,如下图所示。

说白了这个游戏就是通过代码实现,将一个带有大小逻辑关系的数独正确填充,这种数独不同于普通的数独游戏,还需要确保符合每一行每一列的数字大小关系。

二、根据该数独游戏来设计文本

对于该数独游戏来讲,我们需要设计一个文本来显示我们需要填充的数独,此时设计出以下两个未被填充完整的文本来进行Futoshiki数独游戏,并取好名字6.txt,8.txt以便后续可以读取该文本。

三、设计数据结构

为了解决这个游戏问题,我们首先需要把上述设计的文本内容转换成代码可实现的形式,这时我们就需要设计数据结构来存储这个文本内容,我们可以有多种方式来存储,动态数组,双重指针等,我采用的方法是用双重指针来存储文本内容。

现在创建一个结构体来储存数独中每一元素的左关系(该元素与左边元素的大小关系),上关系(该元素与上面一个元素的大小关系),由此可知,判断关系是否正确时需要从第二行或者第二列开始。

typedef struct
{
	int value;//该元素的值 
	char leftRel;//与左边元素的关系
	char upRel;//与上面元素的关系
	int rowRepeat;//判断行元素是否重复 
	int colRepeat;//判断列元素是否重复 
}Matrix;

 接着用双指针声明变量,该变量用来存储文本内容。

Matrix** matrix = NULL;

接着,再设计一个结构体来保存数独游戏的多种结果,以链表的形式读取结果。

typedef struct result
{
    Matrix** matrix;
    struct result* next;
} Result;

同样用指针声明变量 ,这些变量分别用来存储填充的内容和显示数独游戏多种结果。

Result* results = NULL, *res1 = NULL;

 四、设计函数

设计完基本的数据结构,就该设计函数来解决数独游戏问题。

下面就是整个数独游戏解决的主函数,包含着整体思路。

int main()
{
	int i = 0, N = 0;
	Result* results = NULL, *res1 = NULL;
    int total = 0;
	
	char filename[100];
	cout<<"请输入读取的文件名:";
	cin>>filename;
	
	Matrix** matrix = NULL;
	matrix = read_file(filename,&N);
	//print_matrix(matrix,N); 
	if(matrix == NULL)
	{
		complete = 0;
		valid = 0;
	}
	else 
	{
		cout<<"读取文件中的矩阵如下:"<<endl;
		print_matrix(matrix,N); 
		IsValid(matrix,N);
		Matrix** backup = NULL;
		backup = copymatrix(matrix,N);

		if (complete)
		{
			cout<<"这是一个完整的矩阵!";
			if (valid)
				cout<<"而且是一个有效的矩阵."<<endl;
			else
				cout<<"但这不是一个有效的矩阵."<<endl;
		}
		else
		{
			cout<<"这不是一个完整的矩阵!";
			if (valid)
			{
				if (solve_matrix(matrix,N,0))
				{
					cout<<"填充的矩阵如下:"<<endl;
					//print_matrix(matrix,N);
					matrix = copymatrix(backup, N);//将原本读取到的文件内容赋值回来 
	
	            // 对结果分配头结点
		            results = new Result;//分配内存 
		            results->next = NULL;
		            results->matrix = NULL;
		
		            solve_matrix_2(matrix, N, 0, results, &total);
	
	            // 将结果输出
		            res1 = results->next;
		            while(res1 != NULL)
		            {
		                print_matrix(res1->matrix, N);
		                res1 = res1->next;
		            }	
	            	cout<<"所能填充的情况为:"<<total<<endl;
				}
				else
				 	cout<<"无法填充该矩阵!"<<endl;					
			}
			else 
				cout<<"无法填充该矩阵!"<<endl;
		}
	}
	free_matrix(matrix, N);
	free_result(results,N);	
}

 这个函数主要用于读取所设计的文本内容到双重指针中去,主要步骤为利用嵌套循环分别读取数独中的左关系和上关系。

Matrix** read_file(char* filename,int* N)//读取文件内容 
{
	Matrix** matrix = NULL;
	int n = 0, i, j, value = 0;
	char a[10];//定义一个字符后续保存读取文件字符的值
	int b;// 定义一个整型后续保存读取文件整数的值
	
	//开始读取文件 
	ifstream file;
	file.open(filename);
	if(!file.is_open())
	{
		cout<<"文件不存在!";
		return NULL;
	} 
	file>>n;
	file>>n;
	*N = n;
	matrix = initial_matrix(matrix,n);//初始化二维数组 
	//print_matrix(matrix,n);
	
	//接下来就可以读取文件中的内容了  
	for( i = 0 ;i < n ; i++)
	{
		if( i > 0 )//读取文本文件中的上下关系 
		{
			for (j = 0; j < n; j++)
            {
                file>>a;
                matrix[i][j].upRel = a[0];//读取文件中的关系 ,-,<,> 
                //cout<<a<<endl;
                if (j < n - 1)
                {
                    file.ignore(2); // 读取一次大小关系便跳过中间的字符-
                }
            }          
		}
		for( j = 0; j < n; j++)//读取文本文件中的左右关系 
		{
			if( j > 0 )
			{
				file>>a;
				matrix[i][j].leftRel = a[0];
			}
			file>>b;
			matrix[i][j].value = b;				
		} 
	}
	file.close(); 
	//print_matrix(matrix,n);//将读取文本文件后的内容打印 
	return matrix;
}

其中还涉及到一个初始化函数,将所有元素的关系都初始化。

Matrix** initial_matrix(Matrix** matrix, int n)
{
	int i, j;
	matrix = new Matrix*[n];
	if (matrix == NULL)
    {
        cerr << "Memory error!" << endl;
   		exit(1);
    	return 0;
    }//判断分配内存是否成功 
	for(i = 0 ; i < n ; i++)
    {
    	matrix[i] = new Matrix[n];//对每一个一维数组开辟空间
		if (matrix[i] == NULL)
        {
            cerr << "Memory error!" << endl;
   			exit(1);
    		return 0;
        }//判断分配行内存是否成功	 
        for (j = 0; j < n; ++j)
        {
            matrix[i][j].value     = 0;
            matrix[i][j].leftRel = '|';
            matrix[i][j].upRel   = '-';
            matrix[i][j].rowRepeat = 0;
            matrix[i][j].colRepeat = 0;
        }//整个二维数组 martix被成功地分配并初始化。每个元素都有默认的初始值
	}
	//print_matrix(matrix, n);
	return matrix;
}

 接着,这两个函数尤为重要,用来判断所填充的数字是否正确有效。

int check_matrix(Matrix** matrix, int row, int col,int n)//主要作用就是判断已有的数字合不合理放在此处 
{
	int value = matrix[row][col].value;
	char relation;
	if (col > 0 && matrix[row][col - 1].value != 0)
    {
        relation = matrix[row][col].leftRel; // 获取左边的关系符
        if (matrix[row][col - 1].value > value && relation == '<') // 不用检测=的情况,因为每行数据不存在重复数
        {
            return 0;
        }//relation为小于号,如果左边的数据大于右边的数据则是错误的 
        if (matrix[row][col - 1].value < value && relation == '>'  )
        {
            return 0;
        }//同理relation为大于号,如果左边的数据小于右边的数据则是错误的 
    }
    if (row > 0 && matrix[row - 1][col].value != 0) 
    {
    	relation = matrix[row][col].upRel; // 获取上面的关系符
        if (matrix[row - 1][col].value > value && relation == '^') // 不用检测=的情况,因为每行数据不存在重复数
        {
            return 0;
        }//relation为小于号,如果左边的数据大于右边的数据则是错误的 
        if (matrix[row - 1][col].value < value && relation == 'v'  )
        {
            return 0;
        }//同理relation为大于号,如果左边的数据小于右边的数据则是错误的 
	}
	if(row < (n - 1) && row > 0 && matrix[row][col].upRel == '^' && matrix[row+1][col].upRel == 'v' && matrix[row+1][col].value == 2)
	{
		return 0;//如果碰到<2>这种情况则该数独肯定是无效的 
	}
	if(col < (n - 1) && col > 0 && matrix[row][col].leftRel == '<' && matrix[row][col+1].leftRel == '>' && matrix[row+1][col].value == 2 )
	{
		return 0;//如果2的上下关系是^v这种情况则该数独也肯定是无效的 
	}
	if(row < (n - 1) && row > 0 && matrix[row][col].upRel == 'v' && matrix[row+1][col].upRel == '^' && matrix[row+1][col].value == n - 1)
	{
		return 0;//如果碰到> n - 1 <这种情况则该数独肯定是无效的 
	}
	if(col < (n - 1) && col > 0 && matrix[row][col].leftRel == '>' && matrix[row][col+1].leftRel == '<' && matrix[row+1][col].value == n - 1)
	{
		return 0;//如果n - 1是v^这种情况则该数独也肯定是无效的 
	}
	return 1;
}
int IsValid(Matrix** matrix,int n)//判断矩阵是否完整,完整的是否有效 
{
	for( int i = 0; i < n; i++)
	{
		for( int j = 0; j < n; j++)
		{
			if ( matrix[i][j].value == 0)
    		{
        		complete = 0;
    		}
	    	else if ( matrix[i][j].value < 0 || matrix[i][j].value > n)
		    {
		        valid = 0;
		    }
		    else // val在1到n之间
		    {
		        if (matrix[i][matrix[i][j].value - 1].rowRepeat++) // value是数字几就让其对应其实际列数,让其等于1,若后续还有1,则代表重复了,												
		        {									
	            	valid = 0;
	        	}	//用于检测同一行中是否存在重复数,若存在,则该方案失效
	
		        if (matrix[matrix[i][j].value - 1][j].colRepeat++)//value是数字几就让其对应其实际行数,让其等于1,若后续还有1,则代表重复了,  
		        {
		            valid = 0;
		        }	// 用于检测同一列中是否存在重复数,若存在,则该方案失效
		
		        if (check_matrix(matrix, i, j, n) == 0)//判断你输入进来的数据是不是对的,有没有满足本身的限制条件,调用check_relations函数,21行 
		        {
		            valid = 0;
		        }
			}
		}
	}	
}

 最后,到了最重要的函数设计,根据主函数的步骤solve_matrix()用来判断一个数独游戏是否能进行正确的填充,如果可以正确的填充那我们就运行solve_matrix_2()函数来找出该数独游戏的所有结果。该函数利用到的是递归和回溯,通过不断地判断数独的正确填充来找出数独的正确结果和多种可能。

int solve_matrix(Matrix** matrix,int n,int m)
{
    int i = 0, j = 0, k = 0, rowUsed = 0, colUsed = 0;

    i = m / n;
    j = m % n;

    if (m >= n * n) // 终止
    {
        return 1;
    }

    if (matrix[i][j].value) // 如果当前值不为0
    {
        if (check_matrix(matrix, i, j, n)) // 如果检测成功
        {
            return solve_matrix(matrix, n, m + 1); // 递归调用,继续检测下一个元素
        }
        else
        {
            // 检测不成功,返回失败
            return 0;
        }
    }

    // 如果当前值为0
    for (k = 1; k <= n; ++k)
    {
        matrix[i][j].value = k;
        rowUsed = matrix[i][k - 1].rowRepeat++;
        colUsed = matrix[k - 1][j].colRepeat++;

        if (rowUsed == 0 && colUsed == 0 && check_matrix(matrix, i, j, n) == 1) // 如果行和列都不存在重复数,并且关系合法
        {   
            if (solve_matrix(matrix, n, m + 1)) // 继续检测下一个,如果合法返回1
            {
                return 1;
            }
        }

        // 将当前k退出,继续检测下一个k
        matrix[i][j].value = 0;
        --matrix[i][k - 1].rowRepeat;
        --matrix[k - 1][j].colRepeat;
    }
    return 0;
}
int solve_matrix_2(Matrix** matrix, int n, int step, Result* results, int* total)
{
    int i = 0, j = 0, k = 0, rowUsed = 0, colUsed = 0;
    Result* tmp = NULL;

    i = step / n;
    j = step % n;

    if (step >= n * n)
    {
        ++*total;

        // 将结果记录
        tmp = new Result;//分配内存 
        if (tmp == NULL)//判断是否分配成功 
        {
            cerr<<"Memory error!"<<endl;
        	exit(1);
        	return 0;
        }

        // 拷贝结果
        tmp->next = results->next;
        tmp->matrix = copymatrix(matrix, n);
        results->next = tmp;

        return 1;
    }

    if (matrix[i][j].value != 0)
    {
        if (check_matrix(matrix, i, j, n))
        {
            return solve_matrix_2(matrix, n, step + 1, results, total);
        }
        else
        {
            return 0;
        }
    }

    for (k = 1; k <= n; ++k)
    {
        matrix[i][j].value = k;
        rowUsed = matrix[i][k - 1].rowRepeat++;
        colUsed = matrix[k - 1][j].colRepeat++;

        if (rowUsed == 0 && colUsed == 0 && check_matrix(matrix, i, j, n))
        {
            solve_matrix_2(matrix, n, step + 1, results, total);
        }
        
		// 将当前k退出,继续检测下一个k
        matrix[i][j].value = 0;
        --matrix[i][k - 1].rowRepeat;
        --matrix[k - 1][j].colRepeat;
    }

    // 如果k>n则检测失败
    return 0;
}

五、结果展示

可以看到以下的两种结果,如果该数独只有一种填充方式那么就只展示出一种来,如果该数独的填充方式有多种,就将所有的情况都列出来。

 

六、疑惑

为何通过该种方式无法正确的将双重指针和指针的内存完全释放。

void free_matrix(Matrix** matrix, int n)
{
    if (matrix == NULL)
    {
        return;
    }

    for (int i = 0; i < n; ++i)
    {
        delete[] matrix[i];
    }
    delete[] matrix;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值