C++寬度優先搜索算法:隊列與優先級隊列

? ? ? ? 本期我們就來深入學習一下C++算法中一個很重要的算法思想:寬度優先搜索算法

? ? ? ? 寬度優先算法是一個應用十分廣泛的算法思想,涉及的領域也十分繁多,因此本篇我們先只涉獵它的一部分算法題:隊列/優先級隊列,后續我們會進一步地補充。

? ? ? ? 相關題解代碼已經上傳至作者的個人gitee:樓田莉子/C++算法學習喜歡請點個贊謝謝

目錄

基本概念

算法步驟

時間復雜度分析

應用場景

廣度優先搜索(BFS) vs. 深度優先搜索(DFS)算法對比

1、N叉樹的層序遍歷

2、二叉樹的鋸齒形層序遍歷

3、二叉樹最大寬度

4、在每個樹行中找最大值

5、最后一塊石頭的重量

6、數據流中的第K大元素

為什么選擇小根堆?

大根堆的缺點

7、前K個高頻單詞


基本概念

????????寬度優先搜索(Breadth-First Search,簡稱BFS)是一種用于遍歷或搜索樹或圖數據結構的算法。它從根節點開始,首先訪問所有相鄰節點,然后再逐層向下訪問更遠的節點。BFS屬于盲目搜索算法,不使用問題領域的任何啟發式信息。

算法步驟

  1. 初始化

    • 創建一個隊列Q用于存儲待訪問節點
    • 將起始節點S標記為已訪問(通常使用顏色標記或布爾數組)
    • 將S加入隊列Q
  2. 循環處理

    • 當Q不為空時: a. 從Q中取出隊首節點V b. 訪問節點V(根據具體應用可能是檢查、處理等操作) c. 將V的所有未被訪問的鄰接節點標記為已訪問并加入隊列Q
  3. 終止條件

    • 當隊列為空時,算法結束

時間復雜度分析

  • 鄰接表表示:O(V + E),其中V是頂點數,E是邊數
  • 鄰接矩陣表示:O(V2)
  • 空間復雜度:O(V)(最壞情況下需要存儲所有節點)

應用場景

  1. 最短路徑問題

    • 在無權圖中尋找兩個節點間的最短路徑
    • 示例:迷宮求解、社交網絡中尋找最少中間人
  2. 網絡爬蟲

    • 搜索引擎爬蟲按層級抓取網頁
    • 先抓取種子頁面,然后抓取這些頁面鏈接的所有頁面
  3. 廣播網絡

    • 在計算機網絡中傳播信息包
    • 在P2P網絡中定位資源
  4. 社交網絡分析

    • 尋找某人的N度人際關系
    • 計算社交影響力
  5. 連通性檢測

    • 檢查圖中所有節點是否連通
    • 尋找圖中的連通分量

廣度優先搜索(BFS) vs. 深度優先搜索(DFS)算法對比

特性維度廣度優先搜索 (BFS)深度優先搜索 (DFS)
核心數據結構隊列 (Queue)?(FIFO)棧 (Stack)?(LIFO) (或系統的遞歸調用棧)
遍歷思想/順序逐層遍歷?(Level Order)。從起點開始,優先訪問所有相鄰節點,再訪問相鄰節點的相鄰節點。一路到底?(Depth Order)。從起點開始,沿著一條路徑盡可能深地探索,直到盡頭再回溯。
空間復雜度O(b^d)?(對于樹結構,b為分支因子,d為目標所在深度)
在最壞情況下,需要存儲一整層的節點,對于空間消耗較大。
O(h)?(對于樹結構,h為樹的最大深度)
通常只需存儲當前路徑上的節點,空間消耗相對較小。
完備性。如果解存在,BFS(在有限圖中)一定能找到解。(無限圖)。如果樹深度無限且解不在左側分支,DFS可能永遠無法找到解。在有限圖中是完備的。
最優性(未加權圖)。BFS按層擴展,首次找到的目標節點一定是經過邊數最少的(最短路徑)。DFS找到的解不一定是路徑最短的,它取決于遍歷順序和運氣。
常見應用場景1.?尋找最短路徑(未加權圖)
2. 系統性的狀態空間搜索(如謎題求解)
3. 查找網絡中所有節點(如社交網絡的“好友推薦”)
1.?拓撲排序
2.?檢測圖中環
3.?路徑查找(所有可能解,如迷宮問題)
4. 圖的連通分量分析
實現方式通常使用迭代隊列實現。使用遞歸實現最簡潔,也可使用迭代顯式棧(Stack)?實現。
直觀比喻“水波紋”擴散:像一塊石頭投入水中,漣漪一圈一圈地向外擴展。“走迷宮”:選擇一條路一直走下去,直到死胡同,然后返回到最后一個岔路口選擇另一條路。

