在采用广度优先算法进行搜索时,一个需要重点注意的是在搜索过程中判重和去重。前面介绍的几个例子中,判重都较简单,如采用vis[]数组,若vis[i]==0,则i未访问过,i入队列;若vis[i]!=0,则i已访问过,不再重复访问。

但在有些实际应用中,判重不是简单一个设置就可完成的。例如,给出一个由1、2、3、4、5、6组成的6位数,相邻的两个数字可以交换位置,问最少经过多少次交换,可以到达另一个目标6位数。例如:对于123456,最少经过两次交换,可以变成231456。

在解决这个问题时,一定要注意判重。例如:由123456可以变成213456,而213456又可以变成123456,这样形成了循环。因此,若某个状态已出现过,当前再出现这个状态时,应判定重复出现,不能入队进行处理。

显然,这个问题中不能仿照以前的例子,定义一个vis[654322]数组,若某个状态231456出现就置vis[231456]=1。为什么呢?因为6个数字组成的不同状态最多6*5*4*3*2*1=720种,vis数组的元素个数是状态种数的900多倍,太浪费存储空间。

在状态判重方法中,hash法是一种常用的方法。

【例1】最少交换。

给出一个由1、2、3、4、5、6组成的6位数,相邻的两个数字可以交换位置,问最少经过多少次交换,可以到达另一个目标6位数。例如:对于123456,最少经过两次交换,可以变成231456。

(1)编程思路。

用广度优先搜索完成。在搜索中,需要解决状态判重问题。状态判重常用hash法。具体做法是:找到一种办法,把数字1~6的排列映射为一个整数num(0<=num<=(6!-1))。例如,排列“123456”映射为0、“213456”映射为1、“132456”映射为2、“231456”映射为3、…、“654312”映射为718、“654321”映射为719。这样,每种状态就可以对应一个整数。反过来说,0~ (6!-1)之间的任一整数,也可以唯一对应一种状态。因此,可以定义一个数组hash[720](初始值全部为0,代表未出现过),某个状态next出现了,先求出其对应的整数值num,然后置hash[num]=1。这样,判断状态next是否出现过,先求出next对应的整数值num,若hash[num]!=0,则表示状态next出现过。

n! 与n个数字组成的全排列如何映射呢?我们以3个数字1、2、3组成的全排列来说明问题。

设排列中所有数字满足从小到大排列,则称为正序。不是正序的排列中一定存在某个数字k后面有若干个数字比k小,比k小的数字个数n称为k的逆序个数。

例如,123的各位逆序个数序列为:0,0,0。映射整数为:0=0*2!+0*1!+0*0!=0

132的各位逆序个数序列为:0,1,0。映射整数为:1=0*2!+1*1!+0*0!=1

213的各位逆序个数序列为:1,0,0。映射整数为:2=1*2!+0*1!+0*0!=2

231的各位逆序个数序列为:1,1,0。映射整数为:3=1*2!+1*1!+0*0!=3

312的各位逆序个数序列为:2,0,0。映射整数为:4=2*2!+0*1!+0*0!=4

321的各位逆序个数序列为:2,1,0。映射整数为:5=2*2!+1*1!+0*0!=5

对6位数 654312而言,各位逆序个数序列为:5,4,3,2,0,0,应映射为:5*5!+4*4!+3*3!+2*2!+0*1!+0*0!=600+96+18+4=718。

这实际上也很好理解,654312首位出现6,后面比它小的数字有1、2、3、4、5共5个,若首位出现6,则首位分别出现1、2、3、4和5的情况都出现过,才可能首位出现6,而对于6位数而言,首位出现1的情况有5!种,首位出现2的情况也是5!种,…,所以映射时首位5*5!。

同理,可以理解次位是:逆序个数*4!,……。

(2)源程序。

#include <stdio.h>

#include <string.h>

int fact[]={1,1,2,6,24,120};   //  对应0!,1!,2!,3!,4!,5!

int hash(char *s)                 // 把1..6的排列*s 映射为数字 0..(6!-1)

{

int i, j, temp, num;

num = 0;

for (i = 0; i <6-1; i++)

{

temp = 0;

for (j = i + 1; j < 6; j++)

{

if (s[j] < s[i])

temp++;

}

num += fact[6-i-1] * temp;

}

return num;

}

int BFS(char src[],char dest[])

{

int vis[720]={0},step[720],front,rear,s0,s1,ts,i;

char q[720][7],cur[7],next[7],tmp;

front=rear=0;

s1=hash(dest);

strcpy(q[rear++],src);

s0=hash(src);

vis[hash(src)]=1;

step[s0]=0;

while (front<rear)

{

strcpy(cur,q[front++]);  // 出队列

s0=hash(cur);

if (s0==s1)             // 达到目标状态

return  step[s0];

for (i=0;i<6-1;i++)

{

strcpy(next,cur);

tmp=next[i]; next[i]=next[i+1];next[i+1]=tmp;  // 交换位置i和i+1中的数字

ts=hash(next);

if (vis[ts]==0)   // 状态未出现过

{

vis[ts]=1;

step[ts]=step[s0]+1;  // 记录步数

strcpy(q[rear++],next);

}

}

}

}

int main()

{

char src[7],dest[7];

while(scanf("%s%s",src,dest)!=EOF)

{

printf("%d\n",BFS(src,dest));

}

return 0;

}

