双向广度优先搜索的三种实现方式
📌 单队列交替扩展法
这是最早期的极简实现,将起点和终点同时放入同一个队列,正向和逆向搜索交替从队列头部取节点扩展,直到出现重复节点即终止。
✅ 优点
代码实现极其简洁,仅需维护1个队列和1个全局访问标记数组,上手门槛极低,内存占用最小,不需要额外开辟第二份队列空间;
❌ 缺点
两个方向的扩展顺序完全随机,极易出现单侧搜索树快速膨胀的情况,效率不稳定,无法精准记录两个方向各自的搜索步数,路径重建逻辑非常容易出错;
⭐ 推荐星级:⭐⭐
🔍 适用场景:仅适合状态空间极小、分支因子极低的入门级练习题。
📋 例题:P1032 [NOIP 2002 提高组] 字串变换
1 #include<bits/stdc++.h> 2 #include<iostream> 3 using namespace std; 4 string A,B; 5 6 int n=0; 7 string a[10],b[10]; 8 unordered_map< string,pair<int,int> > mp; // < 字符串, <最小步数, 方向> > 9 10 void bfs(){ 11 queue<string> q; 12 q.push(A); 13 q.push(B); 14 mp[A] = make_pair(0,1); 15 mp[B] = make_pair(0,2); 16 while( q.size() ){ 17 string f = q.front(); q.pop(); 18 for( int i=0;i<n;i++ ){ 19 size_t index = 0 ; 20 while(true ) { 21 string ts = f; 22 if( mp[ts].first >= 10 ) continue; 23 index = f.find( a[i] , index ); 24 if( index == string::npos ) break; 25 ts.replace( index , a[i].length() , b[i] ); 26 if( mp.count(ts)==0 ){ 27 q.push( ts ); 28 mp[ts].first = mp[f].first + 1; 29 mp[ts].second = mp[f].second ; 30 } 31 else if( mp[ts].second != mp[f].second ){ 32 cout << mp[ts].first + mp[f].first + 1; 33 return ; 34 } 35 index += a[i].length(); 36 if( index + a[i].length() > f.length() ) break; 37 } 38 } 39 } 40 cout << "NO ANSWER!"; 41 } 42 43 int main(){ 44 cin>>A>>B; 45 while( cin>>a[n] ){ 46 cin>>b[n++]; 47 } 48 bfs(); 49 return 0; 50 }
📌 双队列等层交替扩展法
这是最经典的常规实现,分别维护正向、逆向两个独立队列,每次完整扩展完正向的一整层节点,再切换到逆向扩展一整层,交替循环直到相遇。
✅ 优点
逻辑清晰直观,两个方向的步数独立维护,相遇时能直接通过步数相加得到最短路径长度,完全保留BFS的层序特性,天然保证最短路径的最优性,路径重建难度低;
❌ 缺点
没有考虑两个方向的节点生成速度差异,若其中一侧分支因子远大于另一侧,会出现单侧队列爆炸,性能退化为接近单向BFS,固定交替的策略无法适配非完全树的场景,容易做大量无效扩展;
⭐ 推荐星级:⭐⭐⭐⭐
🔍 适用场景:图结构对称、两个方向分支因子相近的场景,比如标准迷宫寻路、规则对称的拼图游戏。
📋 例题:P1746 离开中山路
1 #include<iostream> 2 #include<queue> 3 using namespace std; 4 int dx[]{1,-1,0,0} , dy[]{0,0,1,-1}; 5 char maze[1005][1005]; 6 bool vis1[1005][1005],vis2[1005][1005]; 7 int n,x1,y1,x2,y2; 8 struct node{ 9 int x,y; 10 }; 11 12 int bfs(){ 13 if( x1==x2 && y1==y2 ) return 0; 14 queue< node > q1 , q2 ; 15 q1.push( {x1,y1} ); vis1[x1][y1] = 1; 16 q2.push( {x2,y2} ); vis2[x2][y2] = 1; 17 int step = 0; 18 while( q1.size() && q2.size() ){ 19 step++; 20 int size1 = q1.size() ; 21 while( size1-- ){ 22 int x = q1.front().x , 23 y = q1.front().y ; 24 q1.pop(); 25 for( int i=0;i<4;i++ ){ 26 int tx = x + dx[i], 27 ty = y + dy[i]; 28 if( tx<1 || tx>n || ty<1 || ty> n) continue; 29 if( maze[tx][ty]=='1' ) continue; 30 if( vis2[tx][ty] ) return step; 31 if( vis1[tx][ty] ) continue; 32 q1.push( {tx,ty} ); 33 vis1[tx][ty] = 1; 34 } 35 } 36 step++; 37 int size2 = q2.size() ; 38 while( size2-- ){ 39 int x = q2.front().x , 40 y = q2.front().y ; 41 q2.pop(); 42 for( int i=0;i<4;i++ ){ 43 int tx = x + dx[i], 44 ty = y + dy[i]; 45 if( tx<1 || tx>n || ty<1 || ty> n) continue; 46 if( maze[tx][ty]=='1' ) continue; 47 if( vis1[tx][ty] ) return step; 48 if( vis2[tx][ty] ) continue; 49 q2.push( {tx,ty} ); 50 vis2[tx][ty] = 1; 51 } 52 } 53 } 54 return -1; 55 } 56 57 58 int main(){ 59 ios::sync_with_stdio(false);cin.tie(0); 60 cin>>n; 61 for(int i=1;i<=n;i++){ 62 for(int j=1;j<=n;j++){ 63 cin>>maze[i][j]; 64 } 65 } 66 cin>>x1>>y1>>x2>>y2; 67 cout << bfs(); 68 return 0; 69 }
📌 优先扩展小队列动态平衡法
这是目前工业界和信奥竞赛中最常用的优化实现,每次动态比较两个队列的当前节点数,优先选择规模更小的一侧进行扩展,始终保持两个方向的搜索树规模接近。
✅ 优点
从根本上解决了两个方向扩展速度不平衡的问题,能最大化发挥双向BFS的指数级效率优势,节点扩展量通常比前两种方式少30%以上;
性能表现稳定,即使在非完全树、分支因子不对称的场景下,也能避免单侧队列膨胀
❌ 缺点
实现复杂度最高,需要额外增加队列大小判断逻辑,调试时需要同时跟踪两个方向的扩展状态;
极端场景下(比如其中一个方向完全没有分支),动态切换的额外判断开销会小幅抵消性能收益;
⭐ 推荐星级:⭐⭐⭐⭐⭐
🔍 适用场景:几乎所有双向BFS的生产级场景,比如单词接龙、八数码问题、社交网络最短路径查询等状态空间较大的任务。
📋 例题:P1379 八数码难题
1 #include<bits/stdc++.h> 2 using namespace std; 3 string s, t = "123804765"; 4 int d[4] = {1, -1, 3, -3}; 5 unordered_set<string> vis1,vis2; 6 int bfs(){ 7 if( s==t ) return 0; 8 queue<string> q1,q2; 9 q1.push(s); 10 q2.push(t); 11 vis1.insert(s); 12 vis2.insert(t); 13 int step = 0; 14 while( q1.size()&&q2.size() ){ 15 if( q1.size() > q2.size() ){ 16 swap(q1,q2); 17 swap(vis1,vis2); 18 } 19 step++; 20 int sz = q1.size(); 21 while(sz--){ 22 string f = q1.front(); q1.pop(); 23 int c = f.find('0'); 24 for(int i = 0; i < 4; i++){ 25 int t = d[i] + c; 26 string fs = f; 27 if(t < 0 or t > 8) continue; 28 else if( i==0 && c%3==2 ) continue; 29 else if( i==1 && c%3==0 ) continue; 30 swap(fs[t], fs[c]); 31 if( vis2.count(fs) ) return step; 32 if( vis1.count(fs) ) continue; 33 q1.push(fs); 34 vis1.insert(fs); 35 } 36 } 37 } 38 return -1; 39 } 40 int main(){ 41 cin >> s; 42 cout << bfs() << endl; 43 return 0; 44 }
⚖️bfs不同实现方式的时间对比
1 #include<bits/stdc++.h> 2 #include<iostream> 3 using namespace std; 4 string A,B; 5 6 int n=0; 7 string a[10],b[10]; 8 map<string,int> step; //step:记录s的步数 9 10 void bfs(){ 11 queue<string> q; 12 q.push(A); 13 step[A] = 0; 14 while( q.size() ){ 15 string f = q.front();q.pop(); 16 if( f==B ) { 17 cout << step[B] ; 18 return ; 19 } 20 for( int i=0;i<n;i++ ){ 21 size_t index = 0 ; 22 while(true ) { 23 string ts = f; 24 index = f.find(a[i],index ); 25 if( index==string::npos ) break; 26 ts.replace( index,a[i].length(),b[i] ); 27 if( step.count(ts)==0 ){ 28 q.push( ts ); 29 step[ts] = step[f]+1; 30 } 31 index += a[i].length(); 32 if( index >= f.length()) break; 33 } 34 } 35 } 36 cout << "NO ANSWER!"; 37 } 38 39 int main(){ 40 cin>>A>>B; 41 while( cin>>a[n] ){ 42 cin>>b[n++]; 43 } 44 bfs(); 45 return 0; 46 }
1 #include<bits/stdc++.h> 2 #include<iostream> 3 using namespace std; 4 string A,B; 5 6 int n=0; 7 string a[10],b[10]; 8 unordered_map< string,pair<int,int> > mp; // < 字符串, <最小步数, 方向> > 9 10 void bfs(){ 11 queue<string> q; 12 q.push(A); 13 q.push(B); 14 mp[A] = make_pair(0,1); 15 mp[B] = make_pair(0,2); 16 while( q.size() ){ 17 string f = q.front(); q.pop(); 18 for( int i=0;i<n;i++ ){ 19 size_t index = 0 ; 20 while(true ) { 21 string ts = f; 22 if( mp[ts].first >= 10 ) continue; 23 index = f.find( a[i] , index ); 24 if( index == string::npos ) break; 25 ts.replace( index , a[i].length() , b[i] ); 26 if( mp.count(ts)==0 ){ 27 q.push( ts ); 28 mp[ts].first = mp[f].first + 1; 29 mp[ts].second = mp[f].second ; 30 } 31 else if( mp[ts].second != mp[f].second ){ 32 cout << mp[ts].first + mp[f].first + 1; 33 return ; 34 } 35 index += a[i].length(); 36 if( index + a[i].length() > f.length() ) break; 37 } 38 } 39 } 40 cout << "NO ANSWER!"; 41 } 42 43 int main(){ 44 cin>>A>>B; 45 while( cin>>a[n] ){ 46 cin>>b[n++]; 47 } 48 bfs(); 49 return 0; 50 }
1 #include<bits/stdc++.h> 2 using namespace std; 3 string s, t = "123804765"; 4 int d[4] = {1, -1, 3, -3}; 5 unordered_set<string> vis1,vis2; 6 int bfs(){ 7 if( s==t ) return 0; 8 queue<string> q1,q2; 9 q1.push(s); 10 q2.push(t); 11 vis1.insert(s); 12 vis2.insert(t); 13 int step = 0; 14 while( q1.size()&&q2.size() ){ 15 if( q1.size() > q2.size() ){ 16 swap(q1,q2); 17 swap(vis1,vis2); 18 } 19 step++; 20 int sz = q1.size(); 21 while(sz--){ 22 string f = q1.front(); q1.pop(); 23 int c = f.find('0'); 24 for(int i = 0; i < 4; i++){ 25 int t = d[i] + c; 26 string fs = f; 27 if(t < 0 or t > 8) continue; 28 else if( i==0 && c%3==2 ) continue; 29 else if( i==1 && c%3==0 ) continue; 30 swap(fs[t], fs[c]); 31 if( vis2.count(fs) ) return step; 32 if( vis1.count(fs) ) continue; 33 q1.push(fs); 34 vis1.insert(fs); 35 } 36 } 37 } 38 return -1; 39 } 40 int main(){ 41 cin >> s; 42 cout << bfs() << endl; 43 return 0; 44 }
例题:P1379 八数码难题
#include<bits/stdc++.h> using namespace std; string s, t = "123804765"; int d[4] = {1, -1, 3, -3}; struct node{string s; int step;}; unordered_set<string> vis; int bfs(){ queue<node> q; q.push((node){s,0}); vis.insert(s); while( q.size() ){ node f = q.front(); q.pop(); if(f.s == t) return f.step; int c = f.s.find('0'); for(int i = 0; i < 4; i++){ int t = d[i] + c; string fs = f.s; if(t < 0 or t > 8) continue; else if( i==0 && c%3==2 ) continue; else if( i==1 && c%3==0 ) continue; swap(fs[t], fs[c]); if( vis.count(fs) ) continue; q.push((node){fs, f.step + 1}); vis.insert(fs); } } return -1; } int main(){ cin >> s; cout << bfs() << endl; return 0; }
1 #include<bits/stdc++.h> 2 using namespace std; 3 string s, t = "123804765"; 4 int d[4] = {1, -1, 3, -3}; 5 unordered_set<string> vis1,vis2; 6 int bfs(){ 7 if( s==t ) return 0; 8 queue<string> q1,q2; 9 q1.push(s); 10 q2.push(t); 11 vis1.insert(s); 12 vis2.insert(t); 13 int step = 0; 14 while( q1.size()&&q2.size() ){ 15 if( q1.size() > q2.size() ){ 16 swap(q1,q2); 17 swap(vis1,vis2); 18 } 19 step++; 20 int sz = q1.size(); 21 while(sz--){ 22 string f = q1.front(); q1.pop(); 23 int c = f.find('0'); 24 for(int i = 0; i < 4; i++){ 25 int t = d[i] + c; 26 string fs = f; 27 if(t < 0 or t > 8) continue; 28 else if( i==0 && c%3==2 ) continue; 29 else if( i==1 && c%3==0 ) continue; 30 swap(fs[t], fs[c]); 31 if( vis2.count(fs) ) return step; 32 if( vis1.count(fs) ) continue; 33 q1.push(fs); 34 vis1.insert(fs); 35 } 36 } 37 step++; 38 sz = q2.size(); 39 while(sz--){ 40 string f = q2.front(); q2.pop(); 41 int c = f.find('0'); 42 for(int i = 0; i < 4; i++){ 43 int t = d[i] + c; 44 string fs = f; 45 if(t < 0 or t > 8) continue; 46 else if( i==0 && c%3==2 ) continue; 47 else if( i==1 && c%3==0 ) continue; 48 swap(fs[t], fs[c]); 49 if( vis1.count(fs) ) return step; 50 if( vis2.count(fs) ) continue; 51 q2.push(fs); 52 vis2.insert(fs); 53 } 54 } 55 } 56 return -1; 57 } 58 int main(){ 59 cin >> s; 60 cout << bfs() << endl; 61 return 0; 62 }
#include<bits/stdc++.h> using namespace std; string s, t = "123804765"; int d[4] = {1, -1, 3, -3}; unordered_set<string> vis1,vis2; int bfs(){ if( s==t ) return 0; queue<string> q1,q2; q1.push(s); q2.push(t); vis1.insert(s); vis2.insert(t); int step = 0; while( q1.size()&&q2.size() ){ if( q1.size() > q2.size() ){ swap(q1,q2); swap(vis1,vis2); } step++; int sz = q1.size(); while(sz--){ string f = q1.front(); q1.pop(); int c = f.find('0'); for(int i = 0; i < 4; i++){ int t = d[i] + c; string fs = f; if(t < 0 or t > 8) continue; else if( i==0 && c%3==2 ) continue; else if( i==1 && c%3==0 ) continue; swap(fs[t], fs[c]); if( vis2.count(fs) ) return step; if( vis1.count(fs) ) continue; q1.push(fs); vis1.insert(fs); } } } return -1; } int main(){ cin >> s; cout << bfs() << endl; return 0; }

浙公网安备 33010602011771号