题目描述 Description
已知有两个字串 A$, B$ 及一组字串变换的规则(至多6个规则):
A1$ -> B1$
A2$ -> B2$
规则的含义为:在 A$中的子串 A1$ 可以变换为 B1$、A2$ 可以变换为 B2$ …。
例如:A$='abcd' B$='xyz'
变换规则为:
‘abc’->‘xu’ ‘ud’->‘y’ ‘y’->‘yz’
则此时,A$ 可以经过一系列的变换变为 B$,其变换的过程为:
‘abcd’->‘xud’->‘xy’->‘xyz’
共进行了三次变换,使得 A$ 变换为B$。
输入描述 Input Description
键盘输人文件名。文件格式如下:
A$ B$
A1$ B1$ \
A2$ B2$ |-> 变换规则
... ... /
所有字符串长度的上限为 20。
输出描述 Output Description
若在 10 步(包含 10步)以内能将 A$ 变换为 B$ ,则输出最少的变换步数;否则输出"NO ANSWER!"
样例输入 Sample Input
abcd xyz
abc xu
ud y
y yz
样例输出 Sample Output
3
首先,双向广搜指的就是,在已知起始状态和目标状态的时候,我们采取从起始状态和目标状态分别进行搜索的方法,当从起始状态搜索(即正向搜索)与从目标状态搜索(即逆向搜索)到达了同一个状态,也就是说搜到了同一种东西,那么这时候正向与逆向搜索交汇,这个搜索也就结束了。这种方法减少了广搜的扩展范围,减少耗时。
正向搜索与逆向搜索可以交替进行,也可以根据当前正向和逆向搜索的尾节点数目来决定。后者较优,因为它保证正向与逆向搜索扩展范围的平衡,便于交汇状态的产生。
为了判断当前状态是否产生了交汇,在每次新元素入队的时候我们都要判断,这个元素在另一个搜索中是否出现过,判断方法可以采用遍历一遍(O(n)),也可以采用哈希表,效果都还可以。
如果出现了交汇,不同的题目处理方式是不一样的,针对这道题目,我们就可以将两边的深度进行求和输出,就是最终的答案。
双向BFS:
所谓双向搜索指的是搜索沿两个方向同时进行:
1.正向搜索:从初始结点向目标结点方向搜索。
2.逆向搜索:从目标结点向初始结点方向搜索。
当两个方向的搜索生成同一子结点时终止此搜索过程。
双向搜索通常有两种方法:
1. 两个方向交替扩展。
2. 选择结点个数较少的那个方向先扩展。
方法2克服了两方向结点的生成速度不平衡的状态,明显提高了效率
#include<cstdio>
#include<cstdlib>
#include<iostream>
using namespace std;
struct node
{
char s[30]; //变换后的字符串
int dep; //变了多少次
} list1[5010],list2[5010]; //两个队列
char a[7][30],b[7][30]; //变换规则
int n; //变换规则数量
bool check(char *s1,char *s2) //检测两个字符串是否一样
{
if (strlen(s1)!=strlen(s2)) return false;
for (int i=0;i<strlen(s1);i++)
if (s1[i]!=s2[i]) return false;
return true;
}
bool pan1(char *s,int i,int x) //判断字符串s[]从第i位起,i~strlen(a[x])间的字符串是否等于a[],
{
for (int j=i;j<i+strlen(a[x]);j++)
if (s[j]!=a[x][j-i]) return false;
return true;
}
bool pan2(char *s,int i,int x) //判断字符串s[]从第i位起,i~strlen(b[x])间的字符串是否等于b[],
{
for (int j=i;j<i+strlen(b[x]);j++)
if (s[j]!=b[x][j-i]) return false;
return true;
}
void bfs()
{
int head1,tail1,head2,tail2,k;
head1=tail1=head2=tail2=1;
while (head1<=tail1 && head2<=tail2)
{
if (list1[head1].dep+list2[head2].dep>10) //如果正向和逆向搜索的变换次数和大于10,则结束
{
printf("NO ANSWER!\n");
return ;
}
for (int i=0;i<strlen(list1[head1].s);i++) //正向搜索
for (int j=1;j<=n;j++)
if (pan1(list1[head1].s,i,j)) //找到能变换的规则a[j]
{
tail1++; //移动尾指针,存储变换后的字符串
for (k=0;k<i;k++) list1[tail1].s[k]=list1[head1].s[k]; //变换
for (int l=0;l<strlen(b[j]);l++,k++) list1[tail1].s[k]=b[j][l]; //变换
for (int l=i+strlen(a[j]);l<=strlen(list1[head1].s);l++,k++) //变换
list1[tail1].s[k]=list1[head1].s[l];
list1[tail1].s[k]='\0'; //变换完,
list1[tail1].dep=list1[head1].dep+1; //变换完,次数加一
for (k=1;k<=tail2;k++) //正向搜索到此时,查看逆向搜索那边是否存在该字串(即,正向逆向相遇)
if (check(list1[tail1].s,list2[k].s)) //若相遇了
{
printf("%d\n",list1[tail1].dep+list2[k].dep); //输出变换次数之和
return ;
}
}
for (int i=0;i<strlen(list2[head2].s);i++) //逆向搜索
for (int j=1;j<=n;j++)
if (pan2(list2[head2].s,i,j))
{
tail2++;
for (k=0;k<i;k++) list2[tail2].s[k]=list2[head2].s[k];
for (int l=0;l<strlen(a[j]);l++,k++) list2[tail2].s[k]=a[j][l];
for (int l=i+strlen(b[j]);l<=strlen(list2[head2].s);l++,k++)
list2[tail2].s[k]=list2[head2].s[l];
list2[tail2].s[k]='\0'; // 变换完
list2[tail2].dep=list2[head2].dep+1; //次数加一
for (k=1;k<=tail1;k++)
if (check(list1[k].s,list2[tail2].s)) //若逆向与正向相遇
{
printf("%d\n",list1[k].dep+list2[tail2].dep); //输出变换次数之和
return ;
}
}
head1++; head2++; //正向,逆向那个队列头指针后移,继续搜索。
}
printf("NO ANSWER!\n");
}
int main()
{
scanf("%s%s",list1[1].s,list2[1].s);
n=1;
while(scanf("%s%s",a[n],b[n])!=EOF) n++;
n--;
list1[1].dep=list2[1].dep=0;
bfs();
return 0;
}