BFS(四):搜索状态判重的更多相关文章

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

    八数码问题搜索有非常多高效方法:如A*算法.双向广搜等 但在搜索过程中都会遇到同一个问题.那就是判重操作(假设反复就剪枝),怎样高效的判重是8数码问题中效率的关键 以下关于几种判重方法进行比較:编码. ...

  2. BFS+Hash(储存,判重) HDOJ 1067 Gap

    题目传送门 题意:一个图按照变成指定的图,问最少操作步数 分析:状态转移简单,主要是在图的存储以及判重问题,原来队列里装二维数组内存也可以,判重用神奇的hash技术 #include <bits ...

  3. BFS以及hash表判重的应用~

    主要还是讲下hash判重的问题吧 这道题目用的是除法求余散列方式 前几天看了下算法导论 由于我们用的是线性再寻址的方式来解决冲突问题 所以hash表的大小(余数的范围)要包含我们要求的范围 对mod的 ...

  4. HDU_1429——胜利大逃亡续,十位二进制状态压缩,状态判重

    Problem Description Ignatius再次被魔王抓走了(搞不懂他咋这么讨魔王喜欢)……这次魔王汲取了上次的教训,把Ignatius关在一个n*m的地牢里,并在地牢的某些地方安装了带锁 ...

  5. HDU2579--Dating with girls(2)--(DFS, 判重)

    Dating with girls(2) Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Oth ...

  6. 逆向bfs搜索打表+康拓判重

    HDU 1043八数码问题 八数码,就是1~8加上一个空格的九宫格,这道题以及这个游戏的目标就是把九宫格还原到从左到右从上到下是1~8然后最后是空格. 没了解康托展开之前,这道题怎么想都觉得很棘手,直 ...

  7. UVA 10651 Pebble Solitaire(bfs + 哈希判重(记忆化搜索?))

    Problem A Pebble Solitaire Input: standard input Output: standard output Time Limit: 1 second Pebble ...

  8. codevs 1004 四子连棋 BFS、hash判重

    004 四子连棋 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 黄金 Gold       题目描述 Description 在一个4*4的棋盘上摆放了14颗棋子,其中有7颗白色棋 ...

  9. 八数码问题+路径寻找问题+bfs(隐式图的判重操作)

    Δ路径寻找问题可以归结为隐式图的遍历,它的任务是找到一条凑够初始状态到终止问题的最优路径, 而不是像回溯法那样找到一个符合某些要求的解. 八数码问题就是路径查找问题背景下的经典训练题目. 程序框架 p ...

随机推荐

  1. Kettle实现从数据库中提取数据到Excel

    因为有个日常提数,工作日每天都要从数据库中提取数据,转换为excel,再以邮件的形式发给用户. 刚好近期同事在研究使用kettle自动提数且完成邮件的发送,觉得很实用又挺有意思的就学了一下这个技能~ ...

  2. 易飞审核员调用DEMO-DELPHI

    作用:我已在其他文章里提过.直接调用易飞审核员程序,易飞9全版本全模块通用. 亮点:错误直接返回错误信息,并非错误代码. 最全.最优的易飞审核员接口开发,支持个案审核员接口开发.有需要的联系本人. 唯 ...

  3. 安全性测试:OWASP ZAP 2.8 使用指南(一):安全测试基础及ZAP下载、安装

    概览 本文意在对于OWASP's Zed Attack Proxy(ZAP)软件做一个基本使用指南介绍. ZAP是一个用于实施安全性测试的工具,即使没有很强的安全测试背景也可以很好的使用. 为了达到这 ...

  4. 学习UML类图

    在类图中一共包含以下几种模型元素,分别是:类(class).接口(interface)以及类之间的关系. 1.类(class) 在面向对象编程中,类是对现象世界中一组具有相同特征的物体的抽象. 2.接 ...

  5. 配置sshd的免密码登录

    在客户端上生成密钥: ssh-keygen -t rsa 然后上传到服务器上即可: ssh-copy-id username@remote-server -p22

  6. IT从业者不可不知的三条定律

    信息技术行业,也就是我们所说的IT行业,有着传统行业所未有的发展速度和模式,当然也有着它独特的发展定律.如果你是从事相关行业,下面讲到的三条定律,不可不知. 摩尔定律 比尔·盖茨曾跟通用公司老板说:如 ...

  7. DbgUiConnectToDbg(ntdll.dll)函数逆向

    暂时未解决问题: 1.  [fs+0F24h]中存储着什么东西. 答案:其存放着被调试程序的DbgObject句柄._NtCreateDebugObject(ntoskrnl.exe)函数逆向分析 该 ...

  8. C# System.Timers Demo

    static void Main(string[] args) { SystemTimersDemo(); Console.ReadLine(); } static void SystemTimers ...

  9. Docker开启Remote API 访问 2375端口

    Docker常见端口 我看到的常见docker端口包括: 2375:未加密的docker socket,远程root无密码访问主机2376:tls加密套接字,很可能这是您的CI服务器4243端口作为h ...

  10. 微信小程序 setData 如何修改动态数据?

    最近这段时间在写微信小程序,有一个页面需要动态修改 data 中的数据,而这里似乎是个坑. 1.正常修改 正常修改很简单,当触发 change 事件时,数据和页面都会同时发生改变.这个也不用多说,很简 ...