探索数据结构:图(一)之邻接矩阵与邻接表


✨✨ 欢迎大家来到贝蒂大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:数据结构与算法
贝蒂的主页:Betty’s blog

1. 图的定义

**图(Graph)**是数学和计算机科学中的一种基本结构,它由两个主要组成部分定义:顶点集(Vertex Set)V 边集(Edge Set)E。在一个图 G = ( V , E ) G=(V,E) G=(V,E)中,V 是顶点的有限非空集合,而 E是连接这些顶点之间的边的集合,即每条边都是两个顶点之间的关系。

  • 顶点(Vertices):顶点可以由字母变量表示,比如 V = { v 1 , v 2 , … , v n } V=\{v_1,v_2,\ldots,v_n\} V={v1,v2,,vn} n n n 表示顶点的数量,也称为图的阶或维度。
  • 边(Edges):边是表示顶点间关系的元素,通常表示为 ( u , v ) (u,v) (u,v),这意味着从顶点 u u u 到顶点 v v v 有一条边。 E E E 的大小,即 ∣ E ∣ |E| E,给出了图中边的数量。

注意:线性表有空表,树有空树,但图没有空图。也就是说图不能一个顶点没有,但是边可以为空。

2. 图的基本概念

2.1 有向图与无向图

  • 有向图中, < x , y > <x,y> <x,y>是有序的,被称为顶点x到顶点y的一条边, < x , y > <x,y> <x,y> < y , x > <y,x> <y,x>是两条不同的边。
  • 无向图中, ( x , y ) (x,y) (x,y)是无序的,称为顶点x和顶点y相关联的一条边,这条边没有特定方向, ( x , y ) (x,y) (x,y) ( y , x ) (y,x) (y,x)是同一条边。

2.2 完全图

在有n个顶点的无向图中,若有 n × ( n − 1 ) ÷ 2 n × ( n − 1 ) ÷ 2 n×(n1)÷2条边,即任意两个顶点之间都有直接相连的边,则称此图为无向完全图
在有n个顶点的有向图中,若有 n × ( n − 1 ) n × ( n − 1 ) n×(n1)条边,即任意两个顶点之间都有双向的边,则称此图为有向完全图

2.3 邻接顶点

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

2.4 顶点的度

  • 有向图中,顶点的度等于该顶点的入度与出度之和,顶点的入度是以该顶点为终点的边的条数,顶点的出度是以该顶点为起点的边的条数。
  • 无向图中,顶点的度等于与该顶点相关联的边的条数,同时也等于该顶点的入度和出度。

2.5 路径与路径长度

  • 若从顶点 v i v_i vi出发有一组边使其可到达顶点 v j v_j vj ,则称顶点 v i v_i vi到顶点 v j v_j vj 的顶点序列为从顶点 v i v_i vi到顶点 v j v_j vj 的路径。
  • 对于不带权的图,一条路径的长度是指该路径上的边的条数;对于带权的图,一条路径的长度是指该路径上各个边权值的总和。其中权值指边附带的数据信息。

2.6 简单路径与回路

  • 若路径上的各个顶点 v 1 , v 2 , v 3 . . . v n v_1,v_2,v_3...v_n v1,v2,v3...vn均不相同,则称这样的路径为简单路径
  • 若路径上第一个顶点与最后一个顶点相同,则称这样的路径为回路

2.7 子图

设图 G = ( V , E ) G=(V,E) G=(V,E)和图 G 1 = ( V 1 , E 1 ) G_1=(V_1,E_1) G1=(V1,E1),若 V 1 ⊆ V V_1⊆V V1V E 1 ⊆ E E_1⊆E E1E,那么称 G 1 G_1 G1 G G G的子图。

2.8 连通图与强连通图

  • 无向图中,若从顶点 v 1 v_1 v1 到顶点 v 2 v_2 v2 有路径,则称顶点 v 1 v_1 v1 与顶点 v 2 v_2 v2 是连通的。如果图中任意一对顶点都是连通的,则称此图为连通图
  • 有向图中,若每一对顶点 v i v_i vi v j v_j vj 之间都存在一条从 v i v_i vi v j v_j vj 的路,也存在一条从 v j v_j vj v i v_i vi 的路,则称此图是强连通图

