算法入门经典第二版第七章八数码问题

通过这个题,学习了bfs不用stl的实现方式,模拟队列。

学会了用一维数组,存图状态的姿势。

复习了记录路径,学习了hash的应用。。


题目要求,给出八数码初始状态,目标状态,问最少移动几步可以达到目标状态。


样例输入:
2 6 4 1 3 7 0 5 8
8 1 5 7 3 6 4 0 2
样例输出:
31

lrj使用一维数组存储图的状态,然后使用bfs,枚举所有可能的移动(对于空格当前的位置,可以移动的方向)。

然后在每次队列中弹出队首状态时,和目标状态比较。

难点在于对当前状态的标记,lrj介绍了3种方法,这里只说一下前两种。

注意这里的每个节点的状态,是一个9位的一维数组。我们可以把它转化成9位十进制数,像这样。

  int v = 0;
  for(int i = 0; i < 9; i++) v = v * 10 + s[i];

但是如果我们要开一个9位数字大小的数组,那是开不了的。

于是出现了第一种方法。使用set标记,这是一种很常见的标记大数的方法。

set<int> vis;
void init_lookup_table1() { vis.clear(); }
int try_to_insert1(int s) { //判断状态,使用将状态转换为9位数,
  int v = 0;               //用set判重
  for(int i = 0; i < 9; i++) v = v * 10 + st[s][i];
  if(vis.count(v)) return 0;
  vis.insert(v);
  return 1;
}

但是set的存取效率是O(log(n)),比起直接用数组(如果可能的话)(O(1))要慢得多。

于是出现了第二种,lrj说竞赛中常用的方法--hash。

这里的MAXHASHSIZE只开了比MAXSTATE大3的大小,可以根据内存大小自行定义。

然后使用了简单的mod操作进行hash,最简单的hash函数了。

对于映射到同一个位置的数,使用链表将数存起来进行判断。(当然可能出现很长的链表,效率就低了,需要合理的hash函数来做)

很明显,这里把要开的数组 1e9的数组,缩小到1e6+3,数组就能开下了。

// hash
const int MAXHASHSIZE = 1000003;
int head[MAXHASHSIZE], next[MAXSTATE];
void init_lookup_table2() { memset(head, 0, sizeof(head)); }
int hash(State& s) {
  int v = 0;
  for(int i = 0; i < 9; i++) v = v * 10 + s[i];
  return v % MAXHASHSIZE;
}
int try_to_insert2(int s) {
  int h = hash(st[s]);
  int u = head[h];
  while(u) {
    if(memcmp(st[u], st[s], sizeof(st[s])) == 0) return 0;
    u = next[u];
  }
  next[s] = head[h]; //往头前插入。
  head[h] = s;   //头结点置为s
  return 1;
}

最后一种方法,设计排列的编码和解码函数,把0~8的全排列和0~362879的整数一一对应起来,这个博主也没有深入研究,直接贴代码了。

int vis[362880], fact[9];
void init_lookup_table() {
  fact[0] = 1;
  for(int i = 1; i < 9; i++) fact[i] = fact[i-1] * i;
}
int try_to_insert(int s) {
  int code = 0; //把st[s]映射成code
  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;
}


最后贴出前两个的源码,附带了打印路径的代码。

// 八数码
// Winjourn
#include<cstdio>
#include<cstring>
#include<set>
#include<iostream>
using namespace std;

typedef int State[9];
const int MAXSTATE = 1000000;
State st[MAXSTATE], goal;
int dist[MAXSTATE];
int fa[MAXSTATE];

set<int> vis;
void init_lookup_table1() { vis.clear(); }
int try_to_insert1(int s) { //判断状态,使用将状态转换为9位数,
  int v = 0;               //用set判重
  for(int i = 0; i < 9; i++) v = v * 10 + st[s][i];
  if(vis.count(v)) return 0;
  vis.insert(v);
  return 1;
}


// hash
const int MAXHASHSIZE = 1000003;
int head[MAXHASHSIZE], next[MAXSTATE];
void init_lookup_table2() { memset(head, 0, sizeof(head)); }
int hash(State& s) {
  int v = 0;
  for(int i = 0; i < 9; i++) v = v * 10 + s[i];
  return v % MAXHASHSIZE;
}
int try_to_insert2(int s) {
  int h = hash(st[s]);
  int u = head[h];
  while(u) {
    if(memcmp(st[u], st[s], sizeof(st[s])) == 0) return 0;
    u = next[u];
  }
  next[s] = head[h]; //往头前插入。
  head[h] = s;   //头结点置为s
  return 1;
}


const int dx[] = {-1, 1, 0, 0};
const int dy[] = {0, 0, -1, 1};
int bfs() {
  //init_lookup_table1();
  init_lookup_table2();
  //init_lookup_table3();
  int front = 1, rear = 2; // return 0被看做不存在,所以从1开始.
  fa[front] = -1;
  while(front < rear) {
    State& s = st[front];
    if(memcmp(goal, s, sizeof(s)) == 0) return front;
    int z;
    for(z = 0; z < 9; z++) if(!s[z]) break;//找到空格的位置
    int x = z/3, y = z%3; //确定空格的x,y坐标
    for(int d = 0; d < 4; d++) {
      int newx = x + dx[d];
      int newy = y + dy[d];
      int newz = newx * 3 + newy; //新空格的位置
      if(newx >= 0 && newx < 3 && newy >= 0 && newy < 3) {
        State& t = st[rear]; //使用引用操作st数组
        memcpy(&t, &s, sizeof(s)); //将s完全赋值给t
        t[newz] = s[z]; //将新空格的位置,赋值为空格
        t[z] = s[newz]; // 将旧空格的位置,复制为之前新空格位置的数。
                        //也就实现了移动。

        dist[rear] = dist[front] + 1; // 记录步数.
        fa[rear] = front;

      //  if(try_to_insert1(rear)) rear++;
        if(try_to_insert2(rear)) rear++;
        //if(try_to_insert3(rear)) rear++;
      }
    }
    front++;
  }
  return 0;
}

int main() {
  for(int i = 0; i < 9; i++)
    scanf("%d", &st[1][i]);
  for(int i = 0; i < 9; i++)
    scanf("%d", &goal[i]);
  int ans = bfs();
  if(ans > 0) printf("%d\n", dist[ans]);
  else printf("-1\n");
  int head = ans;
  while(head != -1){
    for(int i = 0; i < 9; i++){
        cout<<st[head][i]<<" ";
        if((i +1)% 3 == 0 ) puts("");
    }
    cout<<endl;
    head = fa[head];
  }
  return 0;
}


/*
2 6 4 1 3 7 0 5 8
8 1 5 7 3 6 4 0 2

31

*/





  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值