本期我们就来深入学习一下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(V²)
  • 空间复杂度: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进行优化计算引擎优化特性说明…