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

【数据结构】六、图:2.邻接矩阵、邻接表(有向图、无向图、带权图)

二、存储结构

文章目录

  • 二、存储结构
    • ❗1.邻接矩阵
      • 1.1无向图
        • ❗邻接矩阵-无向图代码-C
      • 1.2有向图
        • ❗邻接矩阵-有向图代码-C
      • 1.3带权图
      • 1.4性能分析
      • 1.5相乘
    • ❗2.邻接表
      • 2.1无向图
      • 2.2有向图
      • ❗邻接表-C
    • 邻接矩阵VS邻接表
      • 邻接矩阵
      • 邻接表

❗1.邻接矩阵

图的邻接矩阵(Adjacency Matrix) 存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。

用1表示有边,0表示没有边。

在这里插入图片描述

结点数为n的图G,邻接矩阵A是一个n*n的方阵,将G顶点编号为 v 1 , v 2 , . . . , v n v_1,v_2,...,v_n v1,v2,...,vn
A [ i ] [ j ] = { 1 , 若边集 E ( G ) 中有 ( v i , v j ) o r < v i , v j > 0 , 若边集 E ( G ) 中没有 ( v i , v j ) o r < v i , v j > A[i][j] = \begin{cases} 1, & 若边集E(G)中有(v_i,v_j)or<v_i,v_j> \\[2ex] 0, & 若边集E(G)中没有(v_i,v_j)or<v_i,v_j> \end{cases} A[i][j]= 1,0,若边集E(G)中有(vi,vj)or<vi,vj>若边集E(G)中没有(vi,vj)or<vi,vj>
存储结构定义:

#define MaxVertexNum 100	//顶点数目的最大值
typedef char VertexType;  //顶点的数据类型
typedef int EdgeType;     //带权图中边上权值的数据类型typedef struct{VertexType Vex[MaxVertexNum] ;	//顶点表:存放结构or信息EdgeType Edge[MaxVertexNum][MaxVertexNum];	//邻接矩阵,边表int vexnum, edgenum;		//图的当前顶点数和边数/弧数
}MGraph;

1.1无向图

  1. 无向图的邻接矩阵一定是一个对称矩阵(即从矩阵的左上角到右下角的主对角线为轴,右上角的元与左下角相对应的元全都是相等的)。

    因此,在存储邻接矩阵时只需存储上(或下)三角矩阵的元素。

  2. 对于无向图,第i个结点的 = 邻接矩阵的**第i行(或第i列)**非零元素(或非∞元素)的个数。

    顶点A的度就是0+1+1+1+0+0=3。

