二维容器进行图的DFS搜索和BFS搜索-C++STL模板

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: 二维容器进行图的DFS搜索和BFS搜索-C++STL模板

场景


小K 喜欢翻看洛谷博客获取知识。每篇文章可能会有若干个(也有可能没有)参考文献的链接指向别的博客文章。小K 求知欲旺盛,如果他看了某篇文章,那么他一定会去看这篇文章的参考文献(如果他之前已经看过这篇参考文献的话就不用再看它了)。那么小K看了某篇文章后一定会看到哪些文章呢?


题源:查找文献-洛谷


算法过程

这是一个图的搜索问题,图的搜索有两种方式:


DFS(深度优先搜索)

BFS(广度优先搜索)

深度优先搜索:


利用栈先进后出的特点,先将开始访问的结点入栈,然后重复下面两个步骤:

1)访问栈顶元素,然后将它出栈

2)将原栈顶结点指向的所有结点入栈

直到栈空或已经访问完所有结点,搜索结束。


广度优先搜索:


利用队列先进先出的特点,类似地,先将开始访问的结点入队,然后重复下面两个步骤:

1)访问队头元素,然后将它出队

2)将原队头结点指向的所有结点出队

直到队空或已经访问完所有结点,搜索结束。


需求分析:


所有结点仅访问一次,且必须访问从起始结点能访问到的全部结点

如果访问一个结点后,下一步同时存在多个选择,则优先访问编号较小的结点

结点数量不超过105,边数不超过106

数据表示:


邻接矩阵显然不太行,因为可能需要1010个元素的二维数组。邻接表是可以的,但由于需要优先访问编号较小的结点,所以我最终选择了以升序优先队列为元素的容器,类似于二维容器。


二维容器

了解优先队列

vector< priority_queue<int,vector<int>,greater<int>>  > a;
vector<int> visit;  //标记结点是否已访问


问题描述


1. 重复访问一个结点

如在深度优先遍历中,我控制了已访问的元素不再入栈。


相关代码:

while(!a[t].empty()) {  //结点t相关结点(未访问的)入栈 
  if(visit[a[t].top()]==0)
  ss.push(a[t].top()); 
  a[t].pop(); }

在一些测试数据中,它确实完成了我所希望它做的事情。但是我们看一个简单的例子:




在这个例子中,访问结点2时,结点4就压入栈中了,接着要访问结点3,但此时结点4还未访问,所以结点4又入栈。这就可能导致对4的重复访问。


解决方案:


改在出栈时判断结点是否已经访问


if(visit[t]==0) {
  cout<<t<<" "; visit[t]=1; visit[0]++; }

2. 非法访问内存

解决了上面的问题之后,程序运行又被告知非法访问内存了。


相关代码:

//----------广度优先遍历
while(visit[0]<n) {
  int t=q.front(); q.pop();
  if(visit[t]==0) {
  cout<<t<<" "; visit[t]=1; visit[0]++; }
  while(!aa[t].empty()) {
  q.push(aa[t].top());
  aa[t].pop(); } }

仔细观察外层循环的控制条件,发现只有在所有的结点都被访问过,循环才会停止。但是我当时忽略了一种可能:如果输入的图是非连通的呢?那么第二行队列q已经空了,仍然会被要求返回队头元素并弹出队头。


解决方案:


