Open the Lock (BFS)

本文介绍了一个使用广度优先搜索(BFS)算法解决四数字密码锁问题的方法。目标是在最少步骤内从初始状态到达指定密码状态。通过增加或减少每个数字上的数值以及交换相邻数字来改变密码组合。
Now an emergent task for you is to open a password lock. The password is consisted of four digits. Each digit is numbered from 1 to 9.
Each time, you can add or minus 1 to any digit. When add 1 to '9', the digit will change to be '1' and when minus 1 to '1', the digit will change to be '9'. You can also exchange the digit with its neighbor. Each action will take one step.

Now your task is to use minimal steps to open the lock.

Note: The leftmost digit is not the neighbor of the rightmost digit.
Input
The input file begins with an integer T, indicating the number of test cases.

Each test case begins with a four digit N, indicating the initial state of the password lock. Then followed a line with anotther four dight M, indicating the password which can open the lock. There is one blank line after each test case.
Output
For each test case, print the minimal steps in one line.
Sample Input
2
1234
2144

1111
9999
Sample Output
2

4



BFS

AC代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<string>
#include<queue>
#include<set>
#include<map>
#include<stack>
#include<vector>
#include<cstdlib>
using namespace std;
typedef long long ll;
int a,b,step;
int vis[100010]={0};
queue<int> q;
int bfs(int a,int b){
    q.push(a);
    vis[a]=1;
    while(!q.empty()){
        int len=q.size();
        while(len--){
            int num[5];
            int fir=q.front(),tmp;
            q.pop();
            if(fir==b)return step;
            num[3]=fir%10;
            num[2]=fir/10%10;
            num[1]=fir/100%10;
            num[0]=fir/1000;
            for(int i=0;i<4;i++){
                int m=num[i];
                num[i]+=1;
                if(num[i]>9)num[i]=1;
                tmp=0;
                for(int j=0;j<4;j++)
                    tmp=tmp*10+num[j];
                if(!vis[tmp]){
                    vis[tmp]=1;
                    q.push(tmp);
                }
                num[i]=m;
                num[i]-=1;
                if(num[i]<1)num[i]=9;
                tmp=0;
                for(int j=0;j<4;j++)
                    tmp=tmp*10+num[j];
                if(!vis[tmp]){
                    vis[tmp]=1;
                    q.push(tmp);
                }
                num[i]=m;
            }
            tmp=num[1]*1000+num[0]*100+num[2]*10+num[3];
            if(!vis[tmp]){
                vis[tmp]=1;
                q.push(tmp);
            }
            tmp=num[0]*1000+num[2]*100+num[1]*10+num[3];
            if(!vis[tmp]){
                vis[tmp]=1;
                q.push(tmp);
            }
            tmp=num[0]*1000+num[1]*100+num[3]*10+num[2];
            if(!vis[tmp]){
                vis[tmp]=1;
                q.push(tmp);
            }
        }
        step++;
    }
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--){
        while(!q.empty())
            q.pop();
        step=0;
        memset(vis,0,sizeof(vis));
        scanf("%d%d",&a,&b);
        bfs(a,b);
        printf("%d\n",step);
    }
    return 0;
}


