在采用广度优先算法进行搜索时,一个需要重点注意的是在搜索过程中判重和去重。前面介绍的几个例子中,判重都较简单,如采用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. ES5提取公共变量

    在ES5中,请求地址前缀等需要作为公共变量提出. 则需在common.js写上 Object.defineProperty(window,'base',{ value:"http://xx. ...

  2. 图解Java常用数据结构

    最近在整理数据结构方面的知识, 系统化看了下 Java 中常用数据结构, 突发奇想用动画来绘制数据流转过程. 主要基于 jdk8, 可能会有些特性与 jdk7 之前不相同, 例如 LinkedList ...

  3. MySQL UNSIGNED和ZEROFILL属性

    UNSIGNED 这个属性就是标记数字类型是无符号的,和C/C++语言中的unsigned含义是一样的,int signed的类型范围是-2147483648~2147483648,而int unsi ...

  4. spring的事件机制实战

    理论 在分布式场景下,实现同步转异步的方式有三种方式: 1.异步线程池执行:比如借助@Asyn注解,放到spring自带的线程池中去执行: 2.放到消息队列中,在消费者的代码中异步的消费,执行相关的逻 ...

  5. JAVA 网络编程 - 实现 群聊 程序

    在实现 这个 程序之前, 我们 需要 了解 一些 关于 Java 网络 编程 的 知识. 基本 的 网络知识: 网络模型 OSI (Open System Interconnection 开放系统互连 ...

  6. docker-mysql-使用docker运行mysql8

    1, 下载镜像, 我用的是8 docker pull mysql: 2, 启动镜像 docker run \ --name mysql8 \ -p : \ -v /Users/wenbronk/Con ...

  7. 使用 FiddlerCore 自定义 HTTP/HTTPS 网络代理

    Fiddler 是个很好用的网络请求查看与调试工具,还可以写插件来扩展其功能. Fiddler 插件开发,使用 WPF 作为 UI 控件 - J.晒太阳的猫 - 博客园 但部分场景下,需要自定义很多网 ...

  8. java基础(27):线程安全、线程同步、等待唤醒机制

    1. 多线程 如果有多个线程在同时运行,而这些线程可能会同时运行这段代码.程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的. 我们通过一个案例,演示线程 ...

  9. mysql - 锁及事务的认识

    mysql事务特性:一致性原子性隔离性持久性 //mysql 事务隔离级别 读未提交 读未提交的数据 读已提交 读已提交的数据 串行序列化 一个事务完成了再执行另一个事务 可重复读(数据库默认) 就算 ...

  10. Linux常用命令(2)

    3.帮助命令 A,帮助命令:man    B,其他帮助命令 3.1,格式:man [命令名] 查看命令拥有哪个级别的帮助:                                       ...