while(visit[0]<n && !q.empty()) {

1

找到问题之后,处理问题反而挺简单了,仅仅加了个!q.empty()的判断条件。但是为什么不把前面visit[0]<n的判断条件去掉呢?

因为访问完所有结点后,队列还不一定为空呀!队列中可能还有已经访问过的结点,提前结束循环可以稍稍提高一点程序的效率。


最终源代码

#include<iostream>
#include<queue>
#include<vector>
#include<stack>
using namespace std;
int main() {
  vector< priority_queue<int,vector<int>,greater<int>>  > a;
  queue<int> q;
  stack<int> s,ss;  //ss辅助 
  vector<int> visit;  //已访问结点记1,下标0记录已访问结点数 
  int n,m,x,y;  //x,y为x到y的一条边 
  cin>>n>>m;
  for(int i=0;i<=n;i++) { //扩充结点,下标0不用 
  a.push_back(priority_queue< int,vector<int>,greater<int> >()); 
  visit.push_back(int()); }
  for(int i=0;i<m;i++) {  //输入边 
  cin>>x>>y;
  a[x].push(y); }
  vector< priority_queue<int,vector<int>,greater<int>>  > aa(a);  //因为a经过一次遍历后就空了 
  //----------深度优先遍历 
  s.push(1);
  while(visit[0]<n && !s.empty()) { 
  int t=s.top(); s.pop(); //t记录栈顶,待访问结点出栈 
  if(visit[t]==0) {
    cout<<t<<" "; visit[t]=1; visit[0]++; }  
  while(!a[t].empty()) {  //t相关结点(未访问的)入栈 
    ss.push(a[t].top()); 
    a[t].pop(); }
  while(!ss.empty()) {  //入栈共两次,使最终出栈为升序 
    s.push(ss.top()); ss.pop(); } }
  cout<<endl;
  //----------广度优先遍历
  for(int i=0;i<=n;i++)
  visit[i]=0;
  q.push(1);
  while(visit[0]<n && !q.empty()) {
  int t=q.front(); q.pop();
  if(visit[t]==0) {
    cout<<t<<" "; visit[t]=1; visit[0]++; }
  while(!aa[t].empty()) {
    q.push(aa[t].top());
    aa[t].pop(); } }
  return 0; }
/*测试数据:
8 9
1 2
1 3
1 4
2 5
2 6
3 7
4 7
4 8
7 8
*/


运行:

image.png

相关文章
|
4天前
|
编译器 C语言 C++
【c++丨STL】list模拟实现(附源码)
本文介绍了如何模拟实现C++中的`list`容器。`list`底层采用双向带头循环链表结构,相较于`vector`和`string`更为复杂。文章首先回顾了`list`的基本结构和常用接口,然后详细讲解了节点、迭代器及容器的实现过程。 最终,通过这些步骤,我们成功模拟实现了`list`容器的功能。文章最后提供了完整的代码实现,并简要总结了实现过程中的关键点。 如果你对双向链表或`list`的底层实现感兴趣,建议先掌握相关基础知识后再阅读本文,以便更好地理解内容。
14 1
|
17天前
|
算法 C语言 C++
【c++丨STL】list的使用
本文介绍了STL容器`list`的使用方法及其主要功能。`list`是一种双向链表结构,适用于频繁的插入和删除操作。文章详细讲解了`list`的构造函数、析构函数、赋值重载、迭代器、容量接口、元素访问接口、增删查改操作以及一些特有的操作接口如`splice`、`remove_if`、`unique`、`merge`、`sort`和`reverse`。通过示例代码,读者可以更好地理解如何使用这些接口。最后,作者总结了`list`的特点和适用场景,并预告了后续关于`list`模拟实现的文章。
33 7
|
2月前
|
存储 编译器 C语言
【c++丨STL】vector的使用
本文介绍了C++ STL中的`vector`容器,包括其基本概念、主要接口及其使用方法。`vector`是一种动态数组,能够根据需要自动调整大小,提供了丰富的操作接口,如增删查改等。文章详细解释了`vector`的构造函数、赋值运算符、容量接口、迭代器接口、元素访问接口以及一些常用的增删操作函数。最后,还展示了如何使用`vector`创建字符串数组,体现了`vector`在实际编程中的灵活性和实用性。
64 4
|
2月前
|
C语言 C++ 容器
【c++丨STL】string模拟实现(附源码)
本文详细介绍了如何模拟实现C++ STL中的`string`类,包括其构造函数、拷贝构造、赋值重载、析构函数等基本功能,以及字符串的插入、删除、查找、比较等操作。文章还展示了如何实现输入输出流操作符,使自定义的`string`类能够方便地与`cin`和`cout`配合使用。通过这些实现,读者不仅能加深对`string`类的理解,还能提升对C++编程技巧的掌握。
77 5
|
2月前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
57 2
|
2月前
|
安全 编译器 C++
【C++11】可变模板参数详解
本文详细介绍了C++11引入的可变模板参数,这是一种允许模板接受任意数量和类型参数的强大工具。文章从基本概念入手,讲解了可变模板参数的语法、参数包的展开方法,以及如何结合递归调用、折叠表达式等技术实现高效编程。通过具体示例,如打印任意数量参数、类型安全的`printf`替代方案等,展示了其在实际开发中的应用。最后,文章讨论了性能优化策略和常见问题,帮助读者更好地理解和使用这一高级C++特性。
57 4
|
2月前
|
算法 编译器 C++
【C++】模板详细讲解(含反向迭代器)
C++模板是泛型编程的核心,允许编写与类型无关的代码,提高代码复用性和灵活性。模板分为函数模板和类模板,支持隐式和显式实例化,以及特化(全特化和偏特化)。C++标准库广泛使用模板,如容器、迭代器、算法和函数对象等,以支持高效、灵活的编程。反向迭代器通过对正向迭代器的封装,实现了逆序遍历的功能。
36 3
|
2月前
|
存储 算法 Linux
【c++】STL简介
本文介绍了C++标准模板库(STL)的基本概念、组成部分及学习方法,强调了STL在提高编程效率和代码复用性方面的重要性。文章详细解析了STL的六大组件:容器、算法、迭代器、仿函数、配接器和空间配置器,并提出了学习STL的三个层次,旨在帮助读者深入理解和掌握STL。
55 0
|
20天前
|
存储 编译器 C语言
【c++丨STL】vector模拟实现
本文深入探讨了 `vector` 的底层实现原理,并尝试模拟实现其结构及常用接口。首先介绍了 `vector` 的底层是动态顺序表,使用三个迭代器(指针)来维护数组,分别为 `start`、`finish` 和 `end_of_storage`。接着详细讲解了如何实现 `vector` 的各种构造函数、析构函数、容量接口、迭代器接口、插入和删除操作等。最后提供了完整的模拟实现代码,帮助读者更好地理解和掌握 `vector` 的实现细节。
30 0
|
2月前
|
存储 设计模式 C++
【C++】优先级队列(容器适配器)
本文介绍了C++ STL中的线性容器及其适配器,包括栈、队列和优先队列的设计与实现。详细解析了`deque`的特点和存储结构,以及如何利用`deque`实现栈、队列和优先队列。通过自定义命名空间和类模板,展示了如何模拟实现这些容器适配器,重点讲解了优先队列的内部机制,如堆的构建与维护方法。
38 0