<think>我们正在讨论双向BFS算法。根据引用[5],双向BFS是一种优化策略,用于减少在搜索空间较大时的计算量。它的核心思想是从起点和终点同时开始进行BFS,当两个方向的搜索相遇时,路径就被找到了。这样可以显著减少搜索的节点数量,因为单向BFS的扩展是指数级的,而双向BFS可以将指数级减少为两个指数级之和。双向BFS的实现原理:1.同时从起点和终点开始进行BFS,使用两个队列分别存储两个方向的当前层节点。2.使用两个哈希表(或数组)分别记录从起点到达的节点和从终点到达的节点,同时记录步数(或者距离)。3.在每一步中,选择当前队列中节点数较少的方向进行扩展(平衡两个方向的扩展,以优化性能)。4.当从起点扩展的节点出现在终点的已访问集合中(或者反之)时,说明两个方向相遇,此时将两个方向的距离相加即可得到最短路径。双向BFS的应用场景:-通常用于求解最短路径问题,尤其是当搜索空间很大(如状态空间搜索)时,使用双向BFS可以大大减少搜索的节点数。-例如,在迷宫问题、拼图问题(如八数码)、单词接龙、解开密码锁等问题中,当状态空间很大时,双向BFS可以显著提高效率。实现示例(以解开密码锁问题为例,参考引用[5]):问题描述:有一个带有四个圆形拨轮的密码锁,每个拨轮有0到9共10个数字。初始状态为"0000",每次可以向上或向下旋转一个拨轮(即一个数字加1或减1,注意0的下一个数字是9,9的上一个数字是0)。同时,有一个死亡数字列表,如果拨轮转到了死亡数字上,则锁被锁定无法再旋转。给定目标数字,要求避开死亡数字,找到从"0000"转到目标数字的最少旋转次数。使用双向BFS的实现步骤:1.初始化两个队列:`q1`从起点"0000"开始,`q2`从目标`target`开始。2.初始化两个访问记录哈希表:`visited1`记录从起点访问过的节点和步数,`visited2`记录从终点访问过的节点和步数。将起点加入`visited1`(步数为0),终点加入`visited2`(步数为0)。3.当两个队列都不为空时,循环:a.选择当前节点数较少的队列进行扩展(假设为`q1`)。b.对`q1`的当前层的每个节点,生成相邻节点(每个拨轮向上或向下转,共8种可能)。c.如果生成的相邻节点在`visited2`中已经存在,则相遇,返回当前节点在`visited1`中的步数+1(加上相邻节点在`visited2`中的步数)。d.如果相邻节点是死亡数字,则跳过。e.如果相邻节点未在`visited1`中出现,则将其加入`visited1`(步数为当前步数+1)并加入`q1`。4.如果相遇则返回总步数,否则如果其中一个队列为空还未相遇,则无解。注意:为了避免重复扩展,我们每次只扩展一个方向的一层(或者一个节点),但通常为了简单,可以扩展一层(即当前队列中的所有节点)。另外,每次扩展后检查相遇。下面是一个使用双向BFS解决该问题的Python代码示例:```pythondefopenLock(deadends,target):iftarget=="0000":return0dead=set(deadends)if"0000"indead:return-1#生成相邻节点defget_neighbors(node):neighbors=[]foriinrange(4):num=int(node[i])#向上旋转up=(num+1)%10#向下旋转down=(num-1)%10ifdown<0:down+=10#注意:这里也可以统一用模10,但是向下旋转时,-1模10等于9,所以可以简写为(num-1)%10,但为了清晰,分开写forxin[up,down]:neighbor=node[:i]+str(x)+node[i+1:]neighbors.append(neighbor)returnneighbors#初始化双向BFSq1=deque(["0000"])q2=deque([target])visited1={"0000":0}#记录从起点到该节点的步数visited2={target:0}#记录从终点到该节点的步数whileq1andq2:#总是从节点数少的队列开始扩展iflen(q1)>len(q2):q1,q2=q2,q1visited1,visited2=visited2,visited1#扩展当前队列q1的一层size=len(q1)for_inrange(size):node=q1.popleft()#如果当前节点在另一个方向已经访问过,则相遇ifnodeinvisited2:returnvisited1[node]+visited2[node]forneighboringet_neighbors(node):ifneighborindeadorneighborinvisited1:continuevisited1[neighbor]=visited1[node]+1q1.append(neighbor)#交换后,下一轮扩展另一个方向(但这里通过交换队列和哈希表,使得下一轮扩展原来的q2,即现在的q1)return-1```注意:上述代码中,我们每次交换队列和哈希表,使得总是扩展较小的队列。这样在每一步中,我们扩展的都是当前节点数较少的方向,从而平衡两个方向的搜索。性能分析:双向BFS的时间复杂度最坏情况下是单向BFS的平方根,因为两个方向同时搜索,减少了搜索的层数。空间复杂度同样也会减少。相关问题:1.双向BFS在哪些情况下不适用?2.双向BFS如何避免在两个方向中重复扩展相同的节点?3.双向BFS和A*算法在性能上有何异同?</think>### 双向BFS算法原理及实现示例 #### 算法原理 双向BFS(Bidirectional BFS)是广度优先搜索的优化变体,核心思想是**从起点和终点同时进行BFS搜索**,当两个方向的搜索相遇时即找到最短路径。相比传统BFS,其优势在于: 1. **时间复杂度优化** 传统BFS时间复杂度为$O(b^d)$($b$为分支因子,$d$为目标深度)。双向BFS时间复杂度降为$O(b^{d/2} + b^{d/2})$,指数级减少搜索空间[^3][^5]。 2. **空间复杂度优化** 双向BFS通过两个较小队列替代单一的大队列,空间复杂度从$O(b^d)$降至$O(b^{d/2})$[^3]。 3. **终止条件** 当两个搜索方向的已访问集合出现交集时(即某个节点被双向访问),算法终止并拼接路径。 #### 实现示例(密码锁问题) **问题描述**: 一个4位密码锁(每位0-9),初始状态"0000",目标状态"0202"。禁止状态(deadends)为["0201","0101","0102","1212","2002"]。每次操作可旋转一个拨轮(±1),求最少操作步数[^5]。 **Python实现**: ```python from collections import deque def openLock(deadends, target): dead = set(deadends) if "0000" in dead: return -1 # 起点为死锁 # 双向BFS初始化 q_start = deque(["0000"]) q_target = deque([target]) visited_start = {"0000": 0} # 状态:步数 visited_target = {target: 0} # 生成相邻状态 def get_neighbors(node): neighbors = [] for i in range(4): num = int(node[i]) for dx in (1, -1): # 正反旋转 nx = (num + dx) % 10 # 处理0-9循环 neighbors.append(node[:i] + str(nx) + node[i+1:]) return neighbors while q_start and q_target: # 选择较小队列扩展(优化空间) if len(q_start) > len(q_target): q_start, q_target = q_target, q_start visited_start, visited_target = visited_target, visited_start size = len(q_start) for _ in range(size): node = q_start.popleft() # 相遇检测 if node in visited_target: return visited_start[node] + visited_target[node] # 跳过死锁 if node in dead: continue # 扩展新状态 for neighbor in get_neighbors(node): if neighbor not in visited_start: visited_start[neighbor] = visited_start[node] + 1 q_start.append(neighbor) return -1 # 无解 # 测试 deadends = ["0201","0101","0102","1212","2002"] target = "0202" print(openLock(deadends, target)) # 输出: 6 ``` #### 关键实现细节 1. **双向队列平衡** 每次选择节点数较少的队列扩展(`len(q_start) > len(q_target)`),避免单向过度增长[^5]。 2. **状态扩展函数** `get_neighbors`生成8个相邻状态(4个拨轮×±1旋转),通过取模`%10`处理0→9和9→0的循环。 3. **相遇判定** 当`node in visited_target`成立时,路径长度为`visited_start[node] + visited_target[node]`。 4. **死锁处理** 遇到`deadends`中的状态直接跳过,不加入队列。 #### 性能对比 | 算法 | 搜索方向 | 时间复杂度 | 空间复杂度 | |------------|----------|----------------|----------------| | 传统BFS | 单向 | $O(b^d)$ | $O(b^d)$ | | 双向BFS | 双向 | $O(b^{d/2})$ | $O(b^{d/2})$ | 此优化在状态空间较大时效果显著,如八数码问题、单词接龙、社交网络六度空间等场景[^3][^5]。 --- ### 相关问题 1. 双向BFS在哪些场景下比A*算法更适用? 2. 如何处理双向BFS中两个方向搜索速度不一致的问题? 3. 双向BFS能否应用于带权图的最短路径搜索?如何改造? 4. 双向BFS的相遇条件能否优化为路径成本之和最小化? 5. 如何证明双向BFS找到的路径一定是最短路径? [^1]: 广度优先搜索(BFS)详解:原理、应用与实现 [^2]: 【图计算算法】广度优先搜索(BFS)算法 [^3]: 专业学习|BFS算法介绍以及实现框架 [^5]: 广度优先搜索BFS优化,双向BFS
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值