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