当前位置: 首页 > news >正文

【c++高阶DS】图

Alt

🔥个人主页Quitecoder

🔥专栏c++笔记仓

Alt

目录

  • 01.并查集
  • 02.图的介绍
  • 03.图的存储结构
    • 03.1.邻接矩阵
    • 03.2.邻接表
    • 03.3.矩阵版本代码实现
    • 03.4.邻接表版本代码实现
  • 完整代码:

01.并查集

在一些应用问题中,需要将n个不同的元素划分成一些不相交的集合。开始时,每个元素自成一个单元素集合,然后按一定的规律将归于同一组元素的集合合并。在此过程中要反复用到查询某一个元素归属于那个集合的运算。适合于描述这类问题的抽象数据类型称为并查集(union-find set)

比如:某公司今年校招全国总共招生10人,西安招4人,成都招3人,武汉招3人,10个人来自不同的学校,起先互不相识,每个学生都是一个独立的小团体,现给这些学生进行编号:{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; 给以下数组用来存储该小集体,数组中的数字代表:该小集体中具有成员的个数

在这里插入图片描述
毕业后,学生们要去公司上班,每个地方的学生自发组织成小分队一起上路,于是:
西安学生小分队s1={0,6,7,8},成都学生小分队s2={1,4,9},武汉学生小分队s3={2,3,5}就相互认识了,10个人形成了三个小团体。假设右三个群主0,1,2担任队长,负责大家的出行。

在这里插入图片描述
在这里插入图片描述
从上图可以看出:编号6,7,8同学属于0号小分队,该小分队中有4人(包含队长0);编号为4和9的同学属于1号小分队,该小分队有3人(包含队长1),编号为3和5的同学属于2号小分队,该小分队有3个人(包含队长1)

仔细观察数组中内融化,可以得出以下结论:

  1. 数组的下标对应集合中元素的编号
  2. 数组中如果为负数,负号代表根,数字代表该集合中元素个数
  3. 数组中如果为非负数,代表该元素双亲在数组中的下标

在公司工作一段时间后,西安小分队中8号同学与成都小分队1号同学奇迹般的走到了一起,两个小圈子的学生相互介绍,最后成为了一个小圈子

在这里插入图片描述
现在0集合有7个人,2集合有3个人,总共两个朋友圈

通过以上例子可知,并查集一般可以解决一下问题:

  1. 查找元素属于哪个集合
    沿着数组表示树形关系以上一直找到根(即:树中中元素为负数的位置)
  2. 查看两个元素是否属于同一个集合
    沿着数组表示的树形关系往上一直找到树的根,如果根相同表明在同一个集合,否则不在
  3. 将两个集合归并成一个集合
    将两个集合中的元素合并
    将一个集合名称改成另一个集合的名称
  4. 集合的个数
    遍历数组,数组中元素为负数的个数即为集合的个数

代码实现:

class UnionFindSet
{
public:UnionFindSet(size_t n):_ufs(n, -1){}void Union(int x1, int x2){int root1 = FindRoot(x1);int root2 = FindRoot(x2);if (root1 == root2)return;_ufs[root1] += _ufs[root2];_ufs[root2] = root1;}int FindRoot(int x){int parent = x;while (_ufs[parent] >= 0){parent = _ufs[parent];}return parent;}bool InSet(int x1,int x2){return FindRoot(x1) == FindRoot(x2);}size_t SetSize(){size_t size= 0;for (int i = 0; i < _ufs.size(); i++){if (_ufs[i] < 0) ++size;}return size;}
private:vector<int> _ufs;
};

题目链接1:LCR 116. 省份数量
题目描述在这里插入图片描述

class Solution {
public:   int findCircleNum(vector<vector<int>>& isConnected) {UnionFindSet ufs(isConnected.size());for(int i=0;i<isConnected.size();i++){for(int j=0;j<isConnected[i].size();j++){if(isConnected[i][j]==1){ufs.Union(i,j);}}}return ufs.SetSize();}
};

题目链接2:990. 等式方程的可满足性
题目描述在这里插入图片描述

class Solution {
public:bool equationsPossible(vector<string>& equations) {UnionFindSet _ufs(26);for(auto& str: equations){if(str[1]=='='){_ufs.Union(str[0]-'a',str[3]-'a');}}for(auto& str2: equations){if(str2[1]=='!'){if(_ufs.InSet(str2[0]-'a',str2[3]-'a')) return false;}}return true;}
};

02.图的介绍

并查集是后面的铺垫,这里我们对图进行讲解:

图是由顶点集合及顶点间的关系组成的一种数据结构:G = (V, E),其中:顶点集合V = {x|x属于某个数据对象集}是有穷非空集合;

E = {(x,y)|x,y属于V}或者E = {<x, y>|x,y属于V && Path(x, y)}是顶点间关系的有穷集合,也叫做边的集合。(x, y)表示x到y的一条双向通路,即(x, y)是无方向的;Path(x, y)表示从x到y的一条单向通路,即Path(x, y)是有方向的

在这里插入图片描述
顶点和边:图中结点称为顶点,第i个顶点记作vi。两个顶点vi和vj相关联称作顶点vi和顶点vj之间有一条边图中的第k条边记作ek,ek = (vi,vj)或<vi,vj>

  • 有向图和无向图:在有向图中,顶点对<x, y>是有序的,顶点对<x,y>称为顶点x到顶点y的一条边(弧),<x, y><y, x>是两条不同的边,比如图G3和G4为有向图。在无向图中,顶点对(x, y)是无序的,顶点对(x,y)称为顶点x和顶点y相关联的一条边,这条边没有特定方向,(x, y)(y,x)是同一条边,比如图G1和G2为无向图。注意:无向边(x, y)等于有向边<x, y>和<y, x>

  • 完全图在有n个顶点的无向图中,若有n * (n-1)/2条边,即任意两个顶点之间有且仅有一条边,则称此图为无向完全图**,比如上图G1;在n个顶点的有向图中,若有n * (n-1)条边,即任意两个顶点之间有且仅有方向相反的边,则称此图为有向完全图,比如上图G4(任意两个顶点之间都相连,最稠密的图

  • 邻接顶点:在无向图中G中,若(u, v)是E(G)中的一条边,则称u和v互为邻接顶点,并称边(u,v)依附于顶点u和v;在有向图G中,若<u, v>是E(G)中的一条边,则称顶点u邻接到v,顶点v邻接自顶点u,并称边<u, v>与顶点u和顶点v相关联

  • 顶点的度顶点v的度是指与它相关联的边的条数,记作deg(v)。在有向图中,顶点的度等于该顶点的入度与出度之和,其中顶点v的入度是以v为终点的有向边的条数,记作indev(v);顶点v的出度是以v为起始点的有向边的条数,记作outdev(v)。因此:dev(v) = indev(v) + outdev(v)。注意:对于无向图,顶点的度等于该顶点的入度和出度,即dev(v) = indev(v) = outdev(v)

  • 路径:在图G = (V, E)中,若从顶点vi出发有一组边使其可到达顶点vj,则称顶点vi到顶点vj的顶点序列为从顶点vi到顶点vj的路径

  • 路径长度:对于不带权的图,一条路径的路径长度是指该路径上的边的条数;对于带权的图,一条路径的路径长度是指该路径上各个边权值的总和
    在这里插入图片描述

简单路径与回路:若路径上各顶点v1,v2,v3,…,vm均不重复,则称这样的路径为简单路径。若路径上第一个顶点v1和最后一个顶点vm重合,则称这样的路径为回路或环
在这里插入图片描述
子图设图G = {V, E}和图G1 = {V1,E1},若V1属于V且E1属于E,则称G1是G的子图

在这里插入图片描述

  • 连通图:在无向图中,若从顶点v1到顶点v2有路径则称顶点v1与顶点v2是连通的如果图中任意一对顶点都是连通的,则称此图为连通图
  • 强连通图:在有向图中,若在每一对顶点vi和vj之间都存在一条从vi到vj的路径,也存在一条从vj到vi的路径,则称此图是强连通图
  • 生成树:一个连通图(无向图)的最小连通子图称作该图的生成树。有n个顶点的连通图的生成树有n个顶点和n-1条边

03.图的存储结构

因为图中既有节点,又有边(节点与节点之间的关系),因此,在图的存储中,只需要保存:节点和边关系即可。节点保存比较简单,只需要一段连续空间即可,那边关系该怎么保存呢

03.1.邻接矩阵

因为节点与节点之间的关系就是连通与否,即为0或者1,因此邻接矩阵(二维数组)即是:先用一个数组将定点保存,然后采用矩阵来表示节点与节点之间的关系

在这里插入图片描述

无向图的邻接矩阵是对称的,第i行(列)元素之和,就是顶点i的度。有向图的邻接矩阵则不一定是对称的,第i行(列)元素之和就是顶点i 的出(入)度

如果边带有权值,并且两个节点之间是连通的,上图中的边的关系就用权值代替,如果两个顶点不通,则使用无穷大代替

在这里插入图片描述
邻接矩阵存储方式非常适合稠密图
邻接矩阵O(1)判断两个顶点的连接关系,并取到权值

用邻接矩阵存储图的有点是能够快速知道两个顶点是否连通,缺陷是如果顶点比较多,边比较少时,矩阵中存储了大量的0成为系数矩阵,比较浪费空间,并且要求两个节点之间的路径不是很好求,同时也不好求一个顶点连接的边数

03.2.邻接表

邻接表:使用数组表示顶点的集合,使用链表表示边的关系。

  1. 无向图邻接表存储
    在这里插入图片描述
    指针数组:自己连接顶点边全都挂在下面
  2. 有向图邻接表存储
    在这里插入图片描述
    注意:有向图中每条边在邻接表中只出现一次,与顶点vi对应的邻接表所含结点的个数,就是该顶点的出度,也称出度表,要得到vi顶点的入度,必须检测其他所有顶点对应的边链表,看有多少边顶点的dst取值是i。

邻接表适合存储稀疏图,也适合查找一个顶点连接出去的边,不适合去确定两个顶点是否相连及权值

03.3.矩阵版本代码实现

template<class V,class W, W MAX_W = INT_MAX,bool Direction = false>
class Graph
{private:vector<V> _vertexs;//顶点集合;map<V, int> _indexMap;//顶点映射下标;vector<vector<W>> _matrix;// 存储边集合的矩阵
};

构造函数

Graph(const V* a,size_t n)
{_vertexs.reserve(n);for (size_t i = 0; i < n; i++){_vertexs.push_back(a[i]);_indexMap[a[i]] = i;}_matrix.resize(n);for (size_t i = 0; i < _matrix.size(); i++){_matrix[i].resize(n, MAX_W);}
}

这是一个带顶点数组 a 和顶点数量 n 的构造函数,作用是:

  • 初始化图的顶点集合 _vertexs。
  • 使用一个映射 _indexMap 存储顶点到索引的映射关系,方便后续操作。
  • 使用二维矩阵 _matrix 初始化边的权值集合

_vertexs.reserve(n):为顶点集合预留容量,避免后续动态扩展的开销。

遍历顶点数组,将每个顶点添加到 _vertexs,并记录其在数组中的索引到 _indexMap,实现顶点到索引的快速查找

_matrix.resize(n):将矩阵调整为 n×n大小。
每行使用 resize(n, MAX_W) 将所有初始值设置为 MAX_W,表示无连接边

添加边:

size_t GetVertexIndex(const V& v)
{auto it = _indexMap.find(v);if (it != _indexMap.end()){return it->second;}else{throw invalid_argument("不存在的顶点");return -1;}
}
void AddEdge(const V& src, const V& dst, consy W& w)
{size_t srci = GetVertexIndex(src);size_t dsti = GetVertexIndex(dst);_matrix[srci][dsti] = w; if (Direction == false){_matrix[dsti][srci] = w;}
}

这段代码实现了两个关键功能:

  1. GetVertexIndex 函数:查找顶点的索引。
  2. AddEdge 函数:在图中添加边(支持有向图和无向图)。

实现逻辑

  • 使用 _indexMap.find(v) 在映射 _indexMap 中查找顶点 v
  • 如果找到,返回对应的索引 it->second
  • 如果未找到,抛出异常 invalid_argument("不存在的顶点"),提示顶点不存在。

在图中添加一条边,支持有向图和无向图。

输入参数

  • const V& src:边的起点顶点。
  • const V& dst:边的终点顶点。
  • const W& w:边的权值,引用方式传递,避免复制。

处理方向性

  • 有向图:只设置 _matrix[srci][dsti]
  • 无向图:同时设置 _matrix[srci][dsti]_matrix[dsti][srci]

打印函数:

void Print()
{// 打印顶点和下标映射关系for (size_t i = 0; i < _vertexs.size(); ++i){cout << _vertexs[i] << "-" << i << " ";}cout << endl << endl;cout << "  ";for (size_t i = 0; i < _vertexs.size(); ++i){cout << i << " ";}cout << endl;// 打印矩阵for (size_t i = 0; i < _matrix.size(); ++i){cout << i << " ";for (size_t j = 0; j < _matrix[i].size(); ++j){if (_matrix[i][j] != MAX_W)cout << _matrix[i][j] << " ";elsecout << "#" << " ";}cout << endl;}cout << endl << endl;// 打印所有的边for (size_t i = 0; i < _matrix.size(); ++i){for (size_t j = 0; j < _matrix[i].size(); ++j){if (i < j && _matrix[i][j] != MAX_W){cout << _vertexs[i] << "-" << _vertexs[j] << ":" <<_matrix[i][j] << endl;}}}
}

进行测试:

在这里插入图片描述

03.4.邻接表版本代码实现

template<class W>
struct LinkEdge
{int _srcindex;           // 边的起点索引int _dstindex;           // 边的终点索引W _w;                    // 边的权值LinkEdge<W>* _next;      // 指向下一个边的指针,用于链式存储LinkEdge(const W& w): _srcIndex(-1)      // 默认起点索引为 -1, _dstIndex(-1)      // 默认终点索引为 -1, _w(w)              // 初始化权值, _next(nullptr)     // 指向下一个边的指针初始化为 nullptr{}
};
template<class V, class W,  bool Direction = false>
class Graph
{typedef LinkEdge<W> Edge;
public:private:vector<V> _vertexs;//顶点集合;map<V, int> _indexMap;//顶点映射下标;vector<Edge*> _tables;// 邻接表
};

初始化:

Graph(const V* a, size_t n)
{_vertexs.reserve(n);for (size_t i = 0; i < n; i++){_vertexs.push_back(a[i]);_indexMap[a[i]] = i;}_tables.resize(n,nullptr);
}

增加边:

void AddEdge(const V& src, const V& dst, const W& w)
{size_t srci = GetVertexIndex(src);size_t dsti = GetVertexIndex(dst);Edge* sd_edge = new Edge(w);sd_edge->_srcindex = srci;sd_edge->_dstindex = dsti;sd_edge->_next = _tables[srci];_tables[srci] = sd_edge;if (Direction == false){Edge* ds_edge = new Edge(w);ds_edge->_srcIndex = dstindex;ds_edge->_dstIndex = srcindex;ds_edge->_next = _tables[dstindex];_tables[dstindex] = ds_edge;}
}

下面是详细的步骤说明,基于图中有四个顶点 ( A, B, C, D )。我们逐步构建它们的邻接表,同时解释代码的详细逻辑。


假设

  1. 图的顶点集合为:[A, B, C, D]。
  2. 图的边集合为:
    • ( A \to B )(权值 1)
    • ( A \to C )(权值 2)
    • ( B \to D )(权值 3)
    • ( C \to D )(权值 4)
  3. 图是 有向图(即 ( Direction = true ))。

初始状态
邻接表 _tables 是一个数组,初始时,每个顶点的邻接表为空:

_tables: [nullptr, nullptr, nullptr, nullptr]
  • ( _tables[0] ) 表示顶点 ( A ) 的邻接表头。
  • ( _tables[1] ) 表示顶点 ( B ) 的邻接表头。
  • ( _tables[2] ) 表示顶点 ( C ) 的邻接表头。
  • ( _tables[3] ) 表示顶点 ( D ) 的邻接表头。

Step 1: 插入边 ( A \to B )(权值 1)

  1. 调用 AddEdge("A", "B", 1)

  2. 获取顶点索引:

    • GetVertexIndex("A") 返回 0。
    • GetVertexIndex("B") 返回 1。
  3. 创建新边 sd_edge

    sd_edge->_srcindex = 0;  // A 的索引
    sd_edge->_dstindex = 1;  // B 的索引
    sd_edge->_w = 1;         // 权值 1
    sd_edge->_next = _tables[0];  // 当前 A 的邻接表头(nullptr)
    _tables[0] = sd_edge;         // 更新 A 的邻接表头为 sd_edge
    
  4. 更新邻接表:

    _tables:
    [sd_edge, nullptr, nullptr, nullptr]
    

    此时,邻接表内容为:

    A: B (权值 1) -> nullptr
    B: nullptr
    C: nullptr
    D: nullptr
    

Step 2: 插入边 ( A \to C )(权值 2)

  1. 调用 AddEdge("A", "C", 2)

  2. 获取顶点索引:

    • GetVertexIndex("A") 返回 0。
    • GetVertexIndex("C") 返回 2。
  3. 创建新边 sd_edge

    sd_edge->_srcindex = 0;  // A 的索引
    sd_edge->_dstindex = 2;  // C 的索引
    sd_edge->_w = 2;         // 权值 2
    sd_edge->_next = _tables[0];  // 当前 A 的邻接表头(指向 B 的边)
    _tables[0] = sd_edge;         // 更新 A 的邻接表头为 sd_edge
    
  4. 更新邻接表:

    _tables:
    [sd_edge, nullptr, nullptr, nullptr]
    

    此时,邻接表内容为:

    A: C (权值 2) -> B (权值 1) -> nullptr
    B: nullptr
    C: nullptr
    D: nullptr
    

Step 3: 插入边 ( B \to D )(权值 3)

  1. 调用 AddEdge("B", "D", 3)

  2. 获取顶点索引:

    • GetVertexIndex("B") 返回 1。
    • GetVertexIndex("D") 返回 3。
  3. 创建新边 sd_edge

    sd_edge->_srcindex = 1;  // B 的索引
    sd_edge->_dstindex = 3;  // D 的索引
    sd_edge->_w = 3;         // 权值 3
    sd_edge->_next = _tables[1];  // 当前 B 的邻接表头(nullptr)
    _tables[1] = sd_edge;         // 更新 B 的邻接表头为 sd_edge
    
  4. 更新邻接表:

    _tables:
    [sd_edge, sd_edge, nullptr, nullptr]
    

    此时,邻接表内容为:

    A: C (权值 2) -> B (权值 1) -> nullptr
    B: D (权值 3) -> nullptr
    C: nullptr
    D: nullptr
    

Step 4: 插入边 ( C \to D )(权值 4)

  1. 调用 AddEdge("C", "D", 4)

  2. 获取顶点索引:

    • GetVertexIndex("C") 返回 2。
    • GetVertexIndex("D") 返回 3。
  3. 创建新边 sd_edge

    sd_edge->_srcindex = 2;  // C 的索引
    sd_edge->_dstindex = 3;  // D 的索引
    sd_edge->_w = 4;         // 权值 4
    sd_edge->_next = _tables[2];  // 当前 C 的邻接表头(nullptr)
    _tables[2] = sd_edge;         // 更新 C 的邻接表头为 sd_edge
    
  4. 更新邻接表:

    _tables:
    [sd_edge, sd_edge, sd_edge, nullptr]
    

    此时,邻接表内容为:

    A: C (权值 2) -> B (权值 1) -> nullptr
    B: D (权值 3) -> nullptr
    C: D (权值 4) -> nullptr
    D: nullptr
    

最终邻接表

最终的邻接表结构如下:

A: C (权值 2) -> B (权值 1) -> nullptr
B: D (权值 3) -> nullptr
C: D (权值 4) -> nullptr
D: nullptr

邻接表的 _tables 数据结构是:

_tables:
[sd_edge (A->C), sd_edge (B->D), sd_edge (C->D), nullptr]

最后完成打印函数;

void Print()
{for (size_t i = 0; i < _vertexs.size(); ++i){cout << _vertexs[i] << "-" << i << " ";}cout << endl << endl;for (int i = 0; i < _tables.size(); i++){cout << _vertexs[i] << "[" << i << "]->";Edge* cur = _tables[i];while (cur){cout << _vertexs[cur->_dstindex] << "[" << cur->_dstindex << "]" << cur->_w << "->";cur = cur->_next;}cout << "nullptr" << endl;}
}

在这里插入图片描述

完整代码:

#pragma once
#include<vector>
#include<map>using namespace std;namespace matrix
{template<class V, class W, W MAX_W = INT_MAX, bool Direction = false>class Graph{public://手动添加边,方便测试Graph(const V* a, size_t n){_vertexs.reserve(n);for (size_t i = 0; i < n; i++){_vertexs.push_back(a[i]);_indexMap[a[i]] = i;}_matrix.resize(n);for (size_t i = 0; i < _matrix.size(); i++){_matrix[i].resize(n, MAX_W);}}size_t GetVertexIndex(const V& v){auto it = _indexMap.find(v);if (it != _indexMap.end()){return it->second;}else{throw invalid_argument("不存在的顶点");return -1;}}void AddEdge(const V& src, const V& dst, const W& w){size_t srci = GetVertexIndex(src);size_t dsti = GetVertexIndex(dst);_matrix[srci][dsti] = w;if (Direction == false){_matrix[dsti][srci] = w;}}void Print(){// 打印顶点和下标映射关系for (size_t i = 0; i < _vertexs.size(); ++i){cout << _vertexs[i] << "-" << i << " ";}cout << endl << endl;cout << "  ";for (size_t i = 0; i < _vertexs.size(); ++i){cout << i << " ";}cout << endl;// 打印矩阵for (size_t i = 0; i < _matrix.size(); ++i){cout << i << " ";for (size_t j = 0; j < _matrix[i].size(); ++j){if (_matrix[i][j] != MAX_W)cout << _matrix[i][j] << " ";elsecout << "#" << " ";}cout << endl;}cout << endl << endl;// 打印所有的边for (size_t i = 0; i < _matrix.size(); ++i){for (size_t j = 0; j < _matrix[i].size(); ++j){if (i < j && _matrix[i][j] != MAX_W){cout << _vertexs[i] << "-" << _vertexs[j] << ":" <<_matrix[i][j] << endl;}}}}private:vector<V> _vertexs;//顶点集合;map<V, int> _indexMap;//顶点映射下标;vector<vector<W>> _matrix;// 存储边集合的矩阵};
}
namespace LinkTable
{template<class W>struct LinkEdge{int _srcindex;int _dstindex;W _w;//权值LinkEdge<W>* _next;LinkEdge(const W& w): _srcindex(-1), _dstindex(-1), _w(w), _next(nullptr){}};template<class V, class W,  bool Direction = false>class Graph{typedef LinkEdge<W> Edge;public:Graph(const V* a, size_t n){_vertexs.reserve(n);for (size_t i = 0; i < n; i++){_vertexs.push_back(a[i]);_indexMap[a[i]] = i;}_tables.resize(n,nullptr);}size_t GetVertexIndex(const V& v){auto it = _indexMap.find(v);if (it != _indexMap.end()){return it->second;}else{throw invalid_argument("不存在的顶点");return -1;}}void AddEdge(const V& src, const V& dst, const W& w){size_t srci = GetVertexIndex(src);size_t dsti = GetVertexIndex(dst);Edge* sd_edge = new Edge(w);sd_edge->_srcindex = srci;sd_edge->_dstindex = dsti;sd_edge->_next = _tables[srci];_tables[srci] = sd_edge;if (Direction == false){Edge* ds_edge = new Edge(w);ds_edge->_srcindex = dsti;ds_edge->_dstindex = srci;ds_edge->_next = _tables[dsti];_tables[dsti] = ds_edge;}}void Print(){for (size_t i = 0; i < _vertexs.size(); ++i){cout << _vertexs[i] << "-" << i << " ";}cout << endl << endl;for (int i = 0; i < _tables.size(); i++){cout << _vertexs[i] << "[" << i << "]->";Edge* cur = _tables[i];while (cur){cout << _vertexs[cur->_dstindex] << "[" << cur->_dstindex << "]" << cur->_w << "->";cur = cur->_next;}cout << "nullptr" << endl;}}private:vector<V> _vertexs;//顶点集合;map<V, int> _indexMap;//顶点映射下标;vector<Edge*> _tables;// 邻接表};
}
void TestGraph()
{matrix::Graph<char, int, INT_MAX, true> g("0123", 4);g.AddEdge('0', '1', 1);g.AddEdge('0', '3', 4);g.AddEdge('1', '3', 2);g.AddEdge('1', '2', 9);g.AddEdge('2', '3', 8);g.AddEdge('2', '1', 5);g.AddEdge('2', '0', 3);g.AddEdge('3', '2', 6);g.Print();
}
void TestGraph2()
{string a[] = { "张三", "李四", "王五", "赵六" };LinkTable::Graph<string, int> g1(a, 4);g1.AddEdge("张三", "李四", 100);g1.AddEdge("张三", "王五", 200);g1.AddEdge("王五", "赵六", 30);g1.Print();
}

相关文章:

【c++高阶DS】图

&#x1f525;个人主页&#xff1a;Quitecoder &#x1f525;专栏&#xff1a;c笔记仓 目录 01.并查集02.图的介绍03.图的存储结构03.1.邻接矩阵03.2.邻接表03.3.矩阵版本代码实现03.4.邻接表版本代码实现 完整代码&#xff1a; 01.并查集 在一些应用问题中&#xff0c;需要将…...

React第十八节 useEffect 用法使用技巧注意事项详解

1、概述 useEffect 是React中一个用于 将组件与外部系统同步的 Hook&#xff1b;在函数式组件中处理副作用函数的 Hook&#xff0c;用于替代类式组件中的生命周期函数&#xff1b; 可以在副作用函数中 实现以下操作&#xff1a; a、请求接口&#xff0c;获取后台提供数据 b、操…...

C++ 指针基础:开启内存操控之门

1. 指针为何如此重要 在 C 编程领域&#xff0c;指针堪称一项极为关键的特性。它赋予了程序员直接访问和操控内存的能力&#xff0c;这使得程序在处理复杂数据结构与优化性能时具有更高的灵活性。想象一下&#xff0c;在编写大型程序时&#xff0c;高效地管理内存资源是多么重要…...

Nginx的stream模块代理四层协议TCP的流量转发

Nginx的stream模块是一个功能强大的工具&#xff0c;专门用于处理四层协议&#xff08;即网络层和传输层&#xff0c;如TCP和UDP&#xff09;的流量。以下是对Nginx stream模块的详细解析&#xff1a; 一、基本功能 Nginx的stream模块主要用于实现TCP和UDP数据流的代理、转发…...

UE5 渲染管线 学习笔记

兰伯特 SSS为散射的意思 带Bias的可以根据距离自动切换mip的卷积值 而带Level的值mipmaps的定值 #define A8_SAMPLE_MASK .a 这样应该就很好理解了 这个只采样a通道 带Level的参考上面的 朝左上和右下进行模糊 带Bias参考上面 随机数 4D 3D 2D 1D...

Echarts连接数据库,实时绘制图表详解

文章目录 Echarts连接数据库&#xff0c;实时绘制图表详解一、引言二、步骤一&#xff1a;环境准备与数据库连接1、环境搭建2、数据库连接 三、步骤二&#xff1a;数据获取与处理1、查询数据库2、数据处理 四、步骤三&#xff1a;ECharts图表配置与渲染1、配置ECharts选项2、动…...

Electron 学习笔记

目录 一、安装和启动electron 1. 官网链接 2. 根据文档在控制台输入 3. 打包必填 4. 安装electron开发依赖 5. 在开发的情况下打开应用 6. 修改main为main.js&#xff0c;然后创建main.js 7.启动 二、启动一个窗口 1. main.js 2. index.html 3. 隐藏菜单栏 三、其他…...

Debian 12 安装配置 fail2ban 保护 SSH 访问

背景介绍 双十一的时候薅羊毛租了台腾讯云的虚机, 是真便宜, 只是没想到才跑了一个月, 系统里面就收集到了巨多的 SSH 恶意登录失败记录. 只能说, 互联网真的是太不安全了. 之前有用过 fail2ban 在 CentOS 7 上面做过防护, 不过那已经是好久好久之前的故事了, 好多方法已经不…...

http反向代理

通过反向代理实现访问biying,目前访问一些网站需要绕过cloudfare还没有解决,代码如下: from fastapi import FastAPI, Request from fastapi.responses import StreamingResponse import httpx import uvicorn import logging# 设置日志 logging.basicConfig(level=logging.…...

java12.24日记

运算符&#xff1a; 算术运算符&#xff1a; 顾名思义进行算数运算的 多为&#xff1a;四则运算&#xff0c;加一个取余 &#xff0c;-&#xff0c;*&#xff0c;/以及 %&#xff08;取余&#xff09; 而外的&#xff1a;自增 以及自减--&#xff0c;对原数进行1或者-1 i…...

vue中proxy代理配置(测试一)

接口地址&#xff1a;http://jsonplaceholder.typicode.com/posts 1、配置一&#xff08;代理没起作用&#xff09; &#xff08;1&#xff09;设置baseURL为http://jsonplaceholder.typicode.com &#xff08;2&#xff09;proxy为 ‘/api’&#xff1a;’ ’ &#xff08;3&a…...

[OpenGL]使用TransformFeedback实现粒子效果

一、简介 本文介绍了如何使用 OpenGL 中的 Transform Feedback 实现粒子效果&#xff0c;最终可以实现下图的效果&#xff1a; 本文的粒子系统实现参考了modern-opengl-tutorial, ogldev-tutorial28 和 粒子系统–喷泉 [OpenGL-Transformfeedback]。 二、使用 TransformFeed…...

GitCode 光引计划投稿 | GoIoT:开源分布式物联网开发平台

GoIoT 是基于Gin 的开源分布式物联网&#xff08;IoT&#xff09;开发平台&#xff0c;用于快速开发&#xff0c;部署物联设备接入项目&#xff0c;是一套涵盖数据生产、数据使用和数据展示的解决方案。 GoIoT 开发平台&#xff0c;它是一个企业级物联网平台解决方案&#xff…...

用 gdbserver 调试 arm-linux 上的 AWTK 应用程序

很多嵌入式 linux 开发者都能熟练的使用 gdb/lldb 调试应用程序&#xff0c;但是还有不少朋友在调试开发板上的程序时&#xff0c;仍然在使用原始的 printf。本文介绍一下使用 gdbserver 通过网络调试开发板上的 AWTK 应用程序的方法&#xff0c;供有需要的朋友参考。 1. 下载 …...

攻防世界web第一题

最近开始学习网络安全的相关知识&#xff0c;开启刷题&#xff0c;当前第一题 题目为攻防世界web新手题 这是题目 翻译&#xff1a;在这个训练挑战中&#xff0c;您将了解 Robots_exclusion_standard。网络爬虫使用 robots.txt 文件来检查是否允许它们对您的网站或仅网站的一部…...

轮播图带详情插件,插件

超级好用的轮播图 介绍访问地址参数介绍使用方法&#xff08;简单使用&#xff0c;参数结构点击链接查看详情&#xff09;图片展示 介绍 video(15) 带有底部物品介绍以及价格的轮播图组件&#xff0c;持续维护&#xff0c;uniApp插件&#xff0c;直接下载填充数据就可以在项目里…...

gesp(三级)(14)洛谷:B4039:[GESP202409 三级] 回文拼接

gesp(三级)(14)洛谷:B4039:[GESP202409 三级] 回文拼接 题目描述 一个字符串是回文串,当且仅当该字符串从前往后读和从后往前读是一样的,例如, aabaa \texttt{aabaa} aabaa 和...

ISO17025最新认证消息

ISO17025认证是国际上广泛认可的实验室管理标准&#xff0c;全称为《检测和校准实验室能力的通用要求》&#xff0c;由国际标准化组织&#xff08;ISO&#xff09;和国际电工委员会&#xff08;IEC&#xff09;联合发布。以下是对ISO17025最新认证消息及相关内容的归纳&#xf…...

ASP.NET Core Web API 控制器

文章目录 一、基类&#xff1a;ControllerBase二、API 控制器类属性三、使用 Get() 方法提供天气预报结果 在深入探讨如何编写自己的 PizzaController 类之前&#xff0c;让我们先看一下 WeatherController 示例中的代码&#xff0c;了解它的工作原理。 在本单元中&#xff0c…...

RAID5原理简介和相关问题

1、RAID5工作原理 2、RAID5单块硬盘的数据连续吗&#xff1f; 3、RAID5单块硬盘存储的是原始数据&#xff0c;还是异或后的数据&#xff1f; 4、RAID5的分块大小 ‌RAID5的分块大小一般选择4KB到64KB之间较为合适‌。选择合适的分块大小主要取决于以下几个考量因素&#xff1…...

Axure RP 8安装(内带安装包)

通过网盘分享的文件&#xff1a;Axure8.0.zip 链接: https://pan.baidu.com/s/195_qy2iiDIcYG4puAudScA 提取码: 6xt8 --来自百度网盘超级会员v1的分享 勾选I Agree 安装完成...

stm32定时器输出比较----驱动步进电机

定时器输出比较理论 OC(Output Compare)输出比较输出比较可以通过比较CNT与CCR寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形每个高级定时器和通用定时器都拥有4个输出比较通道高级定时器的前3个通道额外拥有死区生成和互补输出…...

关于鸿蒙架构feature

鸿蒙feature层模块架构 model&#xff1a;定义数据类型&#xff0c;进行接口请求 view&#xff1a;视图层 写UI viewModel&#xff1a;控制层 关于逻辑和请求调用 page页...

【递归,搜索与回溯算法 综合练习】深入理解暴搜决策树:递归,搜索与回溯算法综合小专题(一)

找出所有子集的异或总和再求和 题目解析 算法原理 解法 决策树 这种决策使得每一次递归都是有效的递归&#xff0c;每一个节点都是最终的结果&#xff0c;所以这棵决策树是不用剪枝的&#xff0c;也没有递归出口的&#xff1b; 注意 决策树执行添加元素…...

vue3 如何使用 mounted

vue3 如何使用 mounted 在 Vue 3 中&#xff0c;mounted 生命周期钩子用于当组件被挂载到 DOM 中后执行一些操作。 这个钩子非常适合用来执行那些依赖于 DOM 的初始化工作&#xff0c;比如获取元素的尺寸或者是与第三方的 DOM 有关的库进行交互等。 下面是一个简单的 Vue 3 组…...

PostgreSQL JOIN

PostgreSQL中的JOIN操作是一种用于合并两个或多个表的SQL语句&#xff0c;它允许根据某些条件&#xff08;通常是表之间的外键关系&#xff09;将相关的数据组合在一起。PostgreSQL支持多种类型的JOIN&#xff0c;包括&#xff1a; CROSS JOIN&#xff08;交叉连接&#xff09…...

mysql(基础语法)

准备一张员工表 /*Navicat Premium Data TransferSource Server : localhost_3306Source Server Type : MySQLSource Server Version : 80037 (8.0.37)Source Host : localhost:3306Source Schema : studymysqlTarget Server Type : MySQLTar…...

【论文阅读笔记】Scalable, Detailed and Mask-Free Universal Photometric Stereo

【论文阅读笔记】Scalable, Detailed and Mask-Free Universal Photometric Stereo 前言摘要引言Task 相关工作方法SDM-UniPS预处理尺度不变的空间光特征编码器像素采样变压器的非局部交互 PS-Mix数据集 实验结果训练细节评估和时间&#xff1a; 消融实验定向照明下的评估没有对…...

抓取手机HCI日志

荣耀手机 1、打开开发者模式 2、开启HCI、ADB调试 3、开启AP LOG 拨号界面输入*##2846579##* 4、蓝牙配对 5、抓取log adb pull /data/log/bt ./...

【linux】 unshare -user -r /bin/bash命令详解

命令解析 unshare -user -r /bin/bash 是一个 Linux 命令&#xff0c;它用于在新的用户命名空间中运行一个进程&#xff08;在这个例子中是 /bin/bash&#xff09;。以下是这个命令的详细解释&#xff1a; 【1. 命令解析】 unshare: unshare 是一个工具&#xff0c;用于从调用…...