BFS_經典案例

案例1 - 迷宮最短路線問題

牛客:迷宮最短路線問題

struct node {
	node(int x, int y)
		: _x(x)
		, _y(y)
	{}
	int _x;
	int _y;
};

bool BFS(vector<vector<int>> graph, int startx, int starty, int destx, int desty) {
	//迷宮大小
	int row = graph.size();
	int col = graph[0].size();
	//存儲迷宮位置
	queue<node> q;

	//用於標記走過的位置的
	vector<vector<bool>> book(row, vector<bool>(col, false));

	q.push(node(startx, starty));
	book[startx][starty] = true;
	
	//代表四個方位
	int nextPos[4][2] = { {0, -1}, {0, 1}, {-1, 0}, {1, 0} };
	
	//標記是否可以走出去
	bool flag = false;
	
	while (!q.empty()) {
		for (int i = 0; i < 4; ++i) {
			//計算新位置
			int newx = q.front()._x + nextPos[i][0];
			int newy = q.front()._y + nextPos[i][1];

			//越界,直接進行下一次循環
			if (newx >= row || newx < 0 || newy >= col || newy < 0)
				continue;

			//如果新的位置沒有障礙,並且沒有走過,保存新的位置
			if (graph[newx][newy] == 0 && book[newx][newy] == false) {
				q.push(node(newx, newy));
				book[newx][newy] = true;
			}

			//如果新的位置是目標位置,結束查找
			if (newx == destx && newy == desty) {
				flag = true;
				break;
			}

			//否則新位置有障礙,或者新位置已經走過,繼續循環下一個位置
		}
		//當前位置的四個方向都進行了尋找,如果找到了結束,找到的路徑一定是最短路徑
		//如果沒有找到以四個方向的新節點爲起點開始新的一輪尋找
		if (flag == true)
			break;
		q.pop();
	}
	//最後只需要返回 flag 就可以知道是否走出迷宮
	return flag;
}

案例2 - 員工重要性

力扣:員工重要性

class Solution {
public:
    int getImportance(vector<Employee*> employees, int id) {
        //將員工信息存儲在鍵值對中 <id, Employee*>,方便使用
        unordered_map<int, Employee*> m;
        for (int i = 0; i < employees.size(); ++i) {
            m.insert(make_pair(employees[i]->id, employees[i]));
        }
        
        //用一個隊列,計算當前員工時將該員工的下屬(id)都添加到隊列中
        queue<int> q;
        q.push(id);
        
        //返回值:當前員工和他下屬的重要度之和
        int result = 0;
        //隊列不爲空,說明依舊有下屬的重要度沒有計算完畢
        while (!q.empty()) {
            //加和當前員工的重要度
            result += m[q.front()]->importance;
            //將當前員工的下屬添加到隊列中
            vector<int> v(m[q.front()]->subordinates); //下屬表
            for (int i = 0; i < v.size(); ++i) {
                q.push(m[v[i]]->id);
            }
            //添加完下屬員工後,從隊列中將當前員工刪除(已經計算完畢)
            q.pop();
        }
        return result;
    }
};

案例3 - N叉樹的層序遍歷

力扣:N 叉樹的層序遍歷

class Solution {
public:
    vector<vector<int>> levelOrder(Node* root) {
        if (root == nullptr)
            return vector<vector<int>>();
        //先打印當前節點的值,然後將利用當前節點帶出它的所有孩子節點(下一層)
        //將節點都添加到隊列中,就可以實現層序遍歷的效果
        queue<Node*> q;
        //還有一個問題是怎麼將每一層分別存儲在二維數組中??
        //建立一個數組,計算隊列中元素個數 qsize ,也就是當前行的元素個數,將當前行元素都添加到數組中
        //並且將所有孩子節點添加到隊列中,刪除隊列的前 qsize 個元素(上一行的元素)
        vector<int> v;
        vector<vector<int>> result;
        q.push(root);
        while (!q.empty()) {
            int qsize = q.size();
            while (qsize--) {
                //q 中的元素就是當前行的所有元素,循環 q.size() 次
                //將當前行的所有元素 插入到二維數組的一行中
                //然後帶出自己的孩子,然後將其刪除
                v.push_back(q.front()->val); //插入到當前行
                for (auto& e : q.front()->children) {
                    q.push(e); //帶出所有孩子節點
                }
                //刪除
                q.pop();
            }
            //循環結束後: v 存儲的是一行的結果,q 中將上一行的元素全部刪除,q 中存儲的是新的一行的所有節點
            result.push_back(v);
            v.clear();  //清空
        }
        return result;
    }
};