1、N叉樹的層序遍歷

? ? ? ? 算法思想:

????????

/*
// Definition for a Node.
class Node {
public:int val;vector<Node*> children;Node() {}Node(int _val) {val = _val;}Node(int _val, vector<Node*> _children) {val = _val;children = _children;}
};
*/class Solution {
public:vector<vector<int>> levelOrder(Node* root) {if(root==nullptr) return {};vector<vector<int>> ret;queue<Node*>q;q.push(root);while(!q.empty()){vector<int>level;int n=q.size();//求本層的個數for(int i=0;i<n;i++){Node* cur=q.front();q.pop();level.push_back(cur->val);for(auto& x:cur->children)//下一層結點入隊{q.push(x);}}ret.push_back(level);}return ret;}
};

2、二叉樹的鋸齒形層序遍歷

? ? ? ? 算法思想:

  1. 廣度優先搜索(BFS):使用隊列進行標準的層序遍歷,按層處理節點。

  2. 方向標志:使用一個布爾變量?Is?來標記當前層是否需要反轉。初始為?false,表示第一層從左到右。

/*** 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>> zigzagLevelOrder(TreeNode* root) {if(root==nullptr) return {}; // 如果根節點為空,返回空數組vector<vector<int>>ret; // 存儲最終結果queue<TreeNode*>q;  // 輔助隊列用于BFSq.push(root); // 將根節點加入隊列bool Is=false; // 方向標志,false表示從左到右,true表示從右到左while(!q.empty()){   vector<int>level; // 存儲當前層的節點值int n=q.size(); // 當前層的節點數for(int i=0;i<n;i++){auto it=q.front(); // 取出隊首節點q.pop();level.push_back(it->val); // 將節點值加入當前層數組if(it->left) q.push(it->left); // 將左子節點加入隊列if(it->right) q.push(it->right); // 將右子節點加入隊列}   if(Is) // 如果需要反轉當前層{reverse(level.begin(),level.end()); // 反轉當前層數組ret.push_back(level); // 將反轉后的數組加入結果Is=!Is; // 切換方向標志}  else // 如果不需要反轉{ret.push_back(level); // 直接加入結果Is=!Is; // 切換方向標志}} return ret; // 返回最終結果}
};

3、二叉樹最大寬度

? ? ? ? 算法思想:利用數組存儲二叉樹的方式,給結點編號

? ? ? ? 細節:下標有可能溢出

/*** 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 widthOfBinaryTree(TreeNode* root) {vector<pair<TreeNode*,unsigned int>>q;//數組模擬隊列q.push_back({root,1});unsigned int ret=0;//統計最終結果while(q.size()){//先更新auto&[x1,y1]=q[0];auto&[x2,y2]=q.back();ret=max(ret,y2-y1+1);//下一層進隊vector<pair<TreeNode*,unsigned int>>tmp;//下一層進第二個組for(auto&[x,y]:q){if(x->left) tmp.push_back({x->left,y*2});if(x->right) tmp.push_back({x->right,y*2+1});}q=tmp;}return ret;}
};

4、在每個樹行中找最大值

? ? ? ? 算法思想:

/*** 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<int> largestValues(TreeNode* root) {if(root==nullptr) return {};vector<int>ret;queue<TreeNode*>q;q.push(root);while(!q.empty()){TreeNode*cur=root;int n=q.size();int a=INT_MIN;//初始化為無窮小for(int i=0;i<n;i++){auto it=q.front();q.pop();a=max(a,it->val);if(it->left) q.push(it->left);if(it->right) q.push(it->right);}ret.push_back(a);}return ret;}
};

5、最后一塊石頭的重量

????????算法思想:

class Solution {
public:int lastStoneWeight(vector<int>& stones) {// 創建一個大根堆(優先級隊列默認是最大堆)// 這意味著隊列頂部始終是當前最大的元素priority_queue<int> q;// 將所有石頭重量加入優先級隊列for(auto& x : stones){q.push(x);}// 循環處理,直到隊列中只剩下一塊石頭或沒有石頭while(q.size() > 1){// 取出當前最重的石頭int a = q.top();q.pop();// 取出當前第二重的石頭int b = q.top();q.pop();// 如果兩塊石頭重量不相等,將差值重新加入隊列if(a > b) q.push(a - b);// 如果相等,兩者都粉碎,不需要操作(相當于都不加入隊列)}// 檢查隊列是否還有剩余石頭:如果有,返回頂部元素(最后一塊石頭的重量);否則返回0return q.size() ? q.top() : 0;}
};

6、數據流中的第K大元素

? ? ? ? 算法思想:Top-K問題

? ? ? ? 堆(O(N*logk))或者快速選擇算法(O(N))

? ? ? ? 1、創建大根堆或者小根堆

? ? ? ? 2、元素一個一個進堆

? ? ? ? 3、判斷堆的大小是否超過K

class KthLargest {//創建一個大小為k的小根堆priority_queue<int,vector<int>,greater<int>>heap;int _k;public:KthLargest(int k, vector<int>& nums) {_k=k;for(auto&x:nums){heap.push(x);if(heap.size()>_k) heap.pop();}}int add(int val) {heap.push(val);if(heap.size()>_k) heap.pop();return heap.top();}
};/*** Your KthLargest object will be instantiated and called as such:* KthLargest* obj = new KthLargest(k, nums);* int param_1 = obj->add(val);*/

為什么選擇小根堆?

  • 問題需求:我們需要實時返回數據流中第K大的元素。這意味著我們需要維護前K大的元素,但只關心其中最小的那個(即第K大的元素)。

  • 小根堆的優勢

    • 維護一個大小為K的小根堆,堆頂元素即為第K大的元素(因為堆頂是堆中最小的元素,而堆中所有元素都是當前最大的K個元素)。

    • 添加新元素時,如果新元素大于堆頂元素,則替換堆頂并調整堆,時間復雜度為O(log K)。查詢操作直接返回堆頂,時間復雜度為O(1)。

    • 整體效率高,適合數據流頻繁添加和查詢的場景。

大根堆的缺點

  • 效率低下:如果使用大根堆存儲所有元素,每次查詢第K大的元素時,需要從堆中彈出K-1個元素才能訪問到第K大的元素。這會破壞堆的結構,且每次查詢的時間復雜度為O(K log n)(因為彈出操作需要調整堆)。

  • 空間浪費:大根堆需要存儲所有元素,而不僅僅是前K大的元素,因此空間復雜度為O(n),而不是O(K)。

  • 查詢成本高:每次查詢都需要執行彈出和重新插入操作(或者復制堆),導致整體性能較差,尤其當K較大或數據流很大時,無法滿足實時性要求。

大根堆的代碼(不推薦這個寫法)

#include <vector>
#include <queue>
using namespace std;class KthLargest {
private:priority_queue<int> maxHeap; // 大根堆,存儲所有元素int k;public:KthLargest(int k, vector<int>& nums) {this->k = k;for (int num : nums) {maxHeap.push(num);}}int add(int val) {maxHeap.push(val); // 添加新元素到大根堆// 創建臨時堆副本,避免破壞原堆priority_queue<int> temp = maxHeap;// 彈出K-1個最大元素,使堆頂變為第K大的元素for (int i = 0; i < k - 1; i++) {temp.pop();}return temp.top(); // 返回第K大的元素}
};

7、前K個高頻單詞

class Solution 
{
public://老方法:stable_sort函數(穩定排序)// 仿函數// struct compare// {//     bool operator()(const pair<string ,int>&kv1,const pair<string ,int>&kv2)//     {//         return kv1.second>kv2.second;//     }// };// vector<string> topKFrequent(vector<string>& words, int k) // {//     map<string ,int>countMap;//     for(auto& str:words)//     {//         //統計次數//         countMap[str]++;//     }//     //數據量很大的時候要建小堆//     //數據量不大用大堆//     //但是這里要按頻率所以不建議用小堆//     //用排序和大堆都差不多//     //不可以用sort直接去排序//     //sort要求是隨機迭代器,只用string、vector、deque支持//     vector<pair<string,int>>v(countMap.begin(),countMap.end());//     //sort(v.begin(),v.end(),compare());//     //sort不是一個穩定的排序//     stable_sort(v.begin(),v.end(),compare());//穩定的排序算法//     vector<string>ret;//     //取出k個//     for(int i=0;i<k;i++)//     {//         ret.push_back(v[i].first);//     }//     return ret;//方法二:仿函數//仿函數// struct compare// {//     bool operator()(const pair<string ,int>&kv1,const pair<string ,int>&kv2)//     {//         return kv1.second>kv2.second||(kv1.second==kv2.second&&kv1.first<kv2.first);//     }// };// vector<string> topKFrequent(vector<string>& words, int k) // {//     map<string ,int>countMap;//     for(auto& str:words)//     {//         //統計次數//         countMap[str]++;//     }//     //數據量很大的時候要建小堆//     //數據量不大用大堆//     //但是這里要按頻率所以不建議用小堆//     //用排序和大堆都差不多//     //不可以用sort直接去排序//     //sort要求是隨機迭代器,只用string、vector、deque支持//     vector<pair<string,int>>v(countMap.begin(),countMap.end());//     //仿函數可以控制比較邏輯//     sort(v.begin(),v.end(),compare());//穩定的排序算法//     vector<string>ret;//     //取出k個//     for(int i=0;i<k;i++)//     {//         ret.push_back(v[i].first);//     }//     return ret;//方法三:優先級隊列struct compare{bool operator()(const pair<string, int>& kv1, const pair<string, int>& kv2) const{// 比較邏輯:頻率高優先,頻率相同則字典序小優先return kv1.second < kv2.second || (kv1.second == kv2.second && kv1.first > kv2.first);}};vector<string> topKFrequent(vector<string>& words, int k) {map<string, int> countMap;for(auto& str : words){countMap[str]++;}//數據量很大的時候要建小堆//數據量不大用大堆//但是這里要按頻率所以不建議用小堆//用排序和大堆都差不多//不可以用sort直接去排序//sort要求是隨機迭代器,只用string、vector、deque支持//建立大堆//priority_queue默認為大堆//不寫仿函數的時候priority_queue按pair比,pair默認按小于比// - 元素類型: pair<string, int>// - 容器類型: vector<pair<string, int>>// - 比較器類型: compare (去掉括號)priority_queue<pair<string, int>,vector<pair<string, int>>,compare> pq(countMap.begin(), countMap.end());vector<string> ret;for(int i = 0; i < k; i++){ret.push_back(pq.top().first);pq.pop();}return ret;}
};

? ? ? ? 本期內容就到這里,喜歡請點個贊謝謝

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/96580.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/96580.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/96580.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

類的property屬性

??Python 中的 property 特性詳解??property 是 Python 中用于??將方法轉換為屬性??的裝飾器&#xff0c;它允許開發者以訪問屬性的方式調用方法&#xff0c;同時可以添加邏輯控制&#xff08;如數據校驗、計算屬性等&#xff09;。以下是其核心用法和優勢&#xff1a;…

【Redis#9】其他數據結構

引言 Redis 除了我們最常用的 String、Hash、List、Set、ZSet&#xff08;Sorted Set&#xff09; 這五種基本數據結構外&#xff0c;還提供了很多高級或特殊用途的數據結構/類型 &#xff0c;它們可以滿足更復雜的業務需求。 ? Redis 的“五大基本數據結構”回顧類型特點Stri…

AutoGen——自定義Agent

目錄引子自定義 AgentCountDownAgentArithmeticAgent在自定義 Agent 中使用自定義模型客戶端讓自定義 Agent 聲明式化Selector Group Chat示例&#xff1a;網頁搜索 / 數據分析代理&#xff08;Agents&#xff09;Workflow終止條件&#xff08;Termination Conditions&#xff…

【重定向和轉發的核心理解】

重定向和轉發 不廢話&#xff1a; “轉發” 的核心定義&#xff1a; 服務端內部主導跳轉、客戶端無感知&#xff08;僅 1 次請求&#xff09;、瀏覽器 URL 不改變&#xff0c;與傳統 Web 開發中 “轉發” 的本質邏輯完全一致&#xff0c;只是實現載體&#xff08;Nginx 路由層 …

生成對抗網絡詳解與實現

生成對抗網絡詳解與實現0. 前言1. GAN 原理2. GAN 架構3. 損失函數3.1 判別器損失3.2 生成器損失3.4 VANILLA GAN4. GAN 訓練步驟0. 前言 生成對抗網絡 (Generative Adversarial Network, GAN) 是圖像和視頻生成中的主要方法之一。在本節中&#xff0c;我們將了解 GAN 的架構、…

FPGA硬件開發-XPE工具的使用

目錄 XPE 工具概述? XPE 使用步驟詳解? 1. 工具獲取與初始化? 2. 器件選擇與配置? 3. 電源電壓設置? 4. 資源使用量配置? 5. 時鐘與開關活動配置? 6. 功耗計算與報告生成? 報告解讀與電源設計優化? 常見問題與最佳實踐? 與實際功耗的差異處理? 工具版本…

CentOS 7.9 RAID 10 實驗報告

文章目錄CentOS 7.9 RAID 10 實驗報告一、實驗概述1.1 實驗目的1.2 實驗環境1.3 實驗拓撲二、實驗準備2.1 磁盤準備2.2 安裝必要軟件三、RAID 10陣列創建3.1 創建RAID 10陣列3.2 創建文件系統并掛載3.3 保存RAID配置四、性能基準測試4.1 初始性能測試4.2 創建測試數據集五、故障…

機器人逆運動學進階:李代數、矩陣指數與旋轉流形計算

做機器人逆運動學&#xff08;IK&#xff09;的時候&#xff0c;你遲早會遇到矩陣指數和對數這些東西。為什么呢&#xff1f;因為計算三維旋轉的誤差&#xff0c;不能簡單地用歐氏距離那一套&#xff0c;那只對位置有效。旋轉得用另一套方法——你需要算兩個旋轉矩陣之間的差異…

計算機視覺(opencv)實戰十八——圖像透視轉換

圖像透視變換詳解與實戰在圖像處理中&#xff0c;透視變換&#xff08;Perspective Transform&#xff09; 是一種常見的幾何變換&#xff0c;用來將圖像中某個四邊形區域拉伸或壓縮&#xff0c;映射到一個矩形區域。常見應用場景包括&#xff1a;糾正拍照時的傾斜&#xff08;…

【飛書多維表格插件】

coze中添加飛書多維表格記錄插件 添加單條記錄 [{"fields":{"任務詳情":"選項1","是否完成":"未完成"}}]添加多條記錄 [{"fields":{"任務詳情":"選項1","是否完成":"已完…

Java基礎 9.14

1.Collection接口遍歷對象方式2-for循環增強增強for循環&#xff0c;可以代替iterator選代器&#xff0c;特點&#xff1a;增強for就是簡化版的iterator本質一樣 只能用于遍歷集合或數組package com.logic.collection_;import java.util.ArrayList; import java.util.Collectio…

數據結構(C語言篇):(十三)堆的應用

目錄 前言 一、堆排序 1.1 版本一&#xff1a;基于已有數組建堆、取棧頂元素完成排序 1.1.1 實現邏輯 1.1.2 底層原理 1.1.3 應用示例 1.1.4 執行流程 1.2 版本二&#xff1a;原地排序 —— 標準堆排序 1.2.1 實現邏輯 1.2.2 底層原理 1.2.3 時間復雜度計算…

4步OpenCV-----掃秒身份證號

這段代碼用 OpenCV 做了一份“數字模板字典”&#xff0c;然后在銀行卡/身份證照片里自動找到身份證號那一行&#xff0c;把每個數字切出來跟模板比對&#xff0c;最終輸出并高亮顯示出完整的身份證號碼&#xff0c;下面是代碼解釋&#xff1a;模塊 1 工具箱&#xff08;通用函…

馮諾依曼體系:現代計算機的基石與未來展望

馮諾依曼體系&#xff1a;現代計算機的基石與未來展望 引人入勝的開篇 當你用手機刷視頻、用電腦辦公時&#xff0c;是否想過這些設備背后共享的底層邏輯&#xff1f;從指尖輕滑切換APP&#xff0c;到電腦秒開文檔&#xff0c;這種「無縫銜接」的體驗&#xff0c;其實藏著一個改…

前端基礎 —— C / JavaScript基礎語法

以下是對《3.JavaScript(基礎語法).pdf》的內容大綱總結&#xff1a;---&#x1f4d8; 一、JavaScript 簡介 - 定義&#xff1a;腳本語言&#xff0c;最初用于表單驗證&#xff0c;現為通用編程語言。 - 應用&#xff1a;網頁開發、游戲、服務器&#xff08;Node.js&#xff09…

springboot 二手物品交易系統設計與實現

springboot 二手物品交易系統設計與實現 目錄 【SpringBoot二手交易系統全解析】從0到1搭建你的專屬平臺&#xff01; &#x1f50d; 需求確認&#xff1a;溝通對接 &#x1f5e3; &#x1f4ca; 系統功能結構&#xff1a;附思維導圖 ☆開發技術&#xff1a; &#x1f6e…

【Android】可折疊式標題欄

在 Android 應用開發中&#xff0c;精美的用戶界面可以顯著提升應用品質和用戶體驗。Material Design 組件中的 CollapsingToolbarLayout 能夠為應用添加動態、流暢的折疊效果&#xff0c;讓標題欄不再是靜態的元素。本文將深入探討如何使用 CollapsingToolbarLayout 創建令人驚…

Debian13下使用 Vim + Vimspector + ST-LINK v2.1 調試 STM32F103 指南

1. 硬件準備與連接 1.1 所需硬件 STM32F103C8T6 最小系統板ST-LINK v2.1 調試器連接線&#xff08;杜邦線&#xff09; 1.2 硬件連接 ST-LINK v2.1 ? STM32F103C8T6 連接方式&#xff1a;ST-LINK v2.1 引腳STM32F103C8T6 引腳功能說明SWDIOPA13數據線SWCLKPA14時鐘線GNDGND共地…

第21課:成本優化與資源管理

第21課:成本優化與資源管理 課程目標 掌握計算資源優化 學習成本控制策略 了解資源調度算法 實踐實現成本優化系統 課程內容 21.1 成本分析框架 成本分析系統 class CostAnalysisFramework {constructor(config) {this.config

SAP HANA Scale-out 04:CalculationView優化

CV執行過程計算視圖激活時&#xff0c;生成Stored ModelSELECT查詢時&#xff1a;首先將Stored Model實例化為runtime Model 計算引擎執行優化&#xff0c;將runtime Model轉換為Optimized Runtime ModelOptimized Runtime Model通過SQL Optimizer進行優化計算引擎優化特性說明…