八数码问题
一. 广度优先算法
使用队列记录当前层次的状态
同时使用哈希表防止重复遍历
单向广度优先是逐渐增大范围同时判断目标是否在范围内
int dir[4][2] = {{1,0},{0,-1},{-1,0},{0,1}};
int main()
{
string board;
cin>>board;//初始化棋盘
string target = "123804765"; //初始化目标
int depth = 0;//搜索深度
queue<string> q;//队列记录当前状态
unordered_set<string> s;//不重复遍历
q.push(board);
while(!q.empty()){
int n = q.size();
for(int i=0;i<n;i++){//遍历当前层次
string &str = q.front(); q.pop(); //下面都是针对str状态来写
if(str==target){//找到目标
cout<<depth;
return 0;
}
s.insert(str);//将已遍历状态记录下来,防止重复遍历
int pos = str.find('0');
int x = pos/3; int y = pos%3;
for(int j=0;j<4;j++){//遍历四个方向,进行交换
int nx = x + dir[j][0]; int ny = y + dir[j][1];
int nextpos = nx*3+ny;
if(nx<0||nx>2||ny<0||ny>2) continue;//下个位置不存在,或者是原来过来的位置,跳过
swap(str[pos],str[nextpos]); //移动
if(s.find(str)==s.end()) q.push(str);//加入一个新的状态
swap(str[pos],str[nextpos]);//撤回
}
}
depth++;
}
return 0;
}
二. 双向广度优先
双向广度优先交替增大范围,并且判断是否交叉
int dir[4][2] = {{1,0},{0,-1},{-1,0},{0,1}};
int main()
{
string board;
cin>>board;//初始化棋盘
string target = "123804765"; //初始化目标
int depth = 0;//搜索深度
queue<string> q1;//队列记录当前状态
unordered_set<string> s1;//不重复遍历
queue<string> q2;//队列记录当前状态
unordered_set<string> s2;//不重复遍历
q1.push(board);
q2.push(target);
while(!q1.empty()||!q2.empty()){
int n = q1.size();
for(int i=0;i<n;i++){//遍历当前层次
string &str = q1.front(); q1.pop(); //下面都是针对str状态来写
if(s2.count(str)){//找到目标
cout<<depth-1;
return 0;
}
s1.insert(str);//将已遍历状态记录下来,防止重复遍历
int pos = str.find('0');
int x = pos/3; int y = pos%3;
for(int j=0;j<4;j++){//遍历四个方向,进行交换
int nx = x + dir[j][0]; int ny = y + dir[j][1];
int nextpos = nx*3+ny;
if(nx<0||nx>2||ny<0||ny>2) continue;//下个位置不存在,或者是原来过来的位置,跳过
swap(str[pos],str[nextpos]); //移动
if(s1.find(str)==s1.end()) q1.push(str);//加入一个新的状态
swap(str[pos],str[nextpos]);//撤回
}
}
depth++;
swap(q1,q2);
swap(s1,s2);
}
return 0;
}
三. A*算法
广度优先+估值函数
结合估值函数进行的广度优先搜索,这里使用优先队列,根据估计的搜索深度进行贪心搜索
string target;
int dir[4][2] = {{1,0},{0,-1},{-1,0},{0,1}};
int h1(string &s){//估值函数1,根据曼哈顿距离
int res = 0;
for(int i=0;i<s.size();i++){
if(s[i]=='0') continue;//不用计算0的匹配度
int x1 = i/3; int y1 = i%3;
int idx = target.find(s[i]);
int x2 = idx/3; int y2 = idx%3;
res += abs(x2-x1) + abs(y2-y1);
}
return res;
}
int h2(string &s){//估值函数2,根据不在位个数
int res = 0;
for(int i=0;i<s.size();i++){
if(s[i]=='0') continue;
if(s[i]!=target[i]) res++;
}
return res;
}
struct Strforsort {
string str; //字符串
int depth; //当前状态搜索深度
int hval;//启发值(最小待搜索层次)
int w;//经过该状态转移,预计的最小搜索次数
Strforsort(){}
Strforsort(const string& s, int d,int h) : str(s), depth(d), hval(h), w(d+h){}
bool operator<(const Strforsort& other) const {//预计搜索次数越小,权重越大
return w > other.w;
}
};
int main()
{
string board;
cin>>board;//初始化棋盘
target = "123804765"; //初始化目标
Strforsort start(board,0,h1(board));//起点搜索深度为0,
priority_queue<Strforsort> pq;
unordered_set<string> s;
pq.push(start);
while(!pq.empty()){//贪心搜索
Strforsort cur = pq.top(); pq.pop();
if(cur.hval==0){//找到目标值
cout<<cur.depth;
return 0;
}
string &str = cur.str;
s.insert(str);//避免重复遍历同一状态
int pos = str.find('0');
int x = pos/3; int y = pos%3;
for(int i=0;i<4;i++){//遍历四个方向,进行交换
int nx = x + dir[i][0]; int ny = y + dir[i][1];
int nextpos = nx*3+ny;
if(nx<0||nx>2||ny<0||ny>2) continue;//下个位置不存在,跳过
swap(str[pos],str[nextpos]);
if(s.find(str)==s.end()){ //该状态没有遍历过
Strforsort next(str,cur.depth+1,h1(str));
pq.push(next);
}
swap(str[pos],str[nextpos]);
}
}
return 0;
}
四. IDA*算法
深度优先+估值函数
核心思想在于像广度优先搜索一样逐步增大搜索层次
这里使用估值函数对深度优先搜索进行剪枝
估值函数为1~8每个数字的曼哈顿距离,这个同样也是理论上的最少步骤
同时提供另一个对不在位进行估值的函数
保证当前搜索的是最优解
string board;
string target;
int depth;//搜索深度
int h1(string &s){//估值函数1,根据曼哈顿距离
int res = 0;
for(int i=0;i<s.size();i++){
if(s[i]=='0') continue;//不用计算0的匹配度
int x1 = i/3; int y1 = i%3;
int idx = target.find(s[i]);
int x2 = idx/3; int y2 = idx%3;
res += abs(x2-x1) + abs(y2-y1);
}
return res;
}
int h2(string &s){//估值函数2,根据不在位个数
int res = 0;
for(int i=0;i<s.size();i++){
if(s[i]=='0') continue;
if(s[i]!=target[i]) res++;
}
return res;
}
int dir[4][2] = {{1,0},{0,-1},{-1,0},{0,1}};
bool dfs(int d,int prepos){
int cnt = h1(board);
if(cnt==0) return 1;//边界条件,与目标一致
if(d+cnt>depth) return 0;//直接剪枝,达到预期深度,不再递归
int pos = board.find('0');
int x = pos/3; int y = pos%3;
for(int i=0;i<4;i++){//遍历四个方向,进行交换
int nx = x + dir[i][0]; int ny = y + dir[i][1];
int nextpos = nx*3+ny;
if(nx<0||nx>2||ny<0||ny>2||nextpos==prepos) continue;//下个位置不存在,或者是原来过来的位置,跳过
swap(board[pos],board[nextpos]);
if(dfs(d+1,pos)) return true;
swap(board[pos],board[nextpos]);
}
return false;
}
int main()
{
cin>>board;//初始化棋盘
target = "123804765"; //初始化目标
depth = h1(board);//一开始评估一下局面,初始化一个深度
while(!dfs(0,-1)) depth++;//初始深度为0,没有上一个步
cout<<depth;
return 0;
}