笔记:记录状态并判重的方法

题目(八数码问题)

编号为1-8的8个正方形滑块被摆成3行3列(有一个格子留空),如下图所示

815
736
42

每次可以把与空格相邻的滑块(有公共边才算相邻)一道空格中,而它原来的位置就成为了新的空格。给定初始局面和目标局面(用0表示空格),你的任务是计算出最少的移动步数。如果无法到达目标局面,则输出-1

解法思路

简单来说就是无权图的最短路径问题(和我昨天看到的魔方问题差不多),可以用bfs来做

如果想看看有权图的最短路径问题,可以看看下面这题:

OpenJudge - 19G:海拔

我做这题的思路:

好了言归正传

这题难点在于对状态的记录的问题:如果声明一个9维数组vis,总共有9^9=387420489个项,太多了,内存直呼根本塞不下。如果把9个元素合并成一个数字,最大的数字是876543210,甚至更大

而实际的结点数量并没有这么多,0~8的全排列只有9!= 362990个,如果按照上面的方法,有大量的内存会被浪费

方法一

直接使用STL的集合set。把状态转化为9位十进制整数,就可以用set<int>判重了

set<int> vis;
void init_lookup_table{vis.clear();}
int try_to_insert(int s){
    int v = 0;
    //将s中的状态转化为数字并放入v中(伪代码)
    if(vis.count(v)) return 0;
    vis.insert(v);
    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(st为二维数组,记录状态,第二维记录各个数字)
    for(int i = 0; i < 9; i++){
        int cnt = 0;
        for(int j = i + 1; j < 0; j++) if(st[s][j] < st[s][i]) cnt++;
        code += fact[8 - i] * cnt;
    }
    if(vis[code]) return 0;
    return vis[code] = 1;
}

屌吧,屌爆了

原理巧妙(我根本想不到),时间效率也非常高,但编码解码法的适用范围并不大:如果总结点数非常大,编码也会非常大,数组还是开不下

方法三

想必诸位都猜到了

使用哈希技术。简单地说,就是要把结点“变成”整数,但是不必一一对应。换句话说,只需设计一个所谓的哈希函数h(x),然后将任意结点x映射到某个给定范围[0, M - 1]的整数即可,其中M是程序员根据可用内存大小自选的。在理想情况下,只开一个大小为M的数组就能完成判重,但此时往往会有不同结点的哈希值相同,因此需要把哈希值相同的状态组织成链表

typedef int State[9];
const int hashsize = 1000003;
const int maxstate = 1000000;

State st[maxstate];//状态数组
int head[hashsize], next[maxstate];
void init_lookup_table(){memset(head, 0, sizeof(head);}
int hash(State &s){
    int v = 0;
    for(int i = 0; i < 9; i++) v = v * 10 + s[i];//把9个数字组成9位数
    return v % hashsize;//确保哈希值是不超过哈希表大小的非负整数
}
int try_to_insert(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;
    return 1;
}

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值