2.9 生成树与最小生成树

  • 一个连通图的最小连通子图称为该图的生成树,有 n n n个顶点的连通图的生成树有 n n n个顶点和 n − 1 n - 1 n1条边。
  • 最小生成树指的是一个图的生成树中,总权值最小的生成树。

3. 图的存储结构

因为图中既有节点,又有边(节点与节点之间的关系),因此,在图的存储中,只需要保存:节点和边关系即可。 下面我们将介绍两种常见的表示方法:邻接矩阵邻接表

3.1 邻接矩阵

邻接矩阵:先用一个数组将定点保存,然后采用矩阵来表示节点与节点之间的关系 。
对于一个具有 n n n个顶点的图 G = ( V , E ) G=(V,E) G=(V,E),其中 V V V 是顶点集, E E E 是边集。其邻接矩阵 A A A是一个 n × n n\times n n×n 的矩阵,定义如下:

  • 如果顶点 i i i 和顶点 j j j 之间有边相连,那么 A [ i ] [ j ] = 1 A[i][j]=1 A[i][j]=1(对于无向图, A [ i ] [ j ] = A [ j ] [ i ] = 1 A[i][j]=A[j][i]=1 A[i][j]=A[j][i]=1)。
  • 如果顶点 i i i 和顶点 j j j 之间没有边相连,那么 A [ i ] [ j ] = 0 A[i][j]=0 A[i][j]=0(对于无向图, A [ i ] [ j ] = A [ j ] [ i ] = 0 A[i][j]=A[j][i]=0 A[i][j]=A[j][i]=0)。

例如,对于一个简单的无向图,有三个顶点 V = { v 1 , v 2 , v 3 } V=\{v_1,v_2,v_3\} V={v1,v2,v3},如果 v 1 v_1 v1 v 2 v_2 v2 之间有边相连, v 2 v_2 v2 v 3 v_3 v3 之间有边相连, v 1 v_1 v1 v 3 v_3 v3 之间没有边相连,那么这个图的邻接矩阵为: [ 0 1 0 1 0 1 0 1 0 ] \begin{bmatrix} 0 & 1 & 0\\ 1 & 0 & 1\\ 0 & 1 & 0 \end{bmatrix} 010101010
如果是对于具有 n n n 个顶点的带权图 G = ( V , E , W ) G=(V,E,W) G=(V,E,W),其中 V V V 是顶点集, E E E 是边集, W W W 是权值集合。其邻接矩阵 A A A 是一个 n × n n\times n n×n 的矩阵。

  • 如果顶点 v i v_i vi 和顶点 v j v_j vj 之间有边相连,那么 A [ i ] [ j ] A[i][j] A[i][j] 存放着该边对应的权值。
  • 如果顶点 v i v_i vi 和顶点 v j v_j vj 之间没有边相连,那么 A [ i ] [ j ] A[i][j] A[i][j] 通常被设为一个特定的标识值,比如无穷大或者一个特殊的标记,具体取决于应用场景和算法需求。

例如,有一个带权无向图,三个顶点分别为 v 1 v_1 v1 v 2 v_2 v2 v 3 v_3 v3 v 1 v_1 v1 v 2 v_2 v2 之间边的权值为 2, v 2 v_2 v2 v 3 v_3 v3 之间边的权值为 3, v 1 v_1 v1 v 3 v_3 v3 之间没有边相连。那么这个图的邻接矩阵为: [ 0 2 ∞ 2 0 3 ∞ 3 0 ] \begin{bmatrix} 0 & 2 & ∞\\ 2 & 0 & 3\\ ∞ & 3 & 0 \end{bmatrix} 0220330

3.2 邻接表

邻接表:使用数组表示顶点的集合,使用链表表示边的关系。
对于一个具有 n n n个顶点的图 G = ( V , E ) G=(V,E) G=(V,E),邻接表的主要思想是为每个顶点建立一个链表,链表中存储与该顶点相邻的其他顶点。
具体来说:

  • 对于无向图,图中的每一条边 ( u , v ) (u,v) (u,v),如果顶点 u u u的链表中还没有顶点 v v v,则将顶点 v v v添加到顶点 u u u的链表中;同理,如果顶点 v v v的链表中还没有顶点 u u u,则将顶点 u u u添加到顶点 v v v的链表中。
  • 对于有向图,图中的每一条边 < u , v > <u,v> <u,v>,只需要将顶点 v v v添加到顶点 u u u的链表中。

