八数码总结

题意:

八数码问题也称为九宫问题。编号为1~8的8个正方形滑块被摆成3行3列,棋盘上还有一个空格,每次可以把与空格相邻的滑块移到空格中,而它原来的位置就成了新的空格。给定初始局面和目标局面,计算出最少的移动步数。
参考来源:八数码的八境界

方法1:

暴搜+queue,如果空格用’0’表示的话可以直接将状态压缩成一个9位整数,也可以用字符串表示状态,用结构体表示状态+步数+空格的位置。

代码:

/*
264137058
815736402
*/
#include<iostream>
#include<map>
#include<cstring>
#include<queue>
#include<cstdio>
using namespace std;
map<string, int>vis;
int m, n;
struct state{string str; int pos; int dist;};
state s;
string goal;
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};
int bfs()
{
    queue<state>q;
    q.push(s);
    vis[s.str] = 1;
    while(!q.empty()){
        state t = q.front();q.pop();
        if(t.str.compare(goal) == 0) return t.dist;
        int p= t.pos;
        int x= p / 3, y = p % 3;
        for(int i = 0; i < 4; i++){
            int newx = x + dx[i];
            int newy = y + dy[i];
            int newpos = newx * 3 + newy;
            string a = t.str;
            if(newx >= 0 && newx < 3 && newy >= 0 && newy < 3){
                swap(a[p], a[newpos]);
                if(vis[a]) continue;
                 vis[a] = 1;
                q.push((state){a, newpos, t.dist + 1});
            }
        }
    }
    return -1;
}
int main (void)
{
    string st;
    int pos;
    cin>>st;
    cin>>goal;
    for(int i = 0; i < 9; i++){
        if(st[i] == '0')  pos = i;
    }
    s = (state){st, pos, 0};
    int res = bfs();
    if(res == -1) cout<<"No solution"<<endl;
    else cout<<res<<endl;
}


方法2:

模拟队列+康拓展开/哈希/STL set判重,学习紫书上的代码~
注意其中康拓展开

代码:

康拓展开


#include<iostream>
#include<cstring>
#include<set>
#include<cstdio>
using namespace std;
typedef int state[9];//表示state 代表 int[9]
const int maxn = 1e7 + 5;
state st[maxn], sta, goal;
int dist[maxn];
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};
/*
2 6 4 1 3 7 0 5 8
8 1 5 7 3 6 4 0 2
*/
/******把0~8的全排列和0~362879的整数一一对应起来****/
int vis[400000], fact[10];
void init()
{
    memset(vis, 0, sizeof(vis));
    fact[0] = 1;
    for(int i = 1; i < 9; i++) fact[i] = fact[i - 1] * i;
}
int Insert(int s)
{
    int code = 0;
    for(int i = 0; i < 9; i++){
        int cnt = 0;
        for(int j = i + 1; j < 9; j++)
            if(st[s][j] < st[s][i]) cnt++;
        code += fact[8 - i] * cnt;
    }
    if(vis[code]) return 0;
    return vis[code] = 1;
}
int bfs()
{
    init();
    int fro = 0, rear = 1;
    dist[fro] = 0;
    Insert(fro);
    while(fro < rear){
        state& s = st[fro];
        if (memcmp(goal, s, sizeof(s)) == 0) return dist[fro];
        int pos;
        for(pos = 0; pos < 9; pos++) if(!s[pos]) break;
        int x = pos / 3, y = pos % 3;
        for (int i = 0; i < 4; i++){
            int newx = x + dx[i];
            int newy = y + dy[i];
            int newpos = newx * 3 + newy;
            if (newx >= 0 && newx < 3 && newy >= 0 && newy < 3){
                state& t = st[rear];
                memcpy(&t, &s, sizeof(s));
                t[newpos] = 0;
                t[pos] = s[newpos];
                dist[rear] = dist[fro] + 1;
                if(Insert(rear)) rear++;
            }
        }
        fro++;
    }
    return -1;
}
int main (void)
{
    for(int i = 0; i < 9; i++) cin>>st[0][i];
    for(int i = 0; i < 9; i++) cin>>goal[i];
    int res = bfs();
    if(res != -1) cout<<res<<endl;
    else cout<<"No solution"<<endl;
    return 0;
}

