全排列方法
- 通过next_permutation函数生成1-9的全排列。
- 将每个全排列数转换为3*3的方阵
- 判断每个元素是否与所给的缺少方阵一样,一样则继续进行,不一样直接continue退出本次的全排列
- 在上一步元素匹配的情况下,进行是否是幻方判断。
完成统计工作
代码如下:
#include <algorithm>
#include <bits/stdc++.h>
using namespace std;
//
int p[9];//随机生产排列数
int ans[3][3];//存储幻方结果
int a[3][3];//读入题目给的样例方
int b[3][3];//全排列元素
//数据输入初始化
void input_martix(int (&b)[3][3], int (&p)[9])//传入数组的引用
{
int a;
cout<<"please input your data 9 numbers"<<endl;
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
{
cin>>a;
b[i][j]=a;
}
cout<<"9 numbers has been input "<<endl;
//输入P矩阵,便于参加全排列
for(int i=0;i<9;i++)
p[i]=i+1;
}
//输出数组 验证一下
void ouput_martix(int (&b)[3][3],int (&p)[9])//传入数组的引用
{
int a;
cout<<"the follow is the 9 numbers"<<endl;
for(int i=0;i<3;i++)
{
for(int j=0;j<3;j++)
{
cout<< b[i][j]<<" ";
}
cout<<" "<<endl;
}
cout<<"P martix is "<<endl;
for(int i=0;i<9;i++)
cout<<p[i]<<" "<<endl;
}
signed main()
{
input_martix(a,p);
ouput_martix(a,p);
int cnt=0;
//将全排列数组转换为矩阵
do {
for(int i=0;i<3;i++)
{
for(int j=0;j<3;j++)
{
b[i][j]=p[i*3+j];
}
}
//判断生成的矩阵与题目所给的矩阵匹配与否
bool flag=true;
for(int i=0;i<3;i++)
{
for(int j=0;j<3;j++)
{
if(!a[i][j])
{continue;}
if(a[i][j]!=b[i][j])//如果匹配上了就将标志位设置为true
flag = false;
//只要存在一个数不匹配就设置为false,且直接break退出
}
}
//cout<<"flag is "<<flag;
if(!flag)//如果返回的是false,则不可能存在匹配的幻方
continue;
bool ok= true;//判断是否是幻方的标志位
int sum = b[0][0]+b[1][1]+b[2][2];
//否则就有可能成为幻方,开始进行是否是幻方的判断
if(sum !=b[0][2]+b[1][1]+b[2][0])//次对角线是否不相等
continue;
//对比行和 与 列和是否匹配
for(int i=0;i<3;i++)
{
int tem1=0,tem2=0;
for(int j=0;j<3;j++)
{
tem1+=b[i][j];tem2 += b[j][i];
}
if(tem1!= sum || tem2 != sum)
ok=false;
}
if(!ok) continue;
cnt ++;
if(cnt>=2) return cout<<"Too Many"<<endl,0;
for(int i=0;i<3;i++) for(int j=0;j<3;j++) ans[i][j]=b[i][j];//用ans 记录那个幻方
}while (next_permutation(p,p+9));
//程序到这里没有结束,则说明最少有一个幻方
cout<<"--------------------"<<endl;
for(int i=0;i<3;i++) for(int j=0;j<3;j++)
{
//cout<<ans[i][j]<<"\n"[j==3];
cout<<ans[i][j]<<" \n";}//用ans 记录那个幻方
return 0;
}
深度优先搜索方法
本意要求补全1——9的数字,使其填到对应位置上是否是形成幻方。本质就是对剩下的数字进行全排列。给的矩阵是
0 7 2
0 5 0
0 3 0
就是1,4,6,8,9还没有,只需要将1,4,6,8,9等数字填到原来的0 的位置,最后判断其是否是一个幻方即可。
那我们就对其全排列到对应位置。比如1,4,6,8,9 填到缺损位置 1,4,6,9,8填到缺损位置
补全之后然后判断是不是幻方即可完成任务。
(1)判断一个矩阵是不是幻方
bool check(int t[3][3])
{
int sum = t[0][0]+t[1][1]+t[2][2];
if(sum !=t[0][2]+t[1][1]+t[2][0])//次对角线是否不相等
return false;
for(int i=0;i<3;i++)
{
int tem1=0,tem2=0;
for(int j=0;j<3;j++)
{
tem1+=t[i][j];tem2 += t[j][i];
}
if(tem1!= sum || tem2 != sum)
return false;
}
return true;
}
(2)递归+回溯算法
回溯算法需要的参数可不像二叉树递归的时候那么容易一次性确定下来,所以一般是先写逻辑,然后需要什么参数,就填什么参数。
1,首先必须要有终止条件
本题中,我们知道我们就是求取1,4,6,8,9几个数的排列问题,将对应的数放在为0的位置上即可。
因此,我们就需要把所有的排列顺序算出来,再依次放到对应的位置上。
为了实现这个目标,我们需要将为0的位置记录下来,方便将排列放到对应的位置上。
为此,我们定义一个pair类型的数据
记录为0数据的位置,即x,y坐标
pair<int int> p[9]
在输入的时候,我们就记住这些为0的位置
、、
输入数据
、、
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
{
cin>>a[i][j];
if(!a[i][j])//如果给的元素是0的
p[++n]=make_pair(i, j);//记录这个为0的元素x,y
use[a[i][j]] = 1;//把元素输入矩阵里面有元素的地方标志为使用了
}
输入完毕之后,我们知道了p[1~5]5个数存入了为0的x,y数据。
use数组里面,a[0]以及a[不为0的地方]=1,即a[2,3,5,7]都为1,记录使用过的数据
完成上诉之后,我们开始进行回溯算法的求解
int x=p[num].first,y=p[num].second;//每次num增加的时候,取对应的下标
for(int k=1;k<=9;k++)
{
if(used[k]) continue;//如果数据已经使用过了,则不能再使用
a[x][y]=k;
used[k]=1;//表示该元素已经被使用了
dfs( num+1, used);
//回溯,完成一轮之后,把原来的位置给回归到上一步的样子
a[x][y]=0;
used[k]=0;
}
、、
说明:使用n来控制回溯的深度,因为有5个数据没有填,即n为5,num>5时候表示已经求出一个排列可能了
DFS算法的全部代码如下:
#include <algorithm>
#include <bits/stdc++.h>
#include <iostream>
#include <utility>
using namespace std;
int vis[10],a[3][3],ans[3][3];
int n=0;
int cnt=0;
pair<int,int> p[9];//存输入矩阵为0的下标
int use[10]={0};
//判断是否是幻方的矩阵
bool check(int t[3][3])
{
int sum = t[0][0]+t[1][1]+t[2][2];
if(sum !=t[0][2]+t[1][1]+t[2][0])//次对角线是否不相等
return false;
for(int i=0;i<3;i++)
{
int tem1=0,tem2=0;
for(int j=0;j<3;j++)
{
tem1+=t[i][j];tem2 += t[j][i];
}
if(tem1!= sum || tem2 != sum)
return false;
}
return true;
}
//DFS算法
void dfs(int num,int (&used)[10])
{
if(num>n)//结束条件,表示幻方的缺失元素已经补全了
{
if(check(a))//如果是幻方
{
cnt++;
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
ans[i][j] = a[i][j];//将结果传给ans数组
}
return ;
}
//
int x=p[num].first,y=p[num].second;//每次num增加的时候,取对应的下标
for(int k=1;k<=9;k++)
{
if(used[k]) continue;//如果数据已经使用过了,则不能再使用
a[x][y]=k;
used[k]=1;//表示该元素已经被使用了
dfs( num+1, used);
//回溯
a[x][y]=0;
used[k]=0;
}
}
signed main()
{
cout<<"please input your data 9 numbers"<<endl;
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
{
cin>>a[i][j];
if(!a[i][j])//如果给的元素是非0的
p[++n]=make_pair(i, j);
use[a[i][j]] = 1;//把元素输入矩阵里面有元素的地方标志为使用了
}
cout<<"------";
dfs(1, use);
if (cnt>0)
cout<<"you huangfang"<<cnt;
else
cout<<"meiyou huangfang";
return 0 ;
}
-----------------2024年4月2号补,下面是对于这道题的接续思考-------------------------------
突然翻到了这道题,记得之前做过,今天又来做一下,从新复习一下DFS算法。
思路:找出幻方中没有使用的数,把他全排列填到对应的位置上,并判断是不是幻方即可。
为了方便,数独就先初始化一下:
int shudu[3][3]={
{0,7,2},
{0,5,0},
{0,3,0}
};//存储数独元素
std::vector<std::pair<int ,int> > tem;//存入原来数独里面是0的元素下标
int used[10]={0};//表示该数据是否用过
初始化一下
void Init()
{
// cout<<"---初始化完的数据是-- "<<endl;
tem.clear();
for(int i =0;i<3;++i)
for(int j =0;j<3;++j)
{
if(shudu[i][j]==0)
{
tem.push_back(std::make_pair(i,j));//记录每个0元素的下标
}
else
{
used[shudu[i][j]] = 1; //记录已经被使用的数据
}
}
}
检查是否是幻方
bool check(int s[3][3])
{
int sum= s[0][0]+s[0][1]+s[0][2];
//对角线是否相等
if(sum != s[0][0]+s[1][1]+s[2][2] || sum != s[0][2]+s[1][1]+s[2][0] )
return false;
for(int j=0;j<3;++j)
{
auto temp_row = s[j][0]+s[j][1]+s[j][2];//行和
auto temp_clo = s[0][j]+s[1][j]+s[2][j];//列和
if(sum != temp_clo || sum != temp_row )
{
return false;
}
}
return true;
}
DFS求全排列
void dfs(int n , int used[10],int s[3][3])
{
//递归的停止条件
if(path.size() == n) //找打了一组全排列
{
for(int i=0;i<n;++i)
{
s[tem[i].first][tem[i].second] = path[i]; //依次填到位置上
}
// cout << "path_size= "<<path.size()<<endl;
if ( check(s) )//如果是幻方
{
count1=count1+1;
cout<<"所求得的幻方是:"<<endl;
for(int i =0;i<3;++i)
{
for(int j =0;j<3;++j)
{
cout<< s[i][j]<<" ";
}
cout<<endl;
}
}
return ;
}
//开是递归和回溯过程
for(int i=1;i<=9;++i)
{
if(used[i] != 0) continue;//这个数值已经用过了
path.push_back(i);
used[i]=1;
dfs(n,used,s);
path.pop_back();
//回溯
used[i]=0;
}
}
全部代码
//数独问题
#include<bits/stdc++.h>
using namespace std;
int count1 =0;
int shudu[3][3]={
{0,7,2},
{0,5,0},
{0,3,0}
};//存储数独元素
std::vector<std::pair<int ,int> > tem;//存入原来数独里面是0的元素下标
std::vector<int> path;//存储每一轮的结果
int used[10]={0};//表示该数据是否用过
bool check(int s[3][3])
{
int sum= s[0][0]+s[0][1]+s[0][2];
//对角线是否相等
if(sum != s[0][0]+s[1][1]+s[2][2] || sum != s[0][2]+s[1][1]+s[2][0] )
return false;
for(int j=0;j<3;++j)
{
auto temp_row = s[j][0]+s[j][1]+s[j][2];//行和
auto temp_clo = s[0][j]+s[1][j]+s[2][j];//列和
if(sum != temp_clo || sum != temp_row )
{
return false;
}
}
return true;
}
void Init()
{
// cout<<"---初始化完的数据是-- "<<endl;
tem.clear();
for(int i =0;i<3;++i)
for(int j =0;j<3;++j)
{
// cout<<"hello world"<<endl;
if(shudu[i][j]==0)
{
tem.push_back(std::make_pair(i,j));//记录每个0元素的下标
}
else
{
used[shudu[i][j]] = 1; //记录已经被使用的数据
}
}
// for(int i :used)
// cout<<" used的值 "<<i<< " " ;
//
}
void dfs(int n , int used[10],int s[3][3])
{
//递归的停止条件
if(path.size() == n)
{
for(int i=0;i<n;++i)
{
s[tem[i].first][tem[i].second] = path[i];
}
// cout << "path_size= "<<path.size()<<endl;
if ( check(s) )//如果是幻方
{
count1=count1+1;
cout<<"所求得的幻方是:"<<endl;
for(int i =0;i<3;++i)
{
for(int j =0;j<3;++j)
{
cout<< s[i][j]<<" ";
}
cout<<endl;
}
}
return ;
}
//开是递归和回溯过程
for(int i=1;i<=9;++i)
{
if(used[i] != 0) continue;//这个数值已经用过了
path.push_back(i);
used[i]=1;
dfs(n,used,s);
path.pop_back();
//回溯
used[i]=0;
}
}
signed main()
{
Init();
//cout<<"*******************************"<<endl;
dfs(5 , used,shudu);
//cout<<"cout1 = " <<count1<<endl;
//cout<< boolalpha << check(shudu) <<endl;
return 0;
}
结果