案例4 - 腐爛的橘子

力扣:腐爛的橘子

class Solution {
public:
    int orangesRotting(vector<vector<int>>& grid) {
        if (grid.empty())
            return 0;
        //廣度優先搜索
        //第一步先將所有腐爛的橘子添加到隊列中
        //然後開始擴散,擴散的時候將新鮮橘子入隊,直到沒有新鮮的橘子結束
        //然後最終循環判斷是否還存在新鮮橘子
        //如果存在返回-1,否則返回擴散的步數
        int row = grid.size();
        int col = grid[0].size();
        queue<pair<int, int>> q;
        
        //最初添加所有腐爛橘子
        for (int i = 0; i < row; ++i) {
            for (int j = 0; j < col; ++j) {
                if (grid[i][j] == 2)
                    q.push(make_pair(i, j));
            }
        }
        
        //step 記錄擴散的步數,從-1開始,因爲即使不需要擴散下面對step仍然進行了一次++操作
        //但是如果並沒有橘子,下面將不會進行++操作,也就是說step最終爲-1的情況就是沒有橘子的情況
        //沒有橘子的情況step應該爲0,只需要最終進行處理
        int step = -1;
        //四個方向
        int nextPos[4][2] = {{0,1}, {0,-1}, {-1,0}, {1,0}};
        while (!q.empty()) {
            int qsize = q.size();
            while (qsize--) {
                for (int i = 0; i < 4; ++i) {
                    int nx = q.front().first + nextPos[i][0];
                    int ny = q.front().second + nextPos[i][1];
                    if (nx < 0 || nx >= row || ny < 0 || ny >= col)
                        continue;
                    //新鮮橘子,即將腐爛
                    if (grid[nx][ny] == 1) {
                        q.push(make_pair(nx, ny));
                        grid[nx][ny] = 2;
                    }
                }
                //當前位置的四個方向都被感染
                q.pop();
            }
            //所有腐爛橘子都進行了一次感染,step++
            step++;
            //此時隊列中的橘子都是新腐爛的橘子,舊腐爛的都已刪除
        }
        
        //最後循環查找,有沒有感染不到的橘子,有返回 -1
        for (int i = 0; i < row; ++i) {
            for (int j = 0; j < col; ++j) {
                if (grid[i][j] == 1)
                    return -1;
            }
        }
        return step < 0 ? 0 : step;
    }
};

案例5 - 單詞接龍

力扣:單詞接龍

class Solution {
public:
    int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
        //將字典存儲在 unordered_set 中,查詢效率高
        unordered_set<string> wordDict(wordList.begin(), wordList.end());
        //記錄已經訪問過的單詞
        unordered_set<string> visits;
        queue<string> q;
        //初始化操作
        q.push(beginWord);
        visits.insert(beginWord);
        
        //最終你返回結果
        int result = 1;
        while (!q.empty()) {
            //所有滿足當前步驟的單詞總數
            int qsize = q.size();
            while (qsize--) {
                string fWord = q.front();
                q.pop();
                for (int i = 0; i < fWord.size(); ++i) {
                    //對字符串的每一個字符進行替換操作,但是隻會替換一個字符
                    string newWord = fWord;
                    for (auto ch = 'a'; ch <= 'z'; ++ch) {
                        newWord[i] = ch;
                        //替換完畢後檢測 fWord 是否在字典中
                        //不在字典中,說明轉換有誤,continue,在字典中但是已經轉換過不能再次轉換continue
                        //在字典中並且沒有轉換過那麼進行相應操作
                        if (!wordDict.count(newWord) || visits.count(newWord))
                            continue;
                        //轉換成功,則在上一步轉換的基礎上+1
                        if (newWord == endWord)
                            return result + 1;
                        
                        //還沒有轉化成功但是當前轉換的單詞可能是轉換過程的一部分
                        visits.insert(newWord);
                        q.push(newWord);
                    }
                }
            }
            //進行了一次轉換,轉換一次後得到的字符串如果匹配到了字典中,就會將這個字符串添加到隊列中
            //這就是一次的轉換得到的所有可能結果
            //在利用這些結果進行下一次轉換
            result += 1;
        }
        //轉換不成功
        return 0;
    }
};

