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

news2024/11/15 13:54:25

二、存储结构

文章目录

  • 二、存储结构
    • ❗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是int
    int 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");

	//图的初始化init
	for (i=0; i<G->vexnum; i++){
		for (j=0; j<G->vexnum; j++){
			if (i == j)
				G->Edge[i][j] = 0;      //结点自身
			else
				G->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");

	//图的初始化init
	for (i=0; i<G->vexnum; i++){
		for (j=0; j<G->vexnum; j++){
			if (i == j)
				G->Edge[i][j] = 0;      //结点自身
			else
				G->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", "∞");
			else
				printf("%5d", G.Edge[i][j]);
		}
		printf("\n");
	}	
}


// ------------------------- DFS 深度优先遍历------------------------
int visited[numVertexes]={0};
// 注意:是从0下标开始
void DFS(MGraph G, int x){
    // 访问顶点x
	printf("%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是int
    int 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");

	//图的初始化init
	for (i=0; i<G->vexnum; i++){
		for (j=0; j<G->vexnum; j++){
			if (i == j)
				G->Edge[i][j] = 0;      //结点自身
			else
				G->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");

	//图的初始化init
	for (i=0; i<G->vexnum; i++){
		for (j=0; j<G->vexnum; j++){
			if (i == j)
				G->Edge[i][j] = 0;      //结点自身
			else
				G->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", "∞");
			else
				printf("%5d", G.Edge[i][j]);
		}
		printf("\n");
	}	
}


// ------------------------- DFS 深度优先遍历------------------------
int visited[numVertexes]={0};
// 注意:是从0下标开始
void DFS(MGraph G, int x){
    // 访问顶点x
	printf("%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是int
    int 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. 图的邻接表表示并不唯一,因为在每个顶点对应的单链表中,各边结点的链接次序可以是任意的,它取决于建立邻接表的算法及边的输入次序。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Transformer在量化投资中的应用

开篇 深度学习的发展为我们创建下一代时间序列预测模型提供了强大的工具。深度人工神经网络&#xff0c;作为一种完全以数据驱动的方式学习时间动态的方法&#xff0c;特别适合寻找输入和输出之间复杂的非线性关系的挑战。最初&#xff0c;循环神经网络及其扩展的LSTM网络被设…

云计算实训26——部署LVS负载均衡项目

LVS LVS是linux virtural server的简称——免费、开源、四层负载均衡 工作原理&#xff1a; 通过linux达到负载均衡好和linux操作系统实现高性能高可用的linux服务集群&#xff0c;具有良好的可靠性、可扩展性、可操作性、可扩展性、从而实现以低廉的成本实现最优的性能。LV…

VisionPro二次开发学习笔记10-使用 PMAlign和Fixture固定Blob工具检测孔

使用 PMAlign和Fixture固定Blob工具检测孔 这个示例演示了如何使用 PMAlign 工具和 Fixture 工具来夹持一个 Blob 工具。示例代码将检测支架右上角孔的存在。当点击运行按钮时&#xff0c;将读取新图像。PMAlign 工具运行并生成一个 POSE 作为输出。POSE 是一个六自由度的变换…

Mybatis(1)

一. Mybatis概述 原本是Apache的一个开源项目叫iBatis,2010年迁移到Google Code旗下,改名为Mybatis Mybatis是一款优秀的持久层框架,是对JDBC的封装Mybatis几乎避免了JDBC所有的手动设置参数以及手动获取结果的操作Mybatis可以使用注解或XML文件来配置和映射,将数据库中的数据…

springboot发送邮箱功能的安全与加密配置?

springboot发送邮箱设置的步骤&#xff1f;springboot发信优势&#xff1f; 为了确保邮件发送过程的安全性和隐私保护&#xff0c;我们需要对 SpringBoot发送邮箱功能进行适当的安全与加密配置。AokSend将详细探讨如何在 SpringBoot项目中实现这些配置&#xff0c;以保障邮件传…

大模型面试题集锦:揭秘阿里24k Star项目背后的争议,非常详细收藏我这一篇就够了

今天分享两个 Github 上开源的不错的项目&#xff0c;以及阿里空 Github 项目的趣闻。 一、开源大模型面试题 大致看了以下&#xff0c;面试题分门别类&#xff0c;还是挺全的。包含 LLM 基础&#xff0c;分布式训练&#xff0c;推理&#xff0c;强化学习等。 二、Awesome Co…

记录|Git工具——下载GitHub项目

目录 前言一、Step 1. 下载Git二、Step2. 用Git Bash 下载到本地更新时间 前言 参考文章&#xff1a; 1、如何使用Git将Github项目拉到本地 2、git 安装、创建仓库、上传项目、克隆下载、常用命令 – 一篇文章总结&#xff08;适用github / gitee&#xff09; 3、Git的使用【入…

2024网页设计教程:最新趋势与实用技巧

网页是每个人访问信息的载体&#xff0c;用户点击的按钮、浏览的导航栏和网页界面的视觉性能都与网页制作有关。良好的网页设计代表了友好的使用体验。个人网页、商业网页和社交媒体平台都应该吸引人们的注意&#xff0c;并提供令人印象深刻的客户体验。本文针对网页制作新手&a…

CentOS7.6单机部署RabbitMQ消息队列——实施方案

1、前期环境准备 1.准备一台主机 IP地址主机名角色内存大小192.168.200.10 rabbitmq 消息队列 2G 2. 设置主机名 hostnamectl set-hostname 主机名suexit Ctrlr 3. 设置IP地址然后重启网卡 vim /etc/sysconfig/network-scripts/ifcfg-ens33systemctl restart network 4.…

【一图学技术】8.图解Git工作流程使用 git 解决团队协作中的冲突问题 git 进行版本控制的最佳实践Git 的高级功能

图解Git的工作流程 一、Git常见命令概述 以下是 Git 常用命令的详细作用、使用例子和使用场景&#xff1a; git add 作用&#xff1a;将工作目录中的更改添加到暂存区。暂存区是一个准备下一次提交的区域&#xff0c;它记录了你想要提交的更改。使用例子&#xff1a;git add …

【机器人】关于钉钉机器人如何进行自定义开发问答【详细清晰】

目标&#xff1a;当用户输入问题并钉钉机器人&#xff0c;钉钉机器人进行相应的回答&#xff0c;达到一种交互问答的效果 开发文档参考&#xff1a;https://open.dingtalk.com/document/orgapp/robot-overview 首先进行登录企业&#xff0c;后面如果没有进行登录&#xff0c;会…

html编写贪吃蛇页面小游戏(可以玩)

<!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>贪吃蛇小游戏</title><style>body {…

Hadoop,ActiveMQ,RabbitMQ,Springboot Actuator未授权访问漏洞(附带修复方法)

十.Hadoop Hadoop是⼀个由Apache基⾦会所开发的分布式系统基础架构&#xff0c;由于服务器直接在开放了Hadoop 机器 HDFS 的 50070 web 端⼝及部分默认服务端⼝&#xff0c;⿊客可以通过命令⾏操作多个⽬录下的数据&#xff0c;如进⾏删除&#xff0c;下载&#xff0c;⽬录浏览…

LSTM--概念、作用、原理、优缺点以及简单的示例代码

LSTM的概念 LSTM&#xff08;Long Short-Term Memory&#xff09;是一种特殊的递归神经网络&#xff08;RNN&#xff09;&#xff0c;最早由Sepp Hochreiter和Jrgen Schmidhuber在1997年提出。LSTM设计的主要目的是解决标准RNN中的长时依赖问题。RNN在处理长序列时&#xff0c…

机房防静电地板和架空网络地板有哪些区别

有好些人把机房防静电地板和网络地板混为一谈&#xff0c;觉的两个就是一样的东西&#xff0c;其实呢&#xff0c;虽说都是高架活动地板&#xff0c;但它们之间的区别大着呢&#xff01;下面和宜缘机房一起来看看防静电地板和网络地板两者都有哪些区别&#xff1f; 1、使用场景…

安全基础学习-RC4加密算法

这里仅仅记录一些基础的概念。后期有需求进一步扩展。 RC4 是一种对称流加密算法&#xff0c;由罗恩里维斯特&#xff08;Ron Rivest&#xff09;于1987年设计。RC4 的设计目的是提供一种简单且高效的加密方法。尽管 RC4 曾经广泛使用&#xff0c;但它的安全性在现代已受到质疑…

精通C++ STL(七):stack和queue的介绍及使用

目录 stack stack的定义方式 stack的使用 queue queue的定义方式 queue的使用 stack stack 是一种容器适配器&#xff0c;专门用于处理后进先出的操作场景。它仅允许在容器的一端进行元素的插入和提取操作。 stack的定义方式 方式一&#xff1a; 使用默认适配器定义栈。 sta…

Linux 进程调度(三)之进程的优先级

目录 一、概述二、进程的优先级1、基础概念2、优先级的意义3、查看优先级4、PRI 和 NI5、修改优先级6、控制进程的优先级的系统调用7、调整优先级的限制 一、概述 在 Linux 中&#xff0c;每个进程都有一个优先级。优先级决定了进程在系统资源分配中的先后顺序。Linux 中的进程…

gin框架中的中断请求c.Abort()方法的作用和使用细节说明

Abort()方法的作用 在gin框架中&#xff0c;如果我们希望中断后续的挂起的请求处理链&#xff08;HandlersChain&#xff09;的请求&#xff0c; 可以使用gin.Context中的这个Abort()方法。请注意&#xff0c;这不会停止当前处理程序。假设您有一个授权中间件&#xff0c;用于…

Vue技术栈-Vue 3 项目组件入门:单文件组件 (SFC)

目录 前言 1.简介 2.安装 Vite 和 Vue 3 3.什么是.vue文件? vue 文件解析 4.什么是VUE的组件? 5.工程化vue项目如何组织这些组件? 6.Vue3关于样式( CSS )的导入方式 7.结语 前言 本篇是在上一篇Vue技术栈-Vite最新版创建一个Vue3项目的基础上的后续,先对Vite这一构…