宽度优先搜索的算法思想是比较简单了
关键就是如何在有限的时间内写出正确的宽度优先搜索才是重点
不过在开始还是要弄明白,看到一个题,到底是用宽度优先搜索还是用深度优先搜索,所以,我们要明白宽度优先搜索相对于深度优先搜索的优点
1.方便找到最优解(一般第一个解就是最优的,而深度优先搜索必须要遍历完所有可能解才能确定)
2.没有递归(所以状态转换更加直观明了)
所以宽度优先搜索适合处理:
迷宫类问题,魔板类问题(因为这些一般都要求最优解)
最简单的宽度优先搜索包括:
1.记录要走的下一个状态,并放入队列
2.在正确的状态终结搜索过程
对于第一点,如何记录当前状态,对于迷宫或魔板,一般是一个二维数组,当然也可以用字符串来保存,这里建议放入一个结构体中,方便记录状态的其他参数
一般宽度优先搜索还包括:
1.记录状态转移的路径
2.搜索的深度
路径可以通过一个数组放入结构体中,已知路径,深度当然也可以得到
优化:
1.去重(记录已经走过的状态)
无环迷宫当然不会有重复的状态,但是有环迷宫或者魔板一般都会出现重复的状态,所以如何去重是优化一个宽度优先搜索的关键,不过,对于一个魔板问题,给定一个状态,如何快速地确定这个状态是不是重复的,最简单有效的办法就是hash映射,但是如何把一个状态转换为数组的下标就是关键了,这里使用康托展开:
康拓展开,就是求一个全排列是第几个排列
比如给定1234的全排列,问3241是由小到大是第几个排列,可以看出,1234应该是第一个排列,而1243应该是第二个排列,4321应该是最后一个排列,也就是第4!个
而康托展开就给出了一个系统求全排列的方法:
X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0!
其中,a[n]表示比当前数小的数还有多少种(因为是由小到大)
比如1234,比1小的数没有,那么就是0,再看2,发现比2小的数只有1,但已经出现在第一位了,所以也是0,3,4同理,那么1234康托展开就是0*3!+0*2!+0*1!+0*0!=0
再比如1243,1,2都是0,再看4,比4小的只有3,那么1234的康托展开就是0*3!+0*2!+1*1!+0*0!=1
以下是一段12345678全排列的康托展开
int u[8] = {5040,720,120,24,6,2,1,1}; //预先保存好7!,6!之类的值
int cantor(int a[]) {
int is_visited[9] = {0}; //未访问过的数字,1~8
int num = 0; //康托展开的结果
for(int i = 0; i < 8; i++) {
int mid = a[i];
int x = 0; //用来保存有多少个比当前数字小的数
while(--mid) { //这个循环用来算出有多少个比当前数字小的数
if(!is_visited[mid])
x++;
}
is_visited[a[i]] = 1; //将访问过的数字置为1
num += x * u[i];
}
return num;
}
这样,我们就可以把一个魔板状态压缩成一个小于8!=40320的数字,然后把这个作为数组下标来记录已经访问过的状态
一些注意事项:
1.全局变量的初始化
2.终结条件的准确判断(比深搜要方便)
一些错误类型:
1.如果超时,一般是对STL库使用过多,或者频繁调用函数
2.如果超内存,一般是搜索深度过深,这时要考虑到去重(或者是开了很大的数组)