关于八数码问题中的状态判重的三种解决方法(编码、hash、<set>)

八数码问题搜索有很多高效方法:如A*算法、双向广搜等

但在搜索过程中都会遇到同一个问题,那就是判重操作(如果重复就剪枝),如何高效的判重是8数码问题中效率的关键

下面关于几种判重方法进行比较:编码、hash、set

看到问题初学者最先想到的应该就是用一个vis数组标志一下即可。但是该申请多大的数组呢?一个9维数组(9^9=387420489太大了吧)?如果内存允许这是最高效的办法:O(1)

所以我们现在面临的问题是如何在O(1)的时间复杂度不变的情况下把空间压缩下来:

方法一:编码、解码,我们可以发现8数码问题最多有9!=362880个状态,如果我们对这些状态进行编码,用一个362880大小的数组就可以了,内存消耗大大降低,效率也基本不变,效率很高。但对于问题中状态过多时这种方法存在局限性。

代码:

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;
    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;
}

方法二:hash函数:效率很高,这种方法是用范围比较广。hash函数的选取很重要(好的hash函数冲突小)。前面的编码相当于一种完美的hash函数,没有冲突。

代码:

const int hashsize=1000003;
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];
    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;
}

方法三:stl set集合:编码相对简单了许多,但是这种方法效率也最低,对与时间要求比较高的题目,我们可以先用set,然后用hash代替

代码:

set<int> vis;
void init_lookup_table(){vis.clear();}
int try_to_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;
}

题目链接:点击打开链接

通过题目看效率:vijos八数码问题

编码:122msAC

hash:197msAC

set:932msAC

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <set>
typedef int State[9];
using namespace std;

const int maxstate=1000000;
State st[maxstate],goal={1,2,3,8,0,4,7,6,5};
int dist[maxstate];
int fa[maxstate];
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
/********************编码、解码***********************/
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;
    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;
}
/*********************hash表************************
const int hashsize=1000003;
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];
    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;
}
**********************stl set集合************************
set<int> vis;
void init_lookup_table(){vis.clear();}
int try_to_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;
}
***********************************************/
int bfs(){
    init_lookup_table();
    int front=1,rear=2;
    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;
        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];
                memcpy(&t,&s,sizeof(s));
                t[newz]=s[z];
                t[z]=s[newz];
                dist[rear]=dist[front]+1;
                fa[rear]=front;
                if(try_to_insert(rear)) rear++;
            }
        }
        front++;
    }
    return 0;
}
int main(){
    char ch;
    for(int i=0;i<9;i++) {
        //scanf("%d",&st[1][i]);
        cin>>ch;
        st[1][i]=ch-'0';
    }
    //for(int i=0;i<9;i++) scanf("%d",&goal[i]);
    fa[1]=-1;
    int ans=bfs();
    if(ans>0) printf("%d\n",dist[ans]);
    else
        printf("-1\n");
    return 0;
}

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-11 06:37:04

关于八数码问题中的状态判重的三种解决方法(编码、hash、<set>)的相关文章

.NET中TextBox控件设置ReadOnly=true后台取不到值三种解决方法

.NET中TextBox控件设置ReadOnly=true后台取不到值三种解决方法 当TextBox设置了ReadOnly=true后要是在前台为控件添加了值,后台是取不到的,值为空,多么郁闷的一个问题经过尝试,发现可以通过如下的方式解决这个问题.感兴趣的朋友可以了解下 当TextBox设置了ReadOnly="true" 后,要是在前台为控件添加了值,后台是取不到的,值为“空” 原理没想通,说不清楚微软是出于什么考虑的,不过有时是要我们能通过前台脚本来填充值,并不希望用户修改其控件内

PHP中出现Notice: Undefined index的三种解决办法

前一段做的一个PHP程序在服务器运行正常,被别人拿到本机测试的时候总是出现“Notice: Undefined index:”这样的警告,这只是一个因为PHP版本不同而产生的警告(NOTICE或者WARNING),而非错误(ERROR).PHP中的变量在不声明的情况下使用的时候,PHP4运行正常,但是到了PHP5环境下就会出现上述的警告或者提示.经过搜索查询,总结出来以下三种解决Notice: Undefined index的方法. 第一种方法:修改PHP配置文件,屏蔽掉此类警告和提示 修改ph