哈希:
const int hashsize = 1000003;//大质数
int head[hashsize], next[hashsize];
void init()
{
    memset(head, 0, sizeof(head));
}
int Hash(int s)
{
    int tmp = 0;
    for(int i = 0; i < 9; i++)
        tmp = tmp * 10 + st[s][i];
    return tmp % hashsize;
}
int Insert(int s)
{
    int h = Hash(s);
    int u = head[h];
    while(u){
        if(memcpy(st[u], st[s], sizeof(st[s])) == 0)  return 0;
        u = next[u];
    }
    next[s] = head[h];
    head[h] = s;
    return 1;
}
set判重:
set<int>vis;
void init()
{
    vis.clear();
}
int Insert(int s)
{
    int v  = 0;
    for(int i = 0; i < 9; i++) v = v* 10 + st[s][i];
    if(vis.count(v)) return 0;
    vis.insert(v);
    return 1;
}

方法3:

模拟队列+双向bfs~~这里判重随便选一个就好啦~~节约一半的时间和一半的空间,真心快多了~

代码:

#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
typedef int state[9];
const int maxn = 1e7 + 5;
state st[maxn];
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};
int fact[maxn], vis[maxn], dist[3][maxn];
/*
2 6 4 1 3 7 0 5 8
8 1 5 7 3 6 4 0 2
*/
void init()
{
    memset(vis, 0, sizeof(vis));
    fact[0] = 1;
    for(int i = 1; i < 9; i++)
        fact[i] = fact[i - 1] * i;
}
int canto(state s)
{
    int code = 0;
    for(int i = 0; i < 9; i++){
        int cnt = 0;
        for(int j = i + 1; j < 9; j++){
            if(s[j] < s[i]) cnt++;
        }
        code += fact[8 - i] * cnt;
    }
    return code;
}
int bfs()
{
    init();
    int cs = canto(st[0]);
    int ct = canto(st[1]);
    dist[1][cs] = 0;
    dist[2][ct] = 0;
    vis[cs] = 1;
    vis[ct] = 2;
    int fro = 0, rear  = 2;
    while(fro < rear){
        state& s = st[fro];
        cs = canto(s);
        int pos;
        for(pos = 0; pos < 9; pos++) if(!s[pos]) break;
        int x = pos / 3, y = pos % 3;
        for (int i = 0; i < 4; i++){
            int newx = x + dx[i];
            int newy = y + dy[i];
            int newpos = newx * 3 + newy;
            if (newx >= 0 && newx < 3 && newy >= 0 && newy < 3){
                state& t = st[rear];
                memcpy(&t, &s, sizeof(s));
                t[pos] = s[newpos];
                t[newpos] = s[pos];
                ct = canto(t);
                if(!vis[ct]){
                    vis[ct] = vis[cs];
                    dist[vis[cs]][ct] = dist[vis[cs]][cs] + 1;
                    rear++;
                }else if(vis[ct] != vis[cs]){
                    return dist[vis[ct]][ct] + dist[vis[cs]][cs] + 1;
                }
            }
        }
        fro++;
    }
    return -1;
}
int main (void)
{
    for(int i = 0; i < 9; i++) cin>>st[0][i];
    for(int i = 0; i < 9; i++) cin>>st[1][i];
    int res = bfs();
    if(res != -1) cout<<res<<endl;
    else cout<<"No solution"<<endl;
    return 0;
}

方法4:

A* + 优先级队列(小顶堆)+ 曼哈顿距离
A*搜索算法

当任何第二次走到一个点的时候,判断最小步骤是否小于记录的内容,如果是,则更新掉原最小步数,关于记录最小步数,如果使用曼哈顿距离作为 h(n) ,则不必记录,这时的A*满足:每个点第一次被选出搜索队列时的状态为到达此点的最优解,而且每个点只会进入队列一次。所以只需要设定一个vis的数组就够了。

代码:

