当前位置: 首页 > 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…...

Java 8 Stream API 入门到实践详解

一、告别 for 循环&#xff01; 传统痛点&#xff1a; Java 8 之前&#xff0c;集合操作离不开冗长的 for 循环和匿名类。例如&#xff0c;过滤列表中的偶数&#xff1a; List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...

select、poll、epoll 与 Reactor 模式

在高并发网络编程领域&#xff0c;高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表&#xff0c;以及基于它们实现的 Reactor 模式&#xff0c;为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。​ 一、I…...

基于Java+MySQL实现(GUI)客户管理系统

客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息&#xff0c;对客户进行统一管理&#xff0c;可以把所有客户信息录入系统&#xff0c;进行维护和统计功能。可通过文件的方式保存相关录入数据&#xff0c;对…...

LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》

这段 Python 代码是一个完整的 知识库数据库操作模块&#xff0c;用于对本地知识库系统中的知识库进行增删改查&#xff08;CRUD&#xff09;操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 &#x1f4d8; 一、整体功能概述 该模块…...

在Mathematica中实现Newton-Raphson迭代的收敛时间算法(一般三次多项式)

考察一般的三次多项式&#xff0c;以r为参数&#xff1a; p[z_, r_] : z^3 (r - 1) z - r; roots[r_] : z /. Solve[p[z, r] 0, z]&#xff1b; 此多项式的根为&#xff1a; 尽管看起来这个多项式是特殊的&#xff0c;其实一般的三次多项式都是可以通过线性变换化为这个形式…...

【Linux】Linux 系统默认的目录及作用说明

博主介绍&#xff1a;✌全网粉丝23W&#xff0c;CSDN博客专家、Java领域优质创作者&#xff0c;掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围&#xff1a;SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

Golang——7、包与接口详解

包与接口详解 1、Golang包详解1.1、Golang中包的定义和介绍1.2、Golang包管理工具go mod1.3、Golang中自定义包1.4、Golang中使用第三包1.5、init函数 2、接口详解2.1、接口的定义2.2、空接口2.3、类型断言2.4、结构体值接收者和指针接收者实现接口的区别2.5、一个结构体实现多…...

Oracle11g安装包

Oracle 11g安装包 适用于windows系统&#xff0c;64位 下载路径 oracle 11g 安装包...

Visual Studio Code 扩展

Visual Studio Code 扩展 change-case 大小写转换EmmyLua for VSCode 调试插件Bookmarks 书签 change-case 大小写转换 https://marketplace.visualstudio.com/items?itemNamewmaurer.change-case 选中单词后&#xff0c;命令 changeCase.commands 可预览转换效果 EmmyLua…...