例如,对于一个具有 5 5 5个顶点的无向图,顶点分别为 v 1 , v 2 , v 3 , v 4 , v 5 v_1,v_2,v_3,v_4,v_5 v1,v2,v3,v4,v5,如果有边 ( v 1 , v 2 ) (v_1,v_2) (v1,v2) ( v 1 , v 3 ) (v_1,v_3) (v1,v3) ( v 2 , v 4 ) (v_2,v_4) (v2,v4) ( v 3 , v 4 ) (v_3,v_4) (v3,v4) ( v 3 , v 5 ) (v_3,v_5) (v3,v5),那么其邻接表表示如下:

  • 顶点 v 1 v_1 v1的链表: v 2 v_2 v2 v 3 v_3 v3
  • 顶点 v 2 v_2 v2的链表: v 1 v_1 v1 v 4 v_4 v4
  • 顶点 v 3 v_3 v3的链表: v 1 v_1 v1 v 4 v_4 v4 v 5 v_5 v5
  • 顶点 v 4 v_4 v4的链表: v 2 v_2 v2 v 3 v_3 v3
  • 顶点 v 5 v_5 v5的链表: v 3 v_3 v3

如果是对于一个具有 5 5 5个顶点的有向图,顶点分别为 v 1 , v 2 , v 3 , v 4 , v 5 v_1,v_2,v_3,v_4,v_5 v1,v2,v3,v4,v5,如果有边 < v 1 , v 2 > <v_1,v_2> <v1,v2> < v 1 , v 3 > <v_1,v_3> <v1,v3> < v 2 , v 4 > <v_2,v_4> <v2,v4> < v 3 , v 4 > <v_3,v_4> <v3,v4> < v 3 , v 5 > <v_3,v_5> <v3,v5>,那么其邻接表表示如下:

  • 顶点 v 1 v_1 v1的链表: v 2 v_2 v2 v 3 v_3 v3
  • 顶点 v 2 v_2 v2的链表: v 4 v_4 v4
  • 顶点 v 3 v_3 v3的链表: v 4 v_4 v4 v 5 v_5 v5
  • 顶点 v 4 v_4 v4的链表:没有任何边。
  • 顶点 v 5 v_5 v5的链表:没有任何边。

3.3 邻接矩阵与邻接表的优缺点

对于邻接矩阵:

优点:

  • 直观性强,邻接矩阵能够 O ( 1 ) O(1) O(1)的时间判断两个顶点是否相连,并获得相连边的权值。
  • 适合稠密图:图中的边越多,邻接矩阵的空间利用率就越高。

缺点:

  • 不合适稀疏图:会浪费大量空间。
  • 不适合查找一个顶点连接出去的所有边:需要遍历矩阵中对应的一行,该过程的时间复杂度是 O ( N ) O ( N ) O(N) ,其中 N N N表示的是顶点的个数。

对于邻接表:

优点:

  • 适合查找一个顶点连接出去的所有边:只需要遍历对应的链表即可。
  • 适合稀疏图:图中的边越少,邻接表存储的空间就越少。

缺点:

  • 不合适稠密图:一个顶点会链接大量数据,需要遍历顶点对应位置的链表来确定两点是否相连,该过程的时间复杂度是 O ( E ) O(E) O(E),其中 E E E表示从源顶点连接出去的边的数量。

4. 邻接矩阵与邻接表的实现

4.1 邻接矩阵

4.1.1 邻接矩阵的结构

为了支持所有类型,我们实现一个模版类。其中肯定有两个模版参数VW分别代表顶点与权值类型,MAX_W表示两个顶点之间没有直接相连的值,一般我们默认为INT_MAX,并且还需要一个bool类型的模版参数Direction代表是有向图还是无向图,false为无向,true为有向。
邻接矩阵的成员变量有三个分别为:数组_vertexs代表边的集合,哈希表_indexMap来映射不同类型与下标的关系,二维数组_matrix代表邻接矩阵。

template<class V, class W, W MAX_W = INT_MAX, bool Direction = false>
class Graph
{
public://构造函数Graph(const V*vertexs,int n);//获取对应顶点的下标int getVertexsIndex(const V& v);void addEdge(const V& src, const V& dest, const W& weight);//打印顶点集合和邻接矩阵void Print();
private:vector<V> _vertexs;//顶点集合unordered_map<V, int> _indexMap;//映射关系vector<vector<W>> _matrix;//邻接矩阵
};
4.1.2 邻接矩阵的初始化