#include<iostream>
#include<cstring>
#include<cmath>
#include<queue>
/*
1 2 3 4 5 6 7 8 0
1 2 3 4 5 6 0 7 8

2 6 4 1 3 7 0 5 8
8 1 5 7 3 6 4 0 2
*/
using namespace std;
struct state
{
    int a[9];
    int pos;
    int f,g;
    int can;
     bool operator < ( const state s2)const {
        return f > s2.f;
    }
};
const int maxn = 1e7 + 5;
state sta, goal;
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};
int fact[maxn], vis[maxn];
void init()
{
    fact[0] = 1;
    for(int i = 1; i < 9; i++)
        fact[i] = fact[i - 1] * i;
}
int canto(state s)
{
    int code = 0;
    for(int i = 0; i < 9; i++){
        int cnt = 0;
        for(int j = i + 1; j < 9; j++){
            if(s.a[j] < s.a[i]) cnt++;
        }
        code += fact[8 - i] * cnt;
    }
    return code;
}
int geth(state be)
{
    int res = 0;
    for(int i = 0; i < 9; i++){
        int tmp = goal.a[i];
        for(int j = 0; j < 9; j++){
            if(be.a[j] == tmp)
                res += abs(i/3 - j/3) + abs(i % 3 - j % 3);
                break;
        }
    }
    return res;
}
int astar()
{
    priority_queue<state> q;
    q.push(sta);
    vis[sta.can] = 1;
    while(!q.empty()){
        state s = q.top();
        q.pop();
        if(s.can == goal.can) return s.g;
        int x = s.pos / 3, y = s.pos % 3;
        for(int i = 0; i < 4; i++){
            int newx = x + dx[i];
            int newy = y + dy[i];
            int newpos = newx * 3 + newy;
            if (newx >= 0 && newx < 3 && newy >= 0 && newy < 3){
                state t = s;
                t.a[t.pos] = s.a[newpos];
                t.a[newpos] = s.a[t.pos];
                t.pos = newpos;
                t.can = canto(t);
                if(vis[t.can]) continue;
                t.g = s.g + 1;
                t.f= geth(t) + t.g;
                q.push(t);
                vis[t.can] = 1;
            }
        }
    }
    return -1;
}
void print (state t)
{
    for(int i = 0; i < 9; i++)
        cout<<t.a[i]<<' ';
    cout<<endl;
    cout<<"pos"<<t.pos<<endl;
    cout<<"can"<<t.can<<endl;
    cout<<"f"<<t.f<<endl;
}
int main (void)
{
    init();
    for(int i = 0; i < 9; i++){
        cin>>sta.a[i];
        if(sta.a[i] == 0){
            sta.pos = i;
        }
    }
    sta.g = 0;
    sta.can = canto(sta);
    //print(sta);
    for(int i = 0; i < 9; i++) cin>>goal.a[i];
    goal.can = canto(goal);
    //print(goal);

    int res = astar();
    if(res != -1) cout<<res<<endl;
    else cout<<"No solution"<<endl;
    return 0;
}

方法5:

IDA* + 曼哈顿距离

IDA*即迭代加深的A*搜索,实现代码是最简练的,无须状态判重,无需估价排序。那么就用不到哈希表,堆上也不必应用,空间需求变的超级少。效率上,应用了曼哈顿距离。同时可以根据深度和h值,在找最优解的时候,对超过目前最优解的地方进行剪枝,这可以导致搜索深度的急剧减少,所以,这是一个致命的剪枝!因此,IDA*大部分时候比A*还要快,可以说是A*的一个优化版本!

代码:

