八数码问题搜索有非常多高效方法:如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;
}

关于八数码问题中的状态判重的三种解决方法(编码、hash、&lt;set&gt;)的更多相关文章

  1. fluent中UDF环境变量问题的三种解决方法

    方法一: 这种方式最简便,首选这种,但是有时会因为不明原因而不好使,我自己电脑刚开始用这种方式是行得通的,但是后来中途装过很多乱七八糟的软件,估计环境变量改乱了,这时候只能用第二种或者第三种方法.先说 ...

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

    最近几个月做微信开发比较多,存储微信昵称必不可少,可这万恶的微信支持emoji表情做昵称,这就有点蛋疼了 一般Mysql表设计时,都是用UTF8字符集的.把带有emoji的昵称字段往里面insert一 ...

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

    前一段做的一个PHP程序在服务器运行正常,被别人拿到本机测试的时候总是出现“Notice: Undefined index:”这样的警告,这只是一个因为PHP版本不同而产生的警告(NOTICE或者WA ...

  4. Springboot中关于跨域问题的一种解决方法

    前后端分离开发中,跨域问题是很常见的一种问题.本文主要是解决 springboot 项目跨域访问的一种方法,其他 javaweb 项目也可参考. 1.首先要了解什么是跨域 由于前后端分离开发中前端页面 ...

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

    在我们编辑用户登录功能的时候,常常需要对用户输入的信息进行校验,校验的方法就是通过SQL语句进行一个比对,那么我们就需要用到以下三种中的一种进行校验啦 1.使用mysqli_num_rows()校验 ...

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

    当TextBox设置了ReadOnly=true后要是在前台为控件添加了值,后台是取不到的,值为空,多么郁闷的一个问题经过尝试,发现可以通过如下的方式解决这个问题.感兴趣的朋友可以了解下 当TextB ...

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

    方法一:不设置ReadOnly属性,通过onfocus=this.blur()来模拟,如下: <asp:TextBox ID="TextBox1" runat="s ...

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

    在Electron工程中引用jQuery时,经常会出现以下错误: Uncaught ReferenceError: $ is not defined 解决的具体方法如下: ①.在测试的过程中(测试过1 ...

  9. listener中@Autowired无法注入bean的一种解决方法

    背景:使用监听器处理业务,需要使用自己的service方法: 错误:使用@Autowired注入service对象,最终得到的为null: 原因:listener.fitter都不是Spring容器管 ...

随机推荐

  1. linq使用 count与sum等

    using System; using System.Data; using System.Configuration; using System.Linq; using System.Web; us ...

  2. 信息竞赛程序卡时_C++

    一.卡时简介 卡时是一个竞赛时常用的技巧 有些题目我们想不到完美算法就只能用暴力解决,但是此类方法一般时间复杂度较高,此时我们需要进行卡时 通俗来讲就是进行一个时间限制,让程序在达到这个时间后立马退出 ...

  3. 神奇的幻方(NOIP2015)(真·纯模拟)

    原题传送门 这是道SB模拟题,NOIP--难度 直接贴代码 #include<iostream> #include<cstdio> using namespace std; , ...

  4. 蓝牙攻击指南(kali)

    基本操作 hciconfig 查看蓝牙设备信息 hcitool:这是一个查询工具. 可以用来查询设备名称,设备ID,设备类别和设备时钟. hcidump:可以使用这个来嗅探蓝牙通信 hciconfig ...

  5. Linux安转jdk

    1. 创建目录 > mkdir  /opt/java > cd /opt/java 2. 下载jdk压缩包到上述目录 jdk-8u162-linux-x64.tar.gz 3. 解压缩.建 ...

  6. linux的文件权限分析

    windows中,文件的类型是根据后缀名来确定的,但是linux则是根据标志来确定的,查看一个文件的权限的命令是 ls -l #查看文件的权限 文件的权限结构如图: ①第一部分:10个字符(第1位表示 ...

  7. centos7部署nagios

    一.Nagios简介 Nagios是一款开源的电脑系统和网络监视工具,能有效监控Windows.Linux和Unix的主机状态,交换机路由器等网络设置,打印机等.在系统或服务状态异常时发出邮件或短信报 ...

  8. Ubuntu16.10 +python3.5+Tensorflow 1.1

    1.python版本检查 因为Ubuntu16.10已经默认安装了python2.7 和 3.5,检查python版本, 如果为python2.7,那么就需要我们设置python3.5为默认版本. 查 ...

  9. UpdateLayeredWindow后,使用Gdi DrawText文字透明的解决办法

    来源:http://stackoverflow.com/questions/5309914/updatelayeredwindow-and-drawtext 要点就是在先在memDc DrawText ...

  10. (11)oracle触发器

    触发器是特殊的存储过程. 每当一个特定的数据操作语句(inster,update,delete)在指定的表上触发时,Oracle自动的地执行触发器中定义的语句序列. create trigger 触发 ...