我们首先顶点集合全部初始化,然后将邻接矩阵的值全设为INT_MAX,最后将顶点集合与下标建立映射关系。

//构造函数
Graph(const V*vertexs,int n):_vertexs(vertexs, vertexs + n), _matrix(n,vector<int>(n, MAX_W))
{//映射下标for (int i = 0; i < n; i++){_indexMap[vertexs[i]] = i;}
}
4.1.3 添加边

添加边之前我们需要先找到对应顶点的下标,如果找不到需要抛异常。然后根据对应下标更新邻接矩阵,如果是无向图继续更新。

//获取对应顶点的下标
int getVertexsIndex(const V& v)
{auto it = _indexMap.find(v);if (it == _indexMap.end()){throw invalid_argument("不存在的顶点");return -1;}else{return it->second;//返回对应下标}
}
//添加边
void addEdge(const V& src, const V& dest, const W& weight)
{int srci = getVertexsIndex(src);//获取起始点下标int desti = getVertexsIndex(dest);//获取终点下标_matrix[srci][desti] = weight;if (Direction == false){_matrix[desti][srci] = weight;//无向图}
}
4.1.4 打印邻接矩阵

最后我们打印邻接矩阵,方便测试程序的正误。

//打印顶点集合和邻接矩阵
void Print() 
{int n = _vertexs.size();//打印顶点集合for (int i = 0; i < n; i++) {cout << "[" << i << "]->" << _vertexs[i] << endl;}cout << endl;//打印邻接矩阵cout << "  ";for (int i = 0; i < n; i++) {printf("%4d", i);}cout << endl;for (int i = 0; i < n; i++) {cout << i << " "; //竖下标for (int j = 0; j < n; j++) {if (_matrix[i][j] == MAX_W) {printf("%4c", '*');}else {printf("%4d", _matrix[i][j]);}}cout << endl;}
}

4.2 邻接表

4.2.1 邻接表的结构

同样邻接表我们编写成一个模板类,相比与邻接矩阵我们可以不需要代表权值最大的MAX_W。并且为了方便描述我们需要首先编写一个关于边Edge的类。这个类包含起始与终点下标,已经对应的权值。
然后邻接表中有三个成员变量:数组_vertexs代表边的集合,哈希表indexMap来映射不同类型与下标的关系,数组_linkTable代表邻接矩阵。

template<class W>
//边
struct Edge
{int _srci;//起始下标int _desti;//终点下标W _w;//权值Edge<W>* _next;Edge(int srci,int desti,const W&w):_srci(srci),_desti(desti),_w(w),_next(nullptr){}
};
template<class V, class W, bool Direction = false>
class Graph
{typedef Edge<W> Edge;
public://构造函数Graph(const V* vertexs, int n);//获取对应顶点的下标int getVertexsIndex(const V& v);//添加边void addEdge(const V& src, const V& dest, const W& weight);//打印顶点集合和邻接表void Print();
private:vector<V> _vertexs;//顶点集合unordered_map<V, int> _indexMap;//映射关系vector<Edge*> _linkTable;//邻接矩阵
};
4.2.2 邻接表的初始化

我们首先顶点集合全部初始化,然后将邻接表的值全设为nullptr,最后将顶点集合与下标建立映射关系;

//构造函数
Graph(const V* vertexs, int n):_vertexs(vertexs, vertexs + n), _linkTable(n, nullptr)
{//映射下标for (int i = 0; i < n; i++){_indexMap[vertexs[i]] = i;}
}
4.2.3 添加边

添加边之前我们需要先找到对应顶点的下标,如果找不到需要抛异常。然后根据对应下标更新邻接矩阵,如果是无向图继续更新。

//获取对应顶点的下标
int getVertexsIndex(const V& v)
{auto it = _indexMap.find(v);if (it == _indexMap.end()){throw invalid_argument("不存在的顶点");return -1;}else{return it->second;//返回对应下标}
}
//添加边
void addEdge(const V& src, const V& dest, const W& weight)
{int srci = getVertexsIndex(src);//获取起始点下标int desti = getVertexsIndex(dest);//获取终点下标Edge* sre = new Edge(srci, desti, weight);//进行头插sre->_next = _linkTable[srci];_linkTable[srci] = sre;if (Direction == false){Edge* dese = new Edge(desti, srci, weight);dese->_next = _linkTable[desti];_linkTable[desti] = dese;}
}
4.2.4 打印邻接矩阵

最后我们打印邻接表,方便测试程序的正误。

//打印顶点集合和邻接表
void Print()
{int n = _vertexs.size();//打印顶点集合for (int i = 0; i < n; i++) {cout << "[" << i << "]->" << _vertexs[i] << " ";}cout << endl << endl;//打印邻接表for (int i = 0; i < n; i++) {Edge* cur = _linkTable[i];cout << "[" << i << ":" << _vertexs[i] << "]->";while (cur) {cout << "[" << cur->_desti << ":" << _vertexs[cur->_desti] << ":" << cur->_w << "]->";cur = cur->_next;}cout << "nullptr" << endl;}
}

5. 源码

5.1 邻接矩阵

namespace Matrix
{template<class V, class W, W MAX_W = INT_MAX, bool Direction = false>class Graph{public://构造函数Graph(const V*vertexs,int n):_vertexs(vertexs, vertexs + n), _matrix(n,vector<int>(n, MAX_W)){//映射下标for (int i = 0; i < n; i++){_indexMap[vertexs[i]] = i;}}//获取对应顶点的下标int getVertexsIndex(const V& v){auto it = _indexMap.find(v);if (it == _indexMap.end()){throw invalid_argument("不存在的顶点");return -1;}else{return it->second;//返回对应下标}}//添加边void addEdge(const V& src, const V& dest, const W& weight){int srci = getVertexsIndex(src);//获取起始点下标int desti = getVertexsIndex(dest);//获取终点下标_matrix[srci][desti] = weight;if (Direction == false){_matrix[desti][srci] = weight;//无向图}}//打印顶点集合和邻接矩阵void Print() {int n = _vertexs.size();//打印顶点集合for (int i = 0; i < n; i++) {cout << "[" << i << "]->" << _vertexs[i] << endl;}cout << endl;//打印邻接矩阵cout << "  ";for (int i = 0; i < n; i++) {printf("%4d", i);}cout << endl;for (int i = 0; i < n; i++) {cout << i << " "; //竖下标for (int j = 0; j < n; j++) {if (_matrix[i][j] == MAX_W) {printf("%4c", '*');}else {printf("%4d", _matrix[i][j]);}}cout << endl;}}private:vector<V> _vertexs;//顶点集合unordered_map<V, int> _indexMap;//映射关系vector<vector<W>> _matrix;//邻接矩阵};
}

5.2 邻接表

namespace LinkTable
{template<class W>struct Edge{int _srci;//起始下标int _desti;//终点下标W _w;//权值Edge<W>* _next;Edge(int srci,int desti,const W&w):_srci(srci),_desti(desti),_w(w),_next(nullptr){}};template<class V, class W, bool Direction = false>class Graph{typedef Edge<W> Edge;public://构造函数Graph(const V* vertexs, int n):_vertexs(vertexs, vertexs + n), _linkTable(n, nullptr){//映射下标for (int i = 0; i < n; i++){_indexMap[vertexs[i]] = i;}}//获取对应顶点的下标int getVertexsIndex(const V& v){auto it = _indexMap.find(v);if (it == _indexMap.end()){throw invalid_argument("不存在的顶点");return -1;}else{return it->second;//返回对应下标}}//添加边void addEdge(const V& src, const V& dest, const W& weight){int srci = getVertexsIndex(src);//获取起始点下标int desti = getVertexsIndex(dest);//获取终点下标Edge* sre = new Edge(srci, desti, weight);//进行头插sre->_next = _linkTable[srci];_linkTable[srci] = sre;if (Direction == false){Edge* dese = new Edge(desti, srci, weight);dese->_next = _linkTable[desti];_linkTable[desti] = dese;}}//打印顶点集合和邻接表void Print(){int n = _vertexs.size();//打印顶点集合for (int i = 0; i < n; i++) {cout << "[" << i << "]->" << _vertexs[i] << " ";}cout << endl << endl;//打印邻接表for (int i = 0; i < n; i++) {Edge* cur = _linkTable[i];cout << "[" << i << ":" << _vertexs[i] << "]->";while (cur) {cout << "[" << cur->_desti << ":" << _vertexs[cur->_desti] << ":" << cur->_w << "]->";cur = cur->_next;}cout << "nullptr" << endl;}}private:vector<V> _vertexs;//顶点集合unordered_map<V, int> _indexMap;//映射关系vector<Edge*> _linkTable;//邻接矩阵};
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/406506.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

这周末,除非外面下钞票,否则谁也拦不住我玩《黑神话悟空》(附:两款可以玩转悟空的显卡推荐)

主播说联播 | 从“十分之三”到“悟空”,国潮有何出圈密码? 《黑神话:悟空》里的中国古建取景地,在这里! 这周末,除非外面下钞票,否则谁也拦不住我玩《黑神话悟空》(附:两款可以玩转悟空的显卡推荐) 原创 IPBrain平台君 集成电路大数据平台 2024年08月22日 17:28 …

gif图片怎么压缩大小?深度测评7款动图压缩工具(内含教程)

gif图片在社交媒体和网络上非常流行&#xff0c;深受大家喜爱&#xff0c;因为它可以呈现生动的动画效果。gif动图之所以受到欢迎&#xff0c;主要因为其出色的压缩算法&#xff0c;能有效存储多个帧&#xff0c;从而实现流畅的动画。 然而&#xff0c;大多数社交媒体平台对gi…

[数据集][目标检测]集装箱缺陷检测数据集VOC+YOLO格式4127张3类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;4127 标注数量(xml文件个数)&#xff1a;4127 标注数量(txt文件个数)&#xff1a;4127 标注…

全新分支版本!微软推出Windows 11 Canary Build 27686版

已经很久没有看到 Windows 11 全新的分支版本了&#xff0c;今天微软发布 Windows 11 Canary 新版本&#xff0c;此次版本号已经转移到 Build 27xxx&#xff0c;首发版本为 Build 27686 版。 此次更新带来了多项改进&#xff0c;包括 Windows Sandbox 沙盒功能切换到 Microsof…

关于智能编码助手【通义灵码】,开发者们这么说...

自通义灵码发布以来&#xff0c;不停地有开发者朋友为我们送上通义灵码的测评反馈。 关于通义灵码&#xff0c;开发者这样说 墨问西东 CEO 池建强&墨问研发团队 “通义灵码有一个强大的功能就是企业知识库检索增强&#xff0c;我们只需要上传团队的代码规范&#xff0c;…

[数据集][目标检测]快递包裹检测数据集VOC+YOLO格式5382张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;5382 标注数量(xml文件个数)&#xff1a;5382 标注数量(txt文件个数)&#xff1a;5382 标注…

【C语言小项目】五子棋游戏

目录 前言 一、游戏规则 1.功能分析 2.玩法分析 3.胜负判定条件 二、游戏实现思路 三、代码实现与函数封装 1.项目文件创建 2.头文件说明 3.函数封装 1&#xff09;菜单实现 2&#xff09;进度条实现 3&#xff09;main函数实现 4&#xff09;Game函数 5&#xff0…

TIM输出比较之PWM驱动LED呼吸灯应用案例

文章目录 前言一、应用案例演示二、电路接线图三、应用案例代码四、应用案例分析4.1 基本思路4.2 相关库函数介绍4.3 初始化PWM模块4.3.1 RCC开启时钟4.3.2 配置时基单元4.3.3 配置输出比较单元4.3.4 配置GPIO4.3.5 运行控制 4.4 PWM输出模块4.5 主程序 前言 提示&#xff1a;…

[数据集][目标检测]竹子甘蔗发芽缺陷检测数据集VOC+YOLO格式2953张3类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;2953 标注数量(xml文件个数)&#xff1a;2953 标注数量(txt文件个数)&#xff1a;2953 标注…

电脑录屏高清视频制作:如何选择适合的分辨率和参数

在当今数字化时代&#xff0c;无论是教学、演示还是游戏直播&#xff0c;电脑录屏已经成为了一个不可或缺的工具。然而画质往往是录屏质量的关键因素&#xff0c;许多用户在追求高清录屏体验时&#xff0c;常常面临选择1080p还是4K分辨率的困惑。本文将深入探讨如何优化电脑录屏…

SpringBoot自动配置

一、Condition条件判断功能 Condition 是在Spring 4.0 增加的条件判断功能&#xff0c;其主要作用是判断条件是否满足&#xff0c;从而决定是否初始化并向容器注入Bean对象。通过Conditional注解及其一系列的其他相关注解实现。 在Spring Boot中&#xff0c;条件匹配&#xf…

性能优化理论篇 | swap area是个什么东西

我们知道每台计算机的内存&#xff08;RAM&#xff09;都是有限的&#xff0c;而我们的应用程序需要加载到内存才能被运行&#xff0c;如果一台机器运行多个应用程序时&#xff0c;内存可能会耗尽。Linux 系统中的“交换空间&#xff08;也称为交换分区&#xff09;”可以帮助缓…

使用AWS Lambda轻松开启Amazon Rekognition之旅

这是本系列文章的第一篇&#xff0c;旨在通过动手实践&#xff0c;帮助大家学习亚马逊云科技的生成式AI相关技能。通过这些文章&#xff0c;大家将掌握如何利用亚马逊云科技的各类服务来应用AI技术。 那么让我们开始今天的内容吧&#xff01; 介绍 什么是Amazon Rekognition&…

微软的免费“后悔药“-Windows File Recovery (WinFR)

微软的免费"后悔药"-Windows File Recovery (WinFR) 当你不小心误删除了文件或因各种意外情况导致数据丢失&#xff0c;可以使用 EasyRecovery、Disk Drill、DiskGenius、Recuva 等“上古”软件&#xff0c;也可以交由专业机构进行恢复。微软&#xff08;Microsoft&…

揭秘!移动安全管理系统是什么?有什么功能?(从小白到精通一文揭晓!)

在2024年&#xff0c;移动终端管控软件在企业和组织中的应用日益广泛。 移动安全管理系统不仅提高了管理效率&#xff0c;还增强了数据安全性和移动办公的便捷性。 以下是六款值得推荐的移动终端管控软件&#xff1a; 1. 安企神 特点&#xff1a;作为行业领头羊&#xff0c;…

框架漏洞大全【万字总结】

文章目录 常见语言开发框架&#xff1a;Thinkphp远程代码执行5.0.23 rce介绍影响版本复现 CNVD-2018-24942介绍影响版本复现 任意文件包含包含日志-3.2x介绍影响版本复现 包含语言&#xff08;QVD-2022-46174&#xff09;介绍影响版本复现 sql注入漏洞(5.0.x)介绍影响版本复现 …

(26)微信检查联系人和清粉(针对删除和拉黑)-微信UI自动化(.Net+C#)

整理 | 小耕家的喵大仙 出品 | CSDN&#xff08;ID&#xff1a;lichao19897314&#xff09; Q Q | 978124155 往期知识回顾 (1)开启探索微信自动化之路-微信UI自动化(.NetC#) (2)初始化微信窗体UI自动化实例-微信UI自动化(.NetC#) (3)采用热键终止微信采集任务-微信UI自动…

UI自动化测试:遍历页面元素并获取文本的实践分享!

遍历读取元素的文本 在写UI自动化过程中还会遇到需要遍历读取元素的情况。下面分享以「稿定设计」网站为例&#xff0c;想要通过UI自动化读取素材内容的操作菜单列表&#xff0c;如下图&#xff1a; 代码片段和解释 # 获取菜单列表元素的文本信息&#xff0c;例如&#xff1a…

算法基础及例题

1、双指针 维护区间信息、子序列匹配、利用序列有序性、单项链表找环双指针 - OI Wiki (oi-wiki.org) 盛最多水的容器https://leetcode.cn/problems/container-with-most-water/ public class Solution {public int maxArea(int[] height) {int l 0, r height.length - 1;int…

泡泡玛特2024半年报发布:首度划分四大品类 手办收入占比首次低于60%

8月20日&#xff0c;泡泡玛特发布2024上半年业绩报告。报告显示&#xff0c;2024年上半年泡泡玛特国际集团实现营收45.6亿元&#xff08;人民币&#xff0c;下同&#xff09;&#xff0c;同比增长62.0%&#xff0c;经调整后净利10.2亿元&#xff0c;同比增长90.1%。 上半年泡泡…