❗邻接矩阵-无向图代码-C
/* 图邻接矩阵(Adjacency Matrix) 存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。无向图C实现
*/#include <stdio.h>
#include <string.h>
#define MaxVertexNum  100 //顶点数目最大值
typedef char VertexType;  //顶点的数据类型
typedef int EdgeType;     //带权图中边上权值的数据类型#define numVertexes 6   // 顶点个数,用于visited数组
#define numEdges 7      // 边个数typedef struct
{VertexType Vex[MaxVertexNum];   //顶点表EdgeType Edge[MaxVertexNum][MaxVertexNum];  //邻接矩阵,边表int vexnum, edgenum;    //图的顶点数和弧数
}MGraph;void create_Graph(MGraph *G);
void create_Graph_ByArray(MGraph *G, int edges[][3]);
void print_Matrix(MGraph G);
void DFS(MGraph G,int v);
void DFSTraverse(MGraph G);
void BFS(MGraph G, int v);
void BFSTraverse(MGraph G);int main()
{MGraph G;// 创建无向图方法1//create_Graph(&G);// 创建无向图方法2//这里可以使用int,因为edge的EdgeType是intint edges[numEdges][3] = {  // 边的起点序号,终点序号,权值{1,2,5},{1,3,1},{1,4,6},{2,5,3},{2,6,4},{3,5,3},{4,6,2}};create_Graph_ByArray(&G,edges);print_Matrix(G);printf("\nDFS:");DFS(G,0);printf("\nBFS:");BFS(G,0);printf("\nBFS直接遍历(非连通图):");BFSTraverse(G);return 0;
}// 创建无向图
void create_Graph(MGraph *G){int i, j;int start, end;  //边的起点序号、终点序号int w;   //边上的权值// 所创建无向图的顶点数和边数(用空格隔开)。这里也可以输入G->vexnum = numVertexes;G->edgenum = numEdges;printf("\n");//图的初始化initfor (i=0; i<G->vexnum; i++){for (j=0; j<G->vexnum; j++){if (i == j)G->Edge[i][j] = 0;      //结点自身elseG->Edge[i][j] = 32767;   //初始都为表示∞}}//顶点信息存入顶点表for (i=0; i<G->vexnum; i++){// printf("请输入第%d个顶点的信息(int):",i+1);// scanf("%d", &G->Vex[i]);//这里不输入了,暂时默认0开始G->Vex[i] = i+1;}printf("\n");//输入无向图边的信息for (i=0; i<G->edgenum; i++){printf("请输入边的起点序号,终点序号,权值(用空格隔开)(int,从1开始,没有0):");scanf("%d%d%d", &start, &end, &w);G->Edge[start-1][end-1] = w;G->Edge[end-1][start-1] = w;   //无向图具有对称性}
}// 创建无向图
void create_Graph_ByArray(MGraph *G, int edges[][3]){int i, j;int start, end;  //边的起点序号、终点序号int w;   //边上的权值// 所创建无向图的顶点数和边数(用空格隔开)。这里也可以输入G->vexnum = numVertexes;G->edgenum = numEdges;printf("\n");//图的初始化initfor (i=0; i<G->vexnum; i++){for (j=0; j<G->vexnum; j++){if (i == j)G->Edge[i][j] = 0;      //结点自身elseG->Edge[i][j] = 32767;   //初始都为表示∞}}//顶点信息存入顶点表for (i=0; i<G->vexnum; i++){// printf("请输入第%d个顶点的信息(int):",i+1);// scanf("%d", &G->Vex[i]);//这里不输入了,暂时默认0开始G->Vex[i] = i+1;}printf("\n");// 输入无向图边的信息for (i=0; i<G->edgenum; i++){start = edges[i][0];end = edges[i][1];w = edges[i][2];G->Edge[start-1][end-1] = w;G->Edge[end-1][start-1] = w;   //无向图具有对称性}
}// 输出图
void print_Matrix(MGraph G){int i, j;printf("\n图的顶点为:");for (i=0; i<G.vexnum; i++)printf("%d ", G.Vex[i]);printf("\n输出邻接矩阵:\n");// 横坐标printf(" "); //表示行坐标,前面空格留给纵坐标for (i=0; i<G.vexnum; i++)printf("%5d", G.Vex[i]);printf("\n");for (i=0; i<G.vexnum; i++){// 纵坐标printf("\n%d", G.Vex[i]);// 输出邻接矩阵for (j=0; j<G.vexnum; j++){if (G.Edge[i][j] == 32767)printf("%7s", "∞");elseprintf("%5d", G.Edge[i][j]);}printf("\n");}	
}// ------------------------- DFS 深度优先遍历------------------------
int visited[numVertexes]={0};
// 注意:是从0下标开始
void DFS(MGraph G, int x){// 访问顶点xprintf("%d",G.Vex[x]);visited[x]=1;   //设已访问标记// 遍历x的邻接顶点for(int v=0; v<G.vexnum; v++){//i为x的尚未访问的邻接顶点if(!visited[v] && G.Edge[x][v] != 32767){DFS(G, v);}}
}
//对非连通图进行深度优先遍历
void DFSTraverse(MGraph G){//把所有结点全部标记为false,表示没有访问过for(int v=0; v<G.vexnum; v++){visited[v] = 0;}for(int v=0; v<G.vexnum; v++){	//从v=0开始遍历if(!visited[v]){DFS(G, v);}}
}/// @brief /辅助队列
typedef struct{int data[numVertexes];int f,r;
}Que;
void InitQueue(Que &Q){Q.f=Q.r=0;
}
void In(Que &Q,int e){if ((Q.r+1)%numVertexes==Q.f) return; Q.data[Q.r]=e;Q.r=(Q.r+1)%numVertexes;
}
void Out(Que &Q,int &e){if(Q.f==Q.r) return;e=Q.data[Q.f];Q.f=(Q.f+1)%numVertexes;
}// ------------------------- BFS 广度优先遍历------------------------
// 对连通图进行广度优先遍历
void BFS(MGraph G, int v){Que Q;InitQueue(Q);	//初始化一辅助用的队列//把所有结点全部标记为false,表示没有访问过for(int i=0; i<G.vexnum; i++){visited[i] = 0;}printf("%d",G.Vex[v]);visited[v]=1;In(Q,v);while(Q.f!=Q.r){Out(Q,v);//把出队结点的相邻的所有结点入队for(int w=0; w<G.vexnum; w++){if(!visited[w] && G.Edge[v][w] != 32767){printf("%d",G.Vex[w]);visited[w]=1;In(Q,w);}}}}// 对非连通图的广度遍历
void BFSTraverse(MGraph G){Que Q;InitQueue(Q);	//初始化一辅助用的队列int v;//把所有结点全部标记为false,表示没有访问过for(v=0; v<G.vexnum; v++){visited[v] = 0;}for(v=0; v<G.vexnum; v++){		//这里是从0开始//若是未访问过就处理if(!visited[v]){printf("%d",G.Vex[v]);visited[v]=1;In(Q,v);while(Q.f!=Q.r){Out(Q,v);//把出队结点的相邻的所有结点入队for(int w=0; w<G.vexnum; w++){if(!visited[w] && G.Edge[v][w] != 32767){printf("%d",G.Vex[w]);visited[w]=1;In(Q,w);}}}}}}

请输入边的起点序号,终点序号,权值(用空格隔开)(int,从1开始,没有0):1 2 5
请输入边的起点序号,终点序号,权值(用空格隔开)(int,从1开始,没有0):1 3 1
请输入边的起点序号,终点序号,权值(用空格隔开)(int,从1开始,没有0):1 4 6
请输入边的起点序号,终点序号,权值(用空格隔开)(int,从1开始,没有0):2 5 3
请输入边的起点序号,终点序号,权值(用空格隔开)(int,从1开始,没有0):2 6 4
请输入边的起点序号,终点序号,权值(用空格隔开)(int,从1开始,没有0):3 5 3
请输入边的起点序号,终点序号,权值(用空格隔开)(int,从1开始,没有0):4 6 2

图的顶点为:1 2 3 4 5 6
输出邻接矩阵:
1 2 3 4 5 6

1 0 5 1 6 ∞ ∞

2 5 0 ∞ ∞ 3 4

3 1 ∞ 0 ∞ 3 ∞

4 6 ∞ ∞ 0 ∞ 2

5 ∞ 3 3 ∞ 0 ∞

6 ∞ 4 ∞ 2 ∞ 0

DFS:125364
BFS:123456
BFS直接遍历(非连通图):123456

1.2有向图

主对角线上数值依然为0。但因为是有向图,所以此矩阵并不对称

【注意】行是出度

有向图讲究入度与出度,
第i个结点的入度 = 邻接矩阵的第i列的非零元素的个数。(竖着)
第i个结点的出度 = 邻接矩阵的第i行的非零元素的个数。(横着)
第i个结点的度 = 第i行、第i列的非零元素个数之和。

❗邻接矩阵-有向图代码-C

与无向图的区别,仅有在构造的时候:

G->Edge[start-1][end-1] = w;
//有向图不具有对称性

code:

/* 图邻接矩阵(Adjacency Matrix) 存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。有向图C实现
*/#include <stdio.h>
#include <string.h>
#define MaxVertexNum  100 //顶点数目最大值
typedef char VertexType;  //顶点的数据类型
typedef int EdgeType;     //带权图中边上权值的数据类型#define numVertexes 6   // 顶点个数,用于visited数组
#define numEdges 7      // 边个数typedef struct
{VertexType Vex[MaxVertexNum];   //顶点表EdgeType Edge[MaxVertexNum][MaxVertexNum];  //邻接矩阵,边表int vexnum, edgenum;    //图的顶点数和弧数
}MGraph;void create_Graph(MGraph *G);
void create_Graph_ByArray(MGraph *G, int edges[][3]);
void print_Matrix(MGraph G);
void DFS(MGraph G,int v);
void DFSTraverse(MGraph G);
void BFS(MGraph G, int v);
void BFSTraverse(MGraph G);int main()
{MGraph G;// 创建有向图方法1//create_Graph(&G);// 创建有向图方法2//这里可以使用int,因为edge的EdgeType是intint edges[numEdges][3] = {  // 边的起点序号,终点序号,权值{1,2,5},{3,1,1},{4,1,6},{5,2,3},{5,3,3},{6,2,4},{6,4,2}};create_Graph_ByArray(&G,edges);print_Matrix(G);printf("\nDFS:");DFS(G,0);printf("\nDFS直接遍历(非连通图):");DFSTraverse(G);printf("\nBFS:");BFS(G,0);printf("\nBFS直接遍历(非连通图):");BFSTraverse(G);return 0;
}// 创建有向图
void create_Graph(MGraph *G){int i, j;int start, end;  //边的起点序号、终点序号int w;   //边上的权值// 所创建有向图的顶点数和边数(用空格隔开)。这里也可以输入G->vexnum = numVertexes;G->edgenum = numEdges;printf("\n");//图的初始化initfor (i=0; i<G->vexnum; i++){for (j=0; j<G->vexnum; j++){if (i == j)G->Edge[i][j] = 0;      //结点自身elseG->Edge[i][j] = 32767;   //初始都为表示∞}}//顶点信息存入顶点表for (i=0; i<G->vexnum; i++){// printf("请输入第%d个顶点的信息(int):",i+1);// scanf("%d", &G->Vex[i]);//这里不输入了,暂时默认0开始G->Vex[i] = i+1;}printf("\n");//输入有向图边的信息for (i=0; i<G->edgenum; i++){printf("请输入边的起点序号,终点序号,权值(用空格隔开)(int,从1开始,没有0):");scanf("%d%d%d", &start, &end, &w);G->Edge[start-1][end-1] = w;//有向图不具有对称性}
}// 创建有向图
void create_Graph_ByArray(MGraph *G, int edges[][3]){int i, j;int start, end;  //边的起点序号、终点序号int w;   //边上的权值// 所创建有向图的顶点数和边数(用空格隔开)。这里也可以输入G->vexnum = numVertexes;G->edgenum = numEdges;printf("\n");//图的初始化initfor (i=0; i<G->vexnum; i++){for (j=0; j<G->vexnum; j++){if (i == j)G->Edge[i][j] = 0;      //结点自身elseG->Edge[i][j] = 32767;   //初始都为表示∞}}//顶点信息存入顶点表for (i=0; i<G->vexnum; i++){// printf("请输入第%d个顶点的信息(int):",i+1);// scanf("%d", &G->Vex[i]);//这里不输入了,暂时默认0开始G->Vex[i] = i+1;}printf("\n");// 输入有向图边的信息for (i=0; i<G->edgenum; i++){start = edges[i][0];end = edges[i][1];w = edges[i][2];G->Edge[start-1][end-1] = w;//有向图不具有对称性}
}// 输出图
void print_Matrix(MGraph G){int i, j;printf("\n图的顶点为:");for (i=0; i<G.vexnum; i++)printf("%d ", G.Vex[i]);printf("\n输出邻接矩阵:\n");// 横坐标printf(" "); //表示行坐标,前面空格留给纵坐标for (i=0; i<G.vexnum; i++)printf("%5d", G.Vex[i]);printf("\n");for (i=0; i<G.vexnum; i++){// 纵坐标printf("\n%d", G.Vex[i]);// 输出邻接矩阵for (j=0; j<G.vexnum; j++){if (G.Edge[i][j] == 32767)printf("%7s", "∞");elseprintf("%5d", G.Edge[i][j]);}printf("\n");}	
}// ------------------------- DFS 深度优先遍历------------------------
int visited[numVertexes]={0};
// 注意:是从0下标开始
void DFS(MGraph G, int x){// 访问顶点xprintf("%d",G.Vex[x]);visited[x]=1;   //设已访问标记// 遍历x的邻接顶点for(int v=0; v<G.vexnum; v++){//i为x的尚未访问的邻接顶点if(!visited[v] && G.Edge[x][v] != 32767){DFS(G, v);}}
}
//对非连通图进行深度优先遍历
void DFSTraverse(MGraph G){//把所有结点全部标记为false,表示没有访问过for(int v=0; v<G.vexnum; v++){visited[v] = 0;}for(int v=0; v<G.vexnum; v++){	//从v=0开始遍历if(!visited[v]){DFS(G, v);}}
}/// @brief /辅助队列
typedef struct{int data[numVertexes];int f,r;
}Que;
void InitQueue(Que &Q){Q.f=Q.r=0;
}
void In(Que &Q,int e){if ((Q.r+1)%numVertexes==Q.f) return; Q.data[Q.r]=e;Q.r=(Q.r+1)%numVertexes;
}
void Out(Que &Q,int &e){if(Q.f==Q.r) return;e=Q.data[Q.f];Q.f=(Q.f+1)%numVertexes;
}// ------------------------- BFS 广度优先遍历------------------------
// 对连通图进行广度优先遍历
void BFS(MGraph G, int v){Que Q;InitQueue(Q);	//初始化一辅助用的队列//把所有结点全部标记为false,表示没有访问过for(int i=0; i<G.vexnum; i++){visited[i] = 0;}printf("%d",G.Vex[v]);visited[v]=1;In(Q,v);while(Q.f!=Q.r){Out(Q,v);//把出队结点的相邻的所有结点入队for(int w=0; w<G.vexnum; w++){if(!visited[w] && G.Edge[v][w] != 32767){printf("%d",G.Vex[w]);visited[w]=1;In(Q,w);}}}}// 对非连通图的广度遍历
void BFSTraverse(MGraph G){Que Q;InitQueue(Q);	//初始化一辅助用的队列int v;//把所有结点全部标记为false,表示没有访问过for(v=0; v<G.vexnum; v++){visited[v] = 0;}for(v=0; v<G.vexnum; v++){		//这里是从0开始//若是未访问过就处理if(!visited[v]){printf("%d",G.Vex[v]);visited[v]=1;In(Q,v);while(Q.f!=Q.r){Out(Q,v);//把出队结点的相邻的所有结点入队for(int w=0; w<G.vexnum; w++){if(!visited[w] && G.Edge[v][w] != 32767){printf("%d",G.Vex[w]);visited[w]=1;In(Q,w);}}}}}}

1.3带权图

对于带权图而言,若顶点vi和vj之间有边相连,则邻接矩阵中对应项存放着该边对应的权值。

没有边,那么距离就是无穷。

自己指向自己,那么边就是0。

A[i][j]

在这里插入图片描述

1.4性能分析

一个一维数组,一个二维数组,存储空间是O(n) + O(n2) = O(n2) ,即O(IVI2)。

邻接矩阵法求顶点的度/出度/入度的时间复杂度为O(IVI)。

适合用于稠密图

1.5相乘

设图 G 的邻接矩阵为 A(矩阵元素为0/1),则An的元素 An[i][j] 等于由顶点 i 到顶点 j 的长度为 n 的路径的数目。

在这里插入图片描述

比如:

A2[1][4]:就是从A->B,然后B->D,这样两次(路径长度为2)的。

A2[1][4] = 1,意思就是满足长度为2的路径,只有一条。

A3同理,表示路径长度为 3 的路径数目。

在这里插入图片描述

❗2.邻接表

顺序+链式存储

  • 不足

当一个图为稀疏图时(边数相对顶点较少),使用邻接矩阵法显然要浪费大量的存储空间,如下图所示:

在这里插入图片描述

  • 邻接表(Adjacency List)

而图的邻接表法结合了顺序存储 + 链式存储方法,减少了这种不必要的浪费。

所谓邻接表,是指对图G中的每个顶点 vi 建立一个单链表,第 i 个单链表中的结点表示依附于顶点 vi 的边,这个单链表就称为顶点 vi 的边表。

​ 那么,一个结点的出度就是单链表内的结点个数

边表的头指针和顶点的数据信息采用顺序存储(称为顶点表),所以在邻接表中存在两种结点:顶点表结点和边表结点,如下图所示:

在这里插入图片描述

链表的结点,没有先后顺序,都是直接连在顶点上的。所以链表有很多表示方式,不唯一

一个参考图:

在这里插入图片描述

存储结构定义:

#define MaxVertexNum  100 //顶点数目最大值
typedef char VertexType;  //顶点的数据类型
typedef int EdgeType;     //带权图中边上权值的数据类型//边表结点
typedef struct EdgeNode{int adjvex;		        //该弧所指向的顶点的下标或者位置EdgeType weight;	    //权值,对于非网图可以不需要struct EdgeNode *next;	//指向下一个邻接点
}EdgeNode;//顶点表结点
typedef struct VertexNode{VertexType data;	    //顶点域,存储顶点信息EdgeNode *firstedge;	//边表头指针
}VertexNode, AdjList[MaxVertexNum];//邻接表
typedef struct{AdjList adjList;int vexnum, edgenum;		//图的当前顶点数和边数/弧数
}LinkGraph;

2.1无向图

无向图存储中,一条边会出现在两端点的链表中,边结点的数量是2|E|,整体空间复杂度为 O(|V|+ 2|E|)。

在这里插入图片描述

2.2有向图

有向图存储中,边结点的数量是|E|,整体空间复杂度为 O(|V|+ |E|)。

在这里插入图片描述

❗邻接表-C

/* 图邻接表(Adjacency List)存储方式是结合了 顺序存储 + 链式存储方法,减少了邻接矩阵不必要的浪费。无向图 与 有向图 的区别:就是一条edge是否添加两次到两端节点这里默认是无向图,但是在代码中注释了有向图C实现
*/#include <stdio.h>
#include <string.h>
#include <stdlib.h>#define MaxVertexNum  100 //顶点数目最大值
typedef char VertexType;  //顶点的数据类型
typedef int EdgeType;     //带权图中边上权值的数据类型#define numVertexes 5   // 顶点个数,用于visited数组
#define numEdges 7      // 边个数//边表结点
typedef struct EdgeNode{int adjvex;		        //该弧所指向的顶点的下标或者位置EdgeType weight;	    //权值,对于非网图可以不需要struct EdgeNode *next;	//指向下一个邻接点
}EdgeNode;//顶点表结点
typedef struct VertexNode{VertexType data;	    //顶点域,存储顶点信息EdgeNode *firstedge;	//边表头指针
}VertexNode, AdjList[MaxVertexNum];
// AdjList[MaxVertexNum]是静态的链表//邻接表
typedef struct{AdjList adjList;int vexnum, edgenum;		//图的当前顶点数和边数/弧数
}LinkGraph;void create_Graph(LinkGraph *G);
void create_Graph_ByArray(LinkGraph *G, int edges[][3]);
void print_Graph(LinkGraph G);int main()
{LinkGraph G;// 创建图方法1// create_Graph(&G);// 创建图方法2// 这里可以使用int,因为edge的EdgeType是intint edges[numEdges][3] = {  // 边的起点序号,终点序号,权值{1,2,1},{1,5,1},{2,3,1},{2,4,1},{2,5,1},{3,4,1},{4,5,1}};create_Graph_ByArray(&G,edges);print_Graph(G);return 0;
}// 创建图
void create_Graph(LinkGraph *G){int i, j;int start, end;  //边的起点序号、终点序号int w;   //边上的权值// 所创建图的顶点数和边数(用空格隔开)。这里也可以输入G->vexnum = numVertexes;G->edgenum = numEdges;printf("\n");//初始化顶点for (i=0; i<G->vexnum; i++){G->adjList[i].data = i+1;  //顶点初始化G->adjList[i].firstedge = NULL;  //边表头指针初始化为空}//初始化边for (i=0; i<G->edgenum; i++){printf("请输入边的起点序号,终点序号,权值(用空格隔开)(int,从1开始,没有0):");scanf("%d%d%d", &start, &end, &w);// 创建边表结点// 有向图,只需要创建一个结点EdgeNode *e = (EdgeNode*)malloc(sizeof(EdgeNode));e->adjvex = end-1;  //终点序号e->weight = w;//将结点e插入顶点表start的边表中e->next = G->adjList[start-1].firstedge;G->adjList[start-1].firstedge = e;// 无向图,还要插入终点序号的边表//创建边表结点EdgeNode *e_ = (EdgeNode*)malloc(sizeof(EdgeNode));e_->adjvex = start-1;  //起点序号e_->weight = w;//将结点e_插入顶点表end的边表中e_->next = G->adjList[end-1].firstedge;G->adjList[end-1].firstedge = e_;}
}// 使用数组创建图
void create_Graph_ByArray(LinkGraph *G, int edges[][3]){int i, j;int start, end;  //边的起点序号、终点序号int w;   //边上的权值// 所创建图的顶点数和边数(用空格隔开)。这里也可以输入G->vexnum = numVertexes;G->edgenum = numEdges;printf("\n");//初始化顶点for (i=0; i<G->vexnum; i++){G->adjList[i].data = i+1;  //顶点初始化G->adjList[i].firstedge = NULL;  //边表头指针初始化为空}//初始化边for (i=0; i<G->edgenum; i++){start = edges[i][0];end = edges[i][1];w = edges[i][2];// 创建边表结点// 有向图,只需要创建一个结点EdgeNode *e = (EdgeNode*)malloc(sizeof(EdgeNode));e->adjvex = end-1;  //终点序号e->weight = w;//将结点e插入顶点表start的边表中e->next = G->adjList[start-1].firstedge;G->adjList[start-1].firstedge = e;// 无向图,还要插入终点序号的边表//创建边表结点EdgeNode *e_ = (EdgeNode*)malloc(sizeof(EdgeNode));e_->adjvex = start-1;  //起点序号e_->weight = w;//将结点e_插入顶点表end的边表中e_->next = G->adjList[end-1].firstedge;G->adjList[end-1].firstedge = e_;}
}// 输出图
void print_Graph(LinkGraph G){int i, j;printf("\n图的顶点为:");for (i=0; i<G.vexnum; i++){printf("%d ", G.adjList[i].data);}// 输出邻接表printf("\n图的邻接矩阵为:\n");for (i=0; i<G.vexnum; i++){		//遍历顶点printf("%d. %d:> ",i, G.adjList[i].data);	//输出顶点EdgeNode *p = G.adjList[i].firstedge;	//边结构while (p){							//遍历边printf("%d-->", p->adjvex+1);p = p->next;}printf("Null.\n");}
}

图的顶点为:1 2 3 4 5
图的邻接矩阵为:

  1. 1:> 5–>2–>Null.
  2. 2:> 5–>4–>3–>1–>Null.
  3. 3:> 4–>2–>Null.
  4. 4:> 5–>3–>2–>Null.
  5. 5:> 4–>2–>1–>Null.

邻接矩阵VS邻接表

邻接矩阵邻接表
空间复杂度O( IVI2 )无向图O(|V|+2|E|);
有向图O(|V|+|E|)。
适用于稠密图稀疏图
表示方式唯一不唯一
计算度、出度、入度必须遍历对应的行或列计算有向图的度、入度不方便,其余很方便。
找相邻的边必须遍历对应的行或列找有向图的入边不方便,其余很方便。
删除边、结点删除边很方便,删除结点要移动大量数据删除边、结点都不方便

邻接矩阵

  1. 在简单应用中,可直接用二维数组作为图的邻接矩阵(顶点信息等均可省略)。
  2. 当邻接矩阵中的元素仅表示相应的边是否存在时,EdgeType可定义为值为0和1的枚举类型或者bool。
  3. 无向图的邻接矩阵是对称矩阵,对规模特大的邻接矩阵可采用压缩存储。
  4. 邻接矩阵表示法的空间复杂度为O(n2),其中n为图的顶点数|V|。
  5. 用邻接矩阵法存储图,很容易确定图中任意两个顶点之间是否有边相连。但是,要确定图中有多少条边,则必须按行、按列对每个元素进行检测,所花费的时间代价很大。
  6. 稠密图适合使用邻接矩阵的存储表示。

邻接表

  1. 对于稀疏图,采用邻接表表示将极大地节省存储空间。

  2. 在邻接表中,给定一顶点,能很容易地找出它的所有邻边,因为只需要读取它的邻接表。在邻接矩阵中,相同的操作则需要扫描一行,花费的时间为O(n)。
    但是,若要确定给定的两个顶点间是否存在边,则在邻接矩阵中可以立刻查到,而在邻接表中则需要在相应结点对应的边表中查找另一结点,效率较低。

  3. 在有向图的邻接表表示中,
    求一个给定顶点的出度只需计算其邻接表中的结点个数;
    但求其顶点的入度则需要遍历全部的邻接表。

    因此,也有人采用逆邻接表的存储方式来加速求解给定顶点的入度。当然,这实际上与邻接表存储方式是类似的。

  4. 图的邻接表表示并不唯一,因为在每个顶点对应的单链表中,各边结点的链接次序可以是任意的,它取决于建立邻接表的算法及边的输入次序。

相关文章:

【数据结构】六、图:2.邻接矩阵、邻接表(有向图、无向图、带权图)

二、存储结构 文章目录 二、存储结构❗1.邻接矩阵1.1无向图❗邻接矩阵-无向图代码-C 1.2有向图❗邻接矩阵-有向图代码-C 1.3带权图1.4性能分析1.5相乘 ❗2.邻接表2.1无向图2.2有向图❗邻接表-C 邻接矩阵VS邻接表邻接矩阵邻接表 ❗1.邻接矩阵 图的邻接矩阵(Adjacency Matrix) 存…...

财务会计与管理会计(三)

文章目录 销售回款提成表MATCH函数的模糊查询在提成类业务中的应用 营业收入分类数据分析OFFSET函数在制作图表数据中的应用 自动生成销售记录对账单VLOOKUP函数的应用 销售回款提成表 MATCH函数的模糊查询在提成类业务中的应用 G3INDEX(I$1:M$1,MATCH(E3,H3:M3,1)) G3INDEX(…...

【数据结构和算法】(基础篇三)——栈和队列

栈和队列 栈&#xff08;Stack&#xff09;和队列&#xff08;Queue&#xff09;是两种非常基本的数据结构&#xff0c;它们主要用于存储和检索元素。尽管它们都用于管理一组数据项&#xff0c;但它们的访问规则和数组都是不同的。 栈 栈是一种后进先出&#xff08;Last In,…...

Linux截图工具gsnap移植arm平台过程记录

Linux截图工具gsnap移植arm平台过程记录 最近工作中一款新产品开发接近尾声&#xff0c;需要写文档截图产品图形&#xff0c;找了一款开源的Linux截屏工具gsnap&#xff0c;将其移植到ARM产品中&#xff0c;这里记录一下移植过程。 gsnap 这个工具源代码就是一个C语言源文件&a…...

密码学知识点02

#来自ウルトラマンレオ&#xff08;雷欧&#xff09; 1 常见加密方式 2 对称加密 采用单钥密码系统的加密方法&#xff0c;同一个密钥可以同时用作信息的加密和解密&#xff0c;这种加密方法称为对称加密&#xff0c;也称为单密钥加密。 常见加密算法&#xff1a; DES : Data…...

实现Pytest测试用例按顺序循环执行多次

要实现测试用例按顺序循环执行多次&#xff0c;可以使用 pytest 的自定义装饰器或插件。这里有两种方法可以实现这个需求&#xff1a; 方法一&#xff1a;使用 pytest-repeat 插件 pytest-repeat 插件允许你重复执行测试用例。你可以使用 --count 参数来指定每个测试用例的执…...

SVN工作原理和使用示例

SVN&#xff08;Subversion&#xff09;是另一种版本控制系统&#xff0c;用于管理项目文件及其变更历史。与Git不同&#xff0c;SVN是集中式版本控制系统&#xff0c;这意味着所有版本控制操作都集中在一个中央服务器上。以下是SVN的工作原理和基本使用示例。 目录 SVN 工作…...

云服务器部署Java+Vue前后端分离项目

1、申请一个云服务器 选择云服务器&#xff1a;阿里云、腾讯云、百度云、京东云、华为云等等&#xff0c;我使用的是阿里云服务器。 2、远程链接服务器 使用FinalShell工具或者其他远程工具&#xff0c;使用SSH链接&#xff0c;主机地址要填写阿里云服务的公网ip&#xff0c;如…...

C++的7种设计模式原则

一、设计模式前言 设计模式&#xff08;Design Patterns&#xff09;的“模式”指的是一种在软件设计中经过验证的、解决特定问题的方案。它们不是具体的代码&#xff0c;而是解决常见设计问题的抽象方案或模板。设计模式提供了一种标准的方式来组织代码&#xff0c;以提高代码…...

24.8.5数据结构|栈

栈-弹夹 1、定义&#xff1a; 栈就是特殊的线性表&#xff0c;与之前的线性表的区别就是增加了约束&#xff0c;只允许在一端插入和删除&#xff0c;就这麽简单。 2、基本操作 栈的插入操作叫&#xff1a;入栈{进栈、压栈}&#xff1b;栈的删除&#xff1a;出栈{退栈&#x…...

LeetCode算法题训练

力扣刷题训练 开始记录力扣的刷题之路 刷题思路来自灵茶山艾府 入门题单&#xff1a; 「新」动计划 编程入门编程基础 0 到 1 训练方法 A 滑动窗口&#xff08;定长/不定长/多指针&#xff09;二分算法&#xff08;二分答案/最小化最大值/最大化最小值/第K小&#xff09…...

Python | Leetcode Python题解之第326题3的幂

题目&#xff1a; 题解&#xff1a; class Solution:def isPowerOfThree(self, n: int) -> bool:return n > 0 and 1162261467 % n 0...

手机CPU性能天梯图(2024年8月),含安兔兔/GB6/3DMark跑分

原文地址&#xff08;高清无水印原图/持续更新/含榜单出处链接&#xff09;&#xff1a; 2024年8月手机处理器天梯图 2024年8月1日更新日志&#xff1a;由于近期并未有新处理器发布&#xff0c;故只做常规更新&#xff1b;移除鲁大师天梯图&#xff1b;补充其它天梯图数量。 -…...

通过实际的例子和代码演示,可以更好地理解 `optional` 的使用方式和应用场景

当然&#xff0c;让我们通过一些实际的例子来演示 std::optional 的使用方式和应用场景。 场景 1&#xff1a;函数返回值 假设我们有一个函数&#xff0c;它尝试从字符串中解析一个整数&#xff0c;但如果字符串不是一个有效的整数&#xff0c;我们希望返回一个错误状态。 #…...

Java 电商秒杀系统优化实战:实现进阶示例详解与 RabbitMQ 配置

上一篇博客介绍了使用消息队列、异步处理等技术构建 Java 电商秒杀系统的基本思路&#xff0c;本文将进一步优化代码实现&#xff0c;并提供更详细的代码示例和 RabbitMQ 配置&#xff0c;助您构建更健壮、高效的秒杀系统。 一、 代码优化 1. 接口限流 在 SeckillController…...

路径规划 | 基于狼群算法的无人机路径规划(Matlab)

目录 效果一览基本介绍程序设计参考文献 效果一览 基本介绍 路径规划 | 基于狼群算法的无人机路径规划&#xff08;Matlab&#xff09; 狼是一种群居性动物&#xff0c;社会分工明确&#xff0c;通过承担各自的责任与团结协作&#xff0c;共同促进整个狼群的生存与发展。狼群算…...

13-python函数返回值和装包的后续提取数据方法——解包

1.1 参数解包 不定长参数简单来讲就是装包&#xff0c;把多个参数装到一个元组或者装到字典中&#xff0c;就叫做装包 Ctrld可以快速向下复制 传递实参时&#xff0c;也可以在序列类型的参数前添加星号&#xff0c;这样他会自动将序列中的元素依次作为参数传递 注意&#x…...

I. 对线

https://codeforces.com/gym/103186/problem/I 一开始感觉操作挺复杂的 但是写过Chino的数列 - 洛谷 发现可以通过矩阵来实现swap操作&#xff0c;就想能不能用线段树维护矩阵来写 有三排兵线&#xff0c;我们维护区间和&#xff0c;因此初始矩阵就有了 接下来分析每个操作的…...

Topsis法模型(评价类问题)

目录 本文章内容参考&#xff1a; 一. 概念 二. 特点和适用范围 三. 实现步骤 四. 代码实现 本文章内容参考&#xff1a; TOPSIS法模型讲解(附matlab和python代码) 【数学建模快速入门】数模加油站 江北_哔哩哔哩_bilibili 一. 概念 TOPSIS&#xff08;Technique for O…...

HPA 与pod调度

HPA 自动更新工作负载资源&#xff08;例如 Deployment 或者 StatefulSet&#xff09;&#xff0c; 目的是自动扩缩工作负载以满足需求。 绑定到deploy上&#xff0c;控制pod 依托于metrics-server HorizontalPodAutoscaler 水平pod自动扩缩&#xff1a;意味着对增加的负…...

eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)

说明&#xff1a; 想象一下&#xff0c;你正在用eNSP搭建一个虚拟的网络世界&#xff0c;里面有虚拟的路由器、交换机、电脑&#xff08;PC&#xff09;等等。这些设备都在你的电脑里面“运行”&#xff0c;它们之间可以互相通信&#xff0c;就像一个封闭的小王国。 但是&#…...

Vue记事本应用实现教程

文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展&#xff1a;显示创建时间8. 功能扩展&#xff1a;记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...

python/java环境配置

环境变量放一起 python&#xff1a; 1.首先下载Python Python下载地址&#xff1a;Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个&#xff0c;然后自定义&#xff0c;全选 可以把前4个选上 3.环境配置 1&#xff09;搜高级系统设置 2…...

Ascend NPU上适配Step-Audio模型

1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统&#xff0c;支持多语言对话&#xff08;如 中文&#xff0c;英文&#xff0c;日语&#xff09;&#xff0c;语音情感&#xff08;如 开心&#xff0c;悲伤&#xff09;&#x…...

拉力测试cuda pytorch 把 4070显卡拉满

import torch import timedef stress_test_gpu(matrix_size16384, duration300):"""对GPU进行压力测试&#xff0c;通过持续的矩阵乘法来最大化GPU利用率参数:matrix_size: 矩阵维度大小&#xff0c;增大可提高计算复杂度duration: 测试持续时间&#xff08;秒&…...

精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南

精益数据分析&#xff08;97/126&#xff09;&#xff1a;邮件营销与用户参与度的关键指标优化指南 在数字化营销时代&#xff0c;邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天&#xff0c;我们将深入解析邮件打开率、网站可用性、页面参与时…...

Android第十三次面试总结(四大 组件基础)

Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成&#xff0c;用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机&#xff1a; ​onCreate()​​ ​调用时机​&#xff1a;Activity 首次创建时调用。​…...

【电力电子】基于STM32F103C8T6单片机双极性SPWM逆变(硬件篇)

本项目是基于 STM32F103C8T6 微控制器的 SPWM(正弦脉宽调制)电源模块,能够生成可调频率和幅值的正弦波交流电源输出。该项目适用于逆变器、UPS电源、变频器等应用场景。 供电电源 输入电压采集 上图为本设计的电源电路,图中 D1 为二极管, 其目的是防止正负极电源反接, …...

DingDing机器人群消息推送

文章目录 1 新建机器人2 API文档说明3 代码编写 1 新建机器人 点击群设置 下滑到群管理的机器人&#xff0c;点击进入 添加机器人 选择自定义Webhook服务 点击添加 设置安全设置&#xff0c;详见说明文档 成功后&#xff0c;记录Webhook 2 API文档说明 点击设置说明 查看自…...

NPOI操作EXCEL文件 ——CAD C# 二次开发

缺点:dll.版本容易加载错误。CAD加载插件时&#xff0c;没有加载所有类库。插件运行过程中用到某个类库&#xff0c;会从CAD的安装目录找&#xff0c;找不到就报错了。 【方案2】让CAD在加载过程中把类库加载到内存 【方案3】是发现缺少了哪个库&#xff0c;就用插件程序加载进…...