#include<iostream>
#include<queue>
#include<cstring>
#include<cmath>
using namespace std;
const int maxn = 1e7 + 5;
int maxdepth;
int dx[4] = {1, 0 , 0, -1};
int dy[4] = {0, 1, -1, 0};
/*
1 2 3 4 5 6 7 8 0
1 2 3 4 5 6 0 7 8

2 6 4 1 3 7 0 5 8
8 1 5 7 3 6 4 0 2
*/
struct state
{
    int a[9];
    int pos;
};
state st[maxn];
state goal, sta;
int geth(state be)
{
    int res = 0;
    for(int i = 0; i < 9; i++){
        int tmp = goal.a[i];
        if(tmp == 0) continue;
        for(int j = 0; j < 9; j++){
            if(be.a[j] == tmp){
                 res += abs(i/3 - j/3) + abs(i % 3 - j % 3);
                break;
            }
        }
    }
    return res;
}
void print(state s)
{
    for(int i = 0; i < 9; i++)
        cout<<s.a[i]<<' ';
    cout<<endl;
}
int dfs(state s, int h, int depth, int prei)
{
    if(h == 0) return 1;
   // print(s);
    if(depth + h > maxdepth) return 0;
    int x = s.pos / 3, y = s.pos % 3;
    for (int i = 0; i < 4; i++){
        if(prei + i == 3) continue;
        int newx = x + dx[i];
        int newy = y + dy[i];
        int newpos = newx * 3 + newy;
        if (newx >= 0 && newx < 3 && newy >= 0 && newy < 3){
            state t = s;
            t.a[s.pos]  = s.a[newpos];
            t.a[newpos] = s.a[s.pos];
            t.pos = newpos;

            if(dfs(t, geth(t), depth + 1, i)) return 1;
        }
    }
    return 0;
}
int idastar()
{
    int h = geth(sta);
    maxdepth = h;
    while(!dfs(sta, h, 0, -1) && maxdepth < 100) {
        maxdepth++;
    }
   return maxdepth;
}
int main (void)
{
    for(int i = 0; i < 9; i++){
        cin>>sta.a[i];
        if(sta.a[i] == 0){
            sta.pos = i;
        }
    }
     for(int i = 0; i < 9; i++){
        cin>>goal.a[i];
    }
    int res = idastar();
    if(res < 100) cout<<res<<endl;
    else cout<<"No solution"<<endl;
    return 0;
}

方法6:

有关逆序数
空格在同一行上进行移动,1~8的排列逆序数不变,空格在同一列上进行移动,1~8的排列逆序数发生变化的对数为偶数。所以初始状态的逆序数与目标态同奇偶则有解,反之无解;
这一优化可以用在上面的所有方法中,也是一个很重要的剪枝。

代码:

int Reverse(state s)
{
    int cnt = 0;
    for(int i = 0; i < 9; i++){
        int tmp = s.a[i];
        if(tmp == 0) continue;
        for(int j = i +1; j < 9; j++){
            if(s.a[j] && s.a[i] > s.a[j]) cnt++;
        }
    }
    return cnt % 2;
}

真的是细节决定一切。。。竟然写了一天。。。一半时间还都用来调试。。。写的时候就应该心思细腻,检查的时候模拟一遍过程,任何细节都不能放过!

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
八数码问题是一种经典的人工智能问题,目标是将一个九宫格中的数字按照规定的移动方式移动到目标状态。以下是八数码问题的一些Python实现总结: 1. BFS算法实现: 使用BFS算法可以实现八数码问题的求解。具体步骤为: - 将起始状态加入队列 - 从队列中取出状态,对其进行扩展,生成新的状态 - 如果新的状态已经出现过,则跳过;否则,将新状态加入队列 - 重复执行上述步骤,直到找到目标状态 实现代码如下: ```python def bfs(start, end): queue = [(start, "")] visited = set() while queue: node, path = queue.pop(0) if node == end: return path visited.add(node) for move, delta in MOVES.items(): new_node = make_move(node, delta) if new_node not in visited: queue.append((new_node, path + move)) return "impossible" ``` 2. A*算法实现: A*算法是一种启发式搜索算法,可以更快地找到八数码问题的最优解。具体步骤为: - 计算每个状态到目标状态的估价函数值(即预计还需要多少步才能到达目标状态) - 将起始状态加入开放列表 - 计算起始状态的估价函数值,并将其加入到估价函数列表 - 从开放列表中取出估价函数值最小的状态,对其进行扩展,生成新的状态 - 如果新的状态已经出现过,则跳过;否则,将新状态加入开放列表和估价函数列表 - 重复执行上述步骤,直到找到目标状态 实现代码如下: ```python def astar(start, end): queue = [(heuristic(start, end), start, "")] visited = set() while queue: h, node, path = heapq.heappop(queue) if node == end: return path visited.add(node) for move, delta in MOVES.items(): new_node = make_move(node, delta) if new_node not in visited: new_h = heuristic(new_node, end) heapq.heappush(queue, (new_h, new_node, path + move))) return "impossible" ``` 以上是八数码问题的两种Python实现方式,选择哪种方法取决于你的需求和运行环境。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值