[转]PHP开发中涉及到emoji表情的三种处理方法

最近几个月做微信开发比较多,存储微信昵称必不可少,可这万恶的微信支持emoji表情做昵称,这就有点蛋疼了 一般Mysql表设计时,都是用UTF8字符集的.把带有emoji的昵称字段往里面insert一下就没了,整个字段变成了空字符串.这是怎么回事呢? 原来是因为Mysql的utf8字符集是3字节的,而emoji是4字节,这样整个昵称就无法存储了.这要怎么办呢?我来介绍几种方法 1.使用utf8mb4字符集 如果你的mysql版本>=5.5.3,你大可直接将utf8直接升级为utf8mb4字符集这

mysqli:查询数据库中,是否存在数据的三种校验方法

在我们编辑用户登录功能的时候,常常需要对用户输入的信息进行校验,校验的方法就是通过SQL语句进行一个比对,那么我们就需要用到以下三种中的一种进行校验啦 1.使用mysqli_num_rows()校验 例子: 成功情况: 失败情况: 2.使用mysqli_fetch_array()校验 例子: 成功情况: 失败情况: 3.使用mysqli_fetch_all()校验 例子: 成功情况: 失败情况: 以上 END 原文地址:https://www.cnblogs.com/finalanddistan

.NET中TextBox控件设置ReadOnly=true后台取不到值 三种解决方法

position:static(静态定位) 当position属性定义为static时,可以将元素定义为静态位置,所谓静态位置就是各个元素在HTML文档流中应有的位置 podisition定位问题.所以当没有定义position属性时,并不说明该元素没有自己的位置,它会遵循默认显示为静态位置,在静态定位状态下无法通过坐标值(top,left,right,bottom)来改变它的位置. position:absolute(绝对定位) 当position属性定义为absolute时,元素会脱离文档流

Electron与jQuery中$符号冲突的三种解决方法

在Electron工程中引用jQuery时,经常会出现以下错误: Uncaught ReferenceError: $ is not defined 解决的具体方法如下: ①.在测试的过程中(测试过1.10.1,以及当前最新的3.2.1版本都不行),发现只要使用2.03版本的jQuery或者2.2.0版本的jQuery,就不会出现$未定义的情况. ②.使用jQuery原有的关键字jQuery()来替代$(),或者自定义关键字. <script type="text/javascript&q

universal image loader在listview/gridview中滚动时重复加载图片的问题及解决方法

在listview/gridview中使用UIL来display每个item的图片,当图片数量较多需要滑动滚动时会出现卡顿,而且加载过的图片再次上翻后依然会重复加载(显示设置好的加载中图片) 最近在使用UIL遇到了这个问题,相信这个问题许多使用UIL的人都碰到过 现在把解决方法贴出来给有同样问题的朋友做参考 先看下UIL的工作流程 在已经允许内存,存储卡缓存的前提下,当一个图片被请求display时,首先要判断图片是否缓存在内存中,如果false则尝试从存储卡读取,如果依然不存在最后才从网络地址

Android中常用的三种存储方法浅析

Android中常用的三种存储方法浅析 Android中数据存储有5种方式: [1]使用SharedPreferences存储数据 [2]文件存储数据 [3]SQLite数据库存储数据 [4]使用ContentProvider存储数据 [5]网络存储数据 在这里我只总结了三种我用到过的或即将可能用到的三种存储方法. 一.使用SharedPreferences存储数据 SharedPreferences是Android平台上一个轻量级的存储类,主要是保存一些常用的配置信息比如窗口状态,它的本质是基

jQuery中ajax的使用与缓存问题的解决方法

jQuery中ajax的使用与缓存问题的解决方法 1:GET访问 浏览器 认为 是等幂的就是 一个相同的URL 只有一个结果[相同是指 整个URL字符串完全匹配]所以 第二次访问的时候 如果 URL字符串没变化 浏览器是 直接拿出了第一次访问的结果 POST则 认为是一个 变动性 访问 (浏览器 认为 POST的提交 必定是 有改变的) 防止 GET 的 等幂 访问 就在URL后面加上 ?+new Date();,[总之就是使每次访问的URL字符串不一样的] 设计WEB页面的时候 也应该遵守这个