案例6 - 最小基因變化

力扣:最小基因變化

class Solution {
public:
    int minMutation(string start, string end, vector<string>& bank) {
        if (bank.empty())
            return -1;
        //將基因庫存儲在 unordered_set 中,查找效率高
        unordered_set<string> mBank(bank.begin(), bank.end());
        //存儲已經轉換過的字符串
        //解釋: 第2次轉換和第3次轉換的路徑不同但是最終都轉換成爲了同一個字符串,那麼只需要處理一個就ok
        unordered_set<string> visits;
        
        //所有可能的字符
        vector<char> ch = {'A', 'C', 'G', 'T'};
        
        queue<string> q;
        //初始化
        q.push(start);
        visits.insert(start);
        int step = 0;
        
        while (!q.empty()) {
            int qsize = q.size();
            while (qsize--) {
                string curGene = q.front();
                q.pop();
                //利用當前基因序列找出所有可能的下一步(只改動一個字符,並且在庫中)
                for (int i = 0; i < 8; ++i) {
                    string newGene = curGene;
                    for (int j = 0; j < 4; ++j) {
                        newGene[i] = ch[j];
                        //成功轉換
                        if (newGene == end && mBank.count(newGene))
                            return (step + 1);

                        //如果基因庫中存在 newGene 並且沒有處理過 --> 可以進行轉換,添加到隊列中
                        if (mBank.count(newGene) && !visits.count(newGene)) {
                            q.push(newGene);
                            visits.insert(newGene);
                        }
                    }
                }
            }
            //一次 while 循環結束意味着,當前步驟的所有基因序列都處理完畢
            //此時隊列中存儲的是下一個步驟可能到達的基因序列(通過上一次步驟轉換而來,而且庫中存在)
            step++;
        }
        //轉換失敗
        return -1;
    }
};

案例7 - 打開轉盤鎖

力扣:打開轉盤鎖

class Solution {
public:
    int openLock(vector<string>& deadends, string target) {
        //unordered_set 存儲查找效率高
        unordered_set<string> deadEnds(deadends.begin(), deadends.end());
        if (deadEnds.find("0000") != deadEnds.end())
            return -1;
        //存儲已經處理過的數字
        unordered_set<string> visits;
        
        queue<string> q;
        int step = 0;
        
        //初始化
        q.push("0000");
        visits.insert("0000");
        
        while (!q.empty()) {
            int qsize = q.size();
            while (qsize--) {
                string curNum = q.front();
                q.pop();
                
                if (curNum == target)
                    return step;
                for (int i = 0; i < 4; ++i) {
                    string newNum1 = curNum;
                    string newNum2 = curNum;
                    
                    //撥動一次可能有兩種情況, -- 或 ++
                    newNum1[i] = newNum1[i] == '0' ? '9' : newNum1[i] - 1;
                    //如果撥動過的數字不在死亡列表中並且沒有處理過,添加到隊列中
                    if (!deadEnds.count(newNum1) && !visits.count(newNum1)) {
                        q.push(newNum1);
                        visits.insert(newNum1);
                    }
                    
                    newNum2[i] = newNum2[i] == '9' ? '0' : newNum2[i] + 1;
                    if (!deadEnds.count(newNum2) && !visits.count(newNum2)) {
                        q.push(newNum2);
                        visits.insert(newNum2);
                    }
                }
            }
            step++;
        }
        return -1;
    }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章