1.地下迷宮解題
- 題目戳鏈接
- 題目有兩個點需要注意:
- 小青蛙向不同方向走所消耗體力不同(向上3,向下0,向左或向右1)
- 判斷是否能逃離迷宮,並且要求輸出消耗體力最少的路徑
- 這裏我使用廣搜的思想來實現
- 爲了能在遍歷過程中監測體力消耗情況和記錄路徑,我維護了兩個隊列:
- 一個隊列用於記錄路徑,便於最後輸出最佳路徑
- 另一個隊列用於記錄需要訪問的點,並記錄了到達該點後剩餘的體力值以及是從該點的哪個方向來到該點的(防止回溯導致內存溢出)
- 下面是我實現的代碼,佔用內存480k,運行時間4ms(也可以戳我的github)
#include<iostream>
#include<queue>
#include<vector>
#include<string>
using namespace std;
void ShowPath(const vector<pair<int, int>>& path)
{
int len=path.size();
for(int i=0; i<len-1; i++){
cout<<"["<<path[i].first<<","<<path[i].second<<"],";
}
cout<<"["<<path[len-1].first<<","<<path[len-1].second<<"]";
}
int up=3;
int down=0;
int horizon=1;
int main()
{
int n,m,P;
while(cin>>n>>m>>P){
vector<vector<int>> maze(n, vector<int>(m, 0));
for(int i=0; i<n; i++){
for(int j=0; j<m; j++){
cin>>maze[i][j];
}
}
vector<pair<int, int>> best_path;
int minP = P+1;
queue<vector<int>> q;
queue<vector<pair<int, int>>> path_q;
// {x, y, p, from_direction}
// from_direction:up-0, left-1, right-2, down-3
q.push({0, 0, 0, 0});
path_q.push({ {0, 0} });
while(!q.empty()){
// 取出最先加到隊列的座標
vector<int> node = q.front();
vector<pair<int, int>> current_path = path_q.front();
q.pop();
path_q.pop();
// 判斷當前點是否是終點,記錄最佳路徑
if(node[0]==0 && node[1]==m-1 && node[2]<=P){
if(node[2]<minP){
best_path = current_path;
minP = node[2];
}
continue;
}
// 判斷當前是否還有體力
if(node[2]>=P) continue;
// 向下走一步
if(node[0]+1<n && node[3]!=3){
if(maze[node[0]+1][node[1]]==1){
q.push({node[0]+1, node[1], node[2]+down, 0});
current_path.push_back({node[0]+1, node[1]});
path_q.push(current_path);
current_path.pop_back();
}
}
// 向右走一步
if(node[1]+1<m && node[3]!=2){
if(maze[node[0]][node[1]+1]==1){
q.push({node[0], node[1]+1, node[2]+horizon, 1});
current_path.push_back({node[0], node[1]+1});
path_q.push(current_path);
current_path.pop_back();
}
}
// 向左走一步
if(node[1]-1>=0 && node[3]!=1){
if(maze[node[0]][node[1]-1]==1){
q.push({node[0], node[1]-1, node[2]+horizon, 2});
current_path.push_back({node[0], node[1]-1});
path_q.push(current_path);
current_path.pop_back();
}
}
// 向上走一步
if(node[0]-1>=0 && node[3]!=0){
if(maze[node[0]-1][node[1]]==1){
q.push({node[0]-1, node[1], node[2]+up, 3});
current_path.push_back({node[0]-1, node[1]});
path_q.push(current_path);
current_path.pop_back();
}
}
}
if(best_path.empty()){
cout<<"Can not escape!"<<endl;
}
else{
ShowPath(best_path);
}
}
return 0;
}
2.廣搜與深搜
遍歷圖或二叉樹等的方法有兩種:
- 深度優先:用的數據結構是棧, 主要是遞歸實現
- 廣度優先:用的數據結構是隊列,主要是迭代實現
對於深搜,由於遞歸可以利用系統棧,不需要自己維護棧,實現比較簡單。但如果數據結構非常龐大,或者要求可使用的內存有限,深搜其實並不是一個特別好的選擇。
對於廣搜,需要自己維護一個隊列,可以有效控制內存,且由於隊列大小未知,底層存儲的物理結構採用鏈式存儲。
3.僞代碼簡單說明廣搜與深搜
對上面的簡單的二叉樹進行遍歷:
- 廣搜遍歷順序結果:1->2->3->4->5->6
queue q;
q.push(結點1);
while(!q.empty())
{
node = q.pop();
//遍歷操作
if(node has 兒子)
{
for(node的所有兒子) q.push(兒子);
}
}
- 深搜遍歷順序結果:1->2->4->5->3->6
node = 結點1;
DFS(node)
{
if(node==NULL) return;
// 遍歷操作
DFS(node->left);
DFS(node->right);
}