蓝桥杯算法竞赛系列第八章——提高篇之广度优先搜索(BFS)
??
歡迎回到:遇見藍橋遇見你,不負代碼不負卿!
目錄
一、廣度優(yōu)先搜索算法(BFS)?
典例一:二叉搜索樹的范圍和
方法一:DFS解法
方法二:BFS解法
典例二:二叉樹的層序遍歷
典例三:二叉樹的層序遍歷 II
典例四:島嶼數(shù)量
方法一:DFS解法?
方法二:BFS解法
五、易錯誤區(qū)
六、藍橋結語:遇見藍橋遇見你,不負代碼不負卿!
【前言】
搜索算法在藍橋中考的還是很頻繁的,之前發(fā)表了二叉樹數(shù)據(jù)結構以及深度優(yōu)先搜索章節(jié),前面還是比較簡單的,這里的廣度優(yōu)先搜索可能稍微復雜那么一丟丟,因為要用到隊列,不過我們可以使用STL容器也是很方便就解決了。?
【聲明】:由于前半部分是基礎知識點定義部分,所以前面一小半部分的贅述筆者是參考力扣官方給出的定義以及《算法筆記》一書。
一、廣度優(yōu)先搜索算法(BFS)?
對于廣度優(yōu)先搜索的定義及特點,力扣官方是這樣給出的:?
廣度優(yōu)先搜索算法(Breadth-First Search,縮寫為 BFS),又稱為寬度優(yōu)先搜索,是一種圖形搜索算法。簡單的說,BFS是從根節(jié)點開始,沿著樹的寬度遍歷樹的節(jié)點。廣度優(yōu)先搜索也廣泛應用于圖論問題中。?
齊頭并進的廣度優(yōu)先遍歷
??
?
說明:遍歷到一個結點時,如果這個結點有左(右)孩子結點,依次將它們加入隊列。?
可能上面講的不夠細節(jié),下面詳細介紹何為”廣搜”:
首先呢,鐵汁們先將之前的DFS章節(jié)前面的迷宮問題再回顧一下,知道何為“死胡同”以及“岔道口”
https://blog.csdn.net/weixin_57544072/article/details/121262172https://blog.csdn.net/weixin_57544072/article/details/121262172https://blog.csdn.net/weixin_57544072/article/details/121262172
前面介紹了深度優(yōu)先搜索,可知DFS是以深度作為第一關鍵詞的,即當岔道口時總是先選擇其中的一條岔道前進,而不管其他岔路,直到碰到死胡同時才返回岔道口并選擇其他岔路。接下來將介紹的廣度優(yōu)先搜索(Breadth First Search, BFS)則是以廣度為第一關鍵詞,當碰到岔道口時,總是先依次訪問從該岔道口能直接到達的所有節(jié)點,然后再按這些節(jié)點被訪問的順序去依次訪問它們能直接到達的所有節(jié)點,以此類推,直到所有節(jié)點都被訪問為止。
這就跟在平靜的水面中投入一顆小石子一樣,水花總是以石子落水處為中心,并以同心圓的方式向外擴散至整個水面,從這點來看和DFS那種沿著一條線前進的思路是完全不同的。
概念部分就講這么多咯,我呢一直是以講題目練習為主,OK,廢話不多說,咱們走起來!
??
典例一:二叉搜索樹的范圍和
原題鏈接:https://leetcode-cn.com/problems/range-sum-of-bst/https://leetcode-cn.com/problems/range-sum-of-bst/https://leetcode-cn.com/problems/range-sum-of-bst/
注意:二叉搜索樹的特點就是左子樹都比根要小,右子樹都比根要大!?
題目描述:
示例1:
輸入:root = [10,5,15,3,7,null,18], low = 7, high = 15 輸出:32示例2:
輸入:root = [10,5,15,3,7,13,18,1,null,6], low = 6, high = 10 輸出:23方法一:DFS解法
思路:
本題很簡單,鐵汁們看代碼里面的注釋就能理解啦。
代碼執(zhí)行:
/*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* };*/int rangeSumBST(struct TreeNode* root, int low, int high){// //方法一:遞歸法// //找邊界// if(root == NULL){// return 0;// }// //左子樹// int leftSum = rangeSumBST(root->left, low, high);// //右子樹// int rightSum = rangeSumBST(root->right, low, high);// int result = leftSum + rightSum;// //判斷根節(jié)點// if(root->val >= low && root->val <= high){// result += root->val;// }// return result;//方法二:DFS//判斷特殊情況if(root == NULL){return 0;}//如果根節(jié)點的值大于high,那么右子樹不滿足,此時只需要判斷左子樹if(root->val > high){return rangeSumBST(root->left, low, high);}//如果根節(jié)點的值小于low,那么左子樹一定不滿足,此時只需要判斷右子樹if(root->val < low){return rangeSumBST(root->right, low, high);}//否則如果根節(jié)點的值在low和high之間,那么三者都需要判斷return root->val + rangeSumBST(root->left, low, high) + rangeSumBST(root->right, low, high); }方法二:BFS解法
思路:
使用廣度優(yōu)先搜索的方法,用一個隊列?q?存儲需要計算的節(jié)點。每次取出隊首節(jié)點時,若節(jié)點為空則跳過該節(jié)點,否則按方法一中給出的大小關系來決定加入隊列的子節(jié)點。
代碼執(zhí)行:
/*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/ class Solution { public:int rangeSumBST(TreeNode* root, int low, int high) {queue<TreeNode*>q;//定義一個隊列//首先將根節(jié)點入隊if(root)q.push(root);int res = 0;while(!q.empty())//隊列非空時循環(huán)繼續(xù){int n = q.size();//隊列的長度for(int i = 0; i < n; i++){TreeNode* t = q.front();//訪問隊首元素q.pop();//隊首元素出隊//注意輸入格式中有空節(jié)點,所以要加一個判斷//當訪問到的節(jié)點是空節(jié)點時,跳過該節(jié)點if(t == nullptr){continue;}//注意哦,由于是二叉搜索樹,有它自己的特性//節(jié)點的值大于high時,只需要左子樹入隊if(t->val > high)q.push(t->left);//節(jié)點的值小于low時,只需要右子樹入隊if(t->val < low)q.push(t->right);//節(jié)點的值在low和high之間時,需要加上該節(jié)點值以及左右子樹入隊if(t->val >= low && t->val <= high){res += t->val;q.push(t->left);q.push(t->right);}}}return res;} };??
典例二:二叉樹的層序遍歷
原題鏈接:https://leetcode-cn.com/problems/binary-tree-level-order-traversal/https://leetcode-cn.com/problems/binary-tree-level-order-traversal/https://leetcode-cn.com/problems/binary-tree-level-order-traversal/
題目描述:
示例:
思路:
代碼中注釋給得很詳細咯,快去康康叭。?
代碼執(zhí)行:
class Solution { public:/*** * @param root TreeNode* * @return int整型vector<vector<>>*/vector<vector<int> > levelOrder(TreeNode* root) {// write code herequeue<TreeNode*>q;//定義一個隊列if(root)q.push(root);vector<vector<int> >ans;//定義一個二維數(shù)組用于存放遍歷結果while(!q.empty()){//隊列為空時停下來int n = q.size();//注意哦,n不能放在循環(huán)外邊,隊列中的元素是在變化的vector<int>tmp;//定義一維數(shù)組用于存放每一層的節(jié)點(注意一維數(shù)組定義的位置)for(int i = 0;i < n;i++){TreeNode* t = q.front();//訪問隊首元素q.pop();//隊首元素出隊tmp.push_back(t->val);//將隊首元素的值存放到該層的一維數(shù)組中if(t->left)//左子節(jié)點入隊q.push(t->left);if(t->right)//右子節(jié)點入隊q.push(t->right);}ans.push_back(tmp);//將第一層的一維數(shù)組存放二維數(shù)組中}return ans;} };??
典例三:二叉樹的層序遍歷 II
原題鏈接:https://leetcode-cn.com/problems/binary-tree-level-order-traversal-ii/https://leetcode-cn.com/problems/binary-tree-level-order-traversal-ii/https://leetcode-cn.com/problems/binary-tree-level-order-traversal-ii/
題目描述:
示例:
思路:
哈哈,本題主要是讓大家熟練掌握二叉樹的層序遍歷才添加進來的,本題呢,直接將最后存放到二維數(shù)組中的數(shù)據(jù)反轉(#include<algorithm>頭文件下)即可。?
代碼執(zhí)行:
/*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/ class Solution { public:vector<vector<int>> levelOrderBottom(TreeNode* root) {//定義一個隊列queue<TreeNode*>q;//定義一個二維數(shù)組用于返回結果vector<vector<int> >ans;//先將根節(jié)點入隊if(root)q.push(root);while(!q.empty()){//定義一個一維數(shù)組用于存放每一層節(jié)點的值vector<int>temp;int n = q.size();//隊列的長度for(int i = 0; i < n; i++){//訪問隊首元素TreeNode* t = q.front();//隊首元素出隊q.pop();//將隊首元素的值存放到一維數(shù)組中temp.push_back(t->val);//訪問左子樹if(t->left)q.push(t->left);//訪問右子樹if(t->right)q.push(t->right);}ans.push_back(temp);}reverse(ans.begin(), ans.end());//反轉二維數(shù)組中的結果return ans;} };??
典例四:島嶼數(shù)量
原題鏈接:https://leetcode-cn.com/problems/number-of-islands/https://leetcode-cn.com/problems/number-of-islands/https://leetcode-cn.com/problems/number-of-islands/
題目描述:
示例1:
輸入:grid = [["1","1","1","1","0"],["1","1","0","1","0"],["1","1","0","0","0"],["0","0","0","0","0"] ] 輸出:1示例2:
輸入:grid = [["1","1","0","0","0"],["1","1","0","0","0"],["0","0","1","0","0"],["0","0","0","1","1"] ] 輸出:3方法一:DFS解法?
思路:
為了求出島嶼的數(shù)量,我們可以掃描整個二維網(wǎng)格。如果一個位置為 1,則以其為起始節(jié)點開始進行深度優(yōu)先搜索。在深度優(yōu)先搜索的過程中,每個搜索到的 1 都會被重新標記為 0(也就是下面代碼中所說的“同化”)
代碼執(zhí)行:
int numIslands(char** grid, int gridSize, int* gridColSize){//找遞歸邊界if(grid == NULL || gridSize == 0){return 0;}int row = gridSize;//行數(shù)int col = *gridColSize;//列數(shù)int count = 0;//用于計數(shù)int i = 0;int j = 0;//遍歷這個二維網(wǎng)格for(i = 0; i < row; i++){for(j = 0; j < col; j++){if(grid[i][j] == '1'){count++;//將‘1’周圍的‘1’全部同化成0dfs(grid, i, j, row, col);}}}return count; }void dfs(char** grid, int x, int y, int row, int col) {//判斷特殊情況if(x < 0 || x >= row || y < 0 || y >= col || grid[x][y] == '0')//注意哦,下標等于行數(shù)列數(shù)時也是不可以的喲{return;}grid[x][y] = '0';//將‘1’同化成0dfs(grid, x - 1, y, row, col);dfs(grid, x + 1, y, row, col);dfs(grid, x, y - 1, row, col);dfs(grid, x, y + 1, row, col); }方法二:BFS解法
思路:
同樣地,我們也可以使用廣度優(yōu)先搜索代替深度優(yōu)先搜索。
為了求出島嶼的數(shù)量,我們可以掃描整個二維網(wǎng)格。如果一個位置為 1,則將其加入隊列(注意哦,是將其對應的下標存放到隊列中的)開始進行廣度優(yōu)先搜索。在廣度優(yōu)先搜索的過程中,每個搜索到的 1 都會被重新標記為 0。直到隊列為空,搜索結束。
代碼執(zhí)行:
//由于需要用到queue和pair容器,所以選擇C++編寫代碼 class Solution { public:int numIslands(vector<vector<char>>& grid) {int nr = grid.size();//行數(shù)if (!nr) return 0;//判斷邊界情況int nc = grid[0].size();//列數(shù)int num_islands = 0;//用于計數(shù)//遍歷二維網(wǎng)格for (int r = 0; r < nr; ++r) {for (int c = 0; c < nc; ++c) {//滿足條件時進來,否則進入下一次循環(huán)if (grid[r][c] == '1') {++num_islands;grid[r][c] = '0';//定義一個隊列,用于存放下標信息//注意對pair的理解,可以看作是內部有兩個元素的結構體queue<pair<int, int>> neighbors;neighbors.push({r, c});//將'1'的下標信息入隊while (!neighbors.empty()) {pair<int,int> rc = neighbors.front();//訪問隊首元素neighbors.pop();//隊首元素出隊int row = rc.first;//隊首元素所對應的行號int col = rc.second;//隊首元素所對應的列號//將它上下左右的‘1’都同化成‘0’//上//row - 1 >= 0 判斷位置是否合法if (row - 1 >= 0 && grid[row-1][col] == '1') {neighbors.push({row-1, col});grid[row-1][col] = '0';}//下//row + 1 < nr 判斷位置是否合法if (row + 1 < nr && grid[row+1][col] == '1') {neighbors.push({row+1, col});grid[row+1][col] = '0';}//左//col - 1 >= 0 判斷位置是否合法if (col - 1 >= 0 && grid[row][col-1] == '1') {neighbors.push({row, col-1});grid[row][col-1] = '0';}//右//col + 1 < nc 判斷位置是否合法if (col + 1 < nc && grid[row][col+1] == '1') {neighbors.push({row, col+1});grid[row][col+1] = '0';}}}}}return num_islands;} };五、易錯誤區(qū)
最后需要指出的是,當使用STL的queue時,元素入隊的push操作只是制造了該元素的一個副本入隊,因此在入隊后對原元素的修改是不會影響隊列中的副本,同樣的,對隊列中副本的修改也不會改變原元素,需要注意由此可能引入的bug!?
例如下面這個例子:
#include<cstdio> #include<queue>using namespace std;struct node {int data; }a[10];int main() {queue<int> q;for (int i = 1; i <= 3; i++){a[i].data = i;//a[1] = 1, a[2] = 2, a[3] = 3q.push(a[i]);}//嘗試直接把隊首元素(即a[1])的數(shù)據(jù)域改為100q.front().data = 100;//事實上對隊列元素的修改無法改變原元素printf("%d %d %d\n", a[1].data, a[2].data, a[3].data);//輸出1 2 3 注意哦,并不是100 2 3//嘗試直接修改a[1]的數(shù)據(jù)域為200(即a[1],上面已經(jīng)修改為100)a[1].data = 200;//事實上對原元素的修改也無法改變隊列中的元素printf("%d\n", q.front().data);//輸出100 注意哦,并不是200return 0; }發(fā)現(xiàn)上面出現(xiàn)的問題了嗎,這就是說,當需要對隊列中的元素進行修改而不僅僅是訪問時,隊列中存放的元素最好不要是元素本身,而是它們對應的編號(如果是數(shù)組的話則是下標)。
例如把上面的程序改成下面這樣:
#include<stdio.h> #include<queue> using namespace std;struct node {int data; }a[10];int main() {queue<int> q;//q存放數(shù)組中元素的下標for (int i = 1; i <= 3; i++){a[i].data = i;//a[1] = 1, a[2] = 2, a[3] = 3q.push(i);//這里是將數(shù)組下標i入隊,而不是節(jié)點a[i]本身}a[q.front()].data = 100;//q.front()為下標,通過a[q.front()]即可修改原元素printf("%d\n", a[1].data);//輸出100return 0; }?
六、藍橋結語:遇見藍橋遇見你,不負代碼不負卿!
搜索的基礎部分到這里就結束咯,不過嘞,不會這么簡單就結束掉的,后面的話筆者還會出一個藍橋杯沖刺專欄,還有大量的練習以及相當一部分的真題!OK,今天就到這里咯,下一章節(jié)講的是動態(tài)規(guī)劃(DP)哈。
如果大家有所收獲的話,麻煩給俺個三連唄,萬分感謝,抱拳了哈。
總結
以上是生活随笔為你收集整理的蓝桥杯算法竞赛系列第八章——提高篇之广度优先搜索(BFS)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何处理phpmyadmin中访问被拒绝
- 下一篇: c语言之结构体