Graph storage and basic operation summary (adjacency matrix, adjacency list) and C/C++ code implementation


foreword

A graph is a relatively complex data structure, and there can be multiple relationships between each node.
Therefore, a graph can present a variety of strange forms.
For different forms of graphs, we can use different storage methods to store them.

For example:

  • For graphs with few edges and many nodes , we need to use more storage space to store vertex information. If there is no edge between two vertices, then there is no need to spend storage space to indicate that there is no edge at this place.
  • For a graph with many edges and relatively few vertices , there are likely to be edges between each vertex. If each edge is considered separately, it will be more cumbersome.
  • For graphs with more operations of inserting and deleting edges , we hope to find the information of this edge at that position faster, so the data structure with random access properties is more practical.

There are many more examples like this. Let’s summarize the two most commonly used storage methods— adjacency matrix and adjacency list .


1. Adjacency matrix

1. Concept

The so-called adjacency matrix storage refers to using a one-dimensional array to store the information of the vertices in the graph, using a two-dimensional array to store the information of the edges in the graph (that is, the adjacency relationship between vertices), and storing the two-dimensional adjacency relationship between vertices. The array is called an adjacency matrix.

  • The adjacency matrix of a graph G with n vertices is   n × n \ n×n n×For a two-dimensional array of n , if the vertex numbers are v1, v2, …, vn, then for vertices viand vj, if there is an edge (vi, vj)∈E, then A[i] [j] = 1, otherwise A[i][j] = 0, ie

A [ i ] [ j ] = { 1 , if ( vi , vj ) or ⟨ vi , vj ⟩ is edge 0 in E ( G ), if ( vi , vj ) or ⟨ vi , vj ⟩ is not E ( G ) Edge A[i][j]=\left\{\begin{array}{ll} 1, & \text { if}\left(v_{i}, v_{j}\right) \text { or }\left\langle v_{i}, v_{j}\right\rangle \text { is the edge in} E(G) \text {} \\ 0, & \text { if}\left(v_{i }, v_{j}\right) \text { or}\left\langle v_{i}, v_{j}\right\rangle \text { not an edge in }E(G) \text {} \end{ array}\right.A[i][j]={ 1,0, like (vi,vj) or vi,vj is  an edge in E ( G )   like (vi,vj) or vi,vj is not    an edge in E ( G )

  • For a weighted graph , if there is an edge between the vertices v and v;, the corresponding item in the adjacency matrix stores the weight corresponding to the edge . If the vertices V and V are not connected, use   ∞ \ \infty  to represent that there is no edge between these two vertices:

A [ i ] [ j ] = { wij , if ( vi , vj ) or ⟨ vi , vj ⟩ is edge 0 or ∞ in E ( G ), if ( vi , vj ) or ⟨ vi , vj ⟩ is not E ( G ) side A[i][j]=\left\{\begin{array}{ll} w_{ij}, & \text { 如}\left(v_{i}, v_{j}\right ) \text { or}\left\langle v_{i}, v_{j}\right\rangle \text { is an edge in} E(G) \text{} \\ 0 \text { or}\infty, & \text { if}\left(v_{i}, v_{j}\right) \text { or}\left\langle v_{i}, v_{j}\right\rangle \text { not} E( G) Edges in \text{} \end{array}\right.A[i][j]={ wij,0 or   , like (vi,vj) or vi,vj is  an edge in E ( G )   like (vi,vj) or vi,vj is not    an edge in E ( G )

2. Image example

  • An undirected graph and its adjacency matrix can be represented in the form of the following graph:
    insert image description here
  • A directed graph and its adjacency matrix can be represented in the form of the following graph:
    insert image description here

3. Code implementation

The adjacency matrix code implementation of the graph:

#include<iostream>
#include<string>
#include<assert.h>
using namespace std;

#define MaxVertexNum 100		//顶点数目的最大值
#define INF 0xfffffff
//顶点的数据类型
typedef string VertexType;	
//带权图中边上权值的数据类型	
typedef int EdgeType;
//定义图的类型 
typedef enum GraphType{
    
    
	UDG, DG, UDN, DN
}GraphType;	
//邻接矩阵数据结构定义	
typedef struct{
    
    
	VertexType Vex[MaxVertexNum];				//顶点表
	EdgeType Edge[MaxVertexNum][MaxVertexNum];	//边表
	int vexnum, arcnum;							//图的当前顶点数和弧数
	GraphType type;								//标记图的类型 
}MGraph, *graph;

void graph_create(MGraph &g);				//图的定义 
int vertex_index(MGraph g, string v);		//返回顶点v的坐标
void graph_add_vertex(MGraph &g, string v);	//添加顶点
bool graph_has_vertex(MGraph &g, string v);	//检查是否存在顶点v
void graph_add_edge(MGraph &g, string v1, string v2);		//添加边 
bool graph_has_edge(MGraph g, string v1, string v2);		//检查是否存在v1->v2的边 
void show_graph(MGraph g);					//打印图 


void graph_create(MGraph &g){
    
    	
	string str;
	cout << "请输入要定义的图的类型:" << endl << "UDG(无向图)  DG(有向图)  UDN(无向网)  DN(有向网)" << endl; 
	cin >> str;
	//初始化邻接矩阵 
	for(int i = 0; i < g.vexnum; i++){
    
    
		for(int j = 0; j < g.vexnum; j++){
    
    
			if(i != j){
    
    
				if(str == "UDN" || str == "DN")
					g.Edge[i][j] = INF;
				else g.Edge[i][j] = 0;
			}
			else g.Edge[i][j] = 0;
		}
	}
	if(str == "UDG") g.type = UDG;		//构建无向图
	else if(str == "DG")  g.type = DG;	//构建有向图
	else if(str == "UDN") g.type = UDN;	//构建无向网
	else if(str == "DN")  g.type = DN;	//构建有向网	
}

void graph_add_vertex(MGraph &g, string v){
    
    
	if(!graph_has_vertex(g, v)){
    
    
		assert(g.vexnum <= MaxVertexNum);
		g.Vex[g.vexnum++] = v;
	}
}
bool graph_has_vertex(MGraph &g, string v){
    
    
	for(int i = 0; i < g.vexnum; i++)
		if(g.Vex[i] == v) return true;
	return false;
}

void graph_add_edge(MGraph &g, string v1, string v2){
    
    
	if(!graph_has_edge(g, v1, v2)){
    
    
		int start = vertex_index(g, v1);
		int end = vertex_index(g, v2);
		if(g.type == UDG){
    
    
			g.Edge[start][end] = 1;
			g.Edge[end][start] = 1;
		}else if(g.type == DG){
    
    
			g.Edge[start][end] = 1;
		}else if(g.type == UDN){
    
    
			cout << "请输入边的权值:";
			cin >> g.Edge[start][end];
			g.Edge[end][start] = g.Edge[start][end]; 
		}else if(g.type == DN){
    
    
			cout << "请输入边的权值:";
			cin >> g.Edge[start][end];
		}
	}
}

bool graph_has_edge(MGraph g, string v1, string v2){
    
    
	int start = vertex_index(g, v1);
	int end = vertex_index(g, v2);
	assert(start != -1 && end != -1);
	if(g.type == UDG || g.type == UDN){
    
    
		//如果是无向图或无向网 
		if(g.Edge[start][end] != 0 && g.Edge[start][end] != INF) return true;
		if(g.Edge[end][start] != 0 && g.Edge[end][start] != INF) return true;	
	}else if(g.type == DG || g.type == DN){
    
    
		//如果是有向图或有向网 
		if(g.Edge[start][end] != 0 && g.Edge[start][end] != INF) return true;
	}
	return false;
}

int vertex_index(MGraph g, string v){
    
    
	for(int i = 0; i < g.vexnum; i++){
    
    
		if(g.Vex[i] == v) return i;
	}
	return -1;
}

void show_graph(MGraph g) {
    
    
	cout << "图的邻接矩阵如下所示:" << endl;
    for(int i = 0; i < g.vexnum; i++){
    
    
        //cout << g.Vex[i] << " ";
        for(int j = 0; j < g.vexnum; j++){
    
    
            if(g.Edge[i][j] == INF)
                cout << "∞" << " ";
            else
            	cout << g.Edge[i][j] << " ";
        }
        cout << endl;
    }
}

void test(MGraph &g){
    
    
	int vexNum = 0, edgeNum = 0;
	string str, str1, str2;
	
	cout << "请输入图的顶点的数量:" << endl;
	cin >> vexNum;
	for(int i = 0; i < vexNum; i++){
    
    
		cout << "输入顶点" << i+1 << "的信息:"; 
		cin >> str;
		graph_add_vertex(g, str);
	}
	
	cout << "请输入图的边的数量:" << endl;
	cin >> edgeNum;
	for(int i = 0; i < edgeNum; i++){
    
    
		cout << "输入第" << i+1 << "条边的首尾顶点:";
		cin >> str1 >> str2;
		graph_add_edge(g, str1, str2);
	}
}

int main(){
    
    
	MGraph g;
	graph_create(g);
	test(g); 
	show_graph(g); 
	
	return 0;
}

Of course, you can also write some personalized functions to enrich the functions of the graph according to your needs. For example, graph_destroyit is used to destroy the graph, graph_get_edgeto obtain the weight of the edge, and graph_edges_countto calculate the number of edges related to the vertex v...

Notice

  • In simple applications , a two-dimensional array can be directly used as the adjacency matrix of the graph ( vertex information, etc. can be omitted ).
  • When the elements of the adjacency matrix only indicate whether the corresponding edge exists, EdgeType can take the enumeration type with values ​​0 and 1.
  • The adjacency matrix of an undirected graph is a symmetric matrix, and the adjacency matrix with a large scale can be stored in compression .
  • The space complexity of adjacency matrix notation is   O ( n 2 ) \ O(n ^{2}) O ( n2 ), where n is the number of vertices of the graph   ∣ V ∣ \ |V| V

Features of adjacency matrix

  1. The adjacency matrix of an undirected graph must be a symmetric matrix (and unique). Therefore, only the elements of the upper (or lower) triangular matrix need to be stored when actually storing the adjacency matrix .
  2. For an undirected graph , the   i \ i of the adjacency matrix i line (or   i \ i column i ) non-zero elements (or non   -0\0 0 elements) is exactly the number of vertices   i \ i i的度   T D ( v i ) \ TD(v _{i})  TD(vi)
  3. For directed graphs , the   i \ i of the adjacency matrix Non-zero elements in row i (or non   -∞ \ ∞  elements) is exactly the number of vertices   i \ i i的出度   OD ( vi ) \ OD(v _{i}) About D ( vi) ; i   \ i i column non-zero elements (or non   -∞ \ ∞  element) is exactly the in-degree   ID of vertex i ( vi ) \ ID(v _{i}) ID(vi)
  4. Using an adjacency matrix to store a graph, it is easy to determine whether any two vertices in the graph are connected by an edge. However, to determine how many edges there are in the graph , each element must be tested row by row and column by column, which takes a lot of time .
  5. Dense graphs are suitable for storage representation using an adjacency matrix.
  6. Design   G \ G The adjacency matrix of G is   A\A A   A n \ A ^{n}  An -like element   A n [ i ] [ j ] \ A ^{n}[i][j] An [i][j]is equal to the vertex   i\i i to vertex   j\j j has length   n\n n is the number of paths. It is enough to understand the conclusion, please refer to the discrete mathematics textbook for the proof method.

2. Adjacency list

1. Concept

When a graph is a sparse graph, using the adjacency matrix method obviously wastes a lot of storage space, and the adjacency table method of the graph combines sequential storage and chain storage methods, which greatly reduces this unnecessary waste.

  • The so-called adjacency list refers to the graph   G \ G    Every vertex v\vin G v build asingly linked list,   i \ i The nodes in the i singly linked list are attached to the vertex   v \ v The edge of v (for a directed graph is vertex   v \ v v is the arc of the tail), thissingly linked list is called the vertex   v \ v The edge list of v (for directed graphs, it is called the out edge list).
  • The head pointer of the edge table and the data information of the vertices are stored sequentially (called the vertex table) , so there are two kinds of nodes in the adjacency list: vertex table nodes and edge table nodes.

2. Image example

  • The data structure of the vertex table node is shown in the following figure:
    insert image description here

  • The data structure of the edge table node is shown in the figure below:
    insert image description here
    the vertex table node is composed of the vertex field (data) and the pointer (firstarc) pointing to the first adjacent edge , and the edge table node (adjacency table) is composed of the adjacent point field (adjvex ) and a pointer field (nextarc) pointing to the next adjacent edge .

  • An undirected graph and its adjacency list can be represented in the form of the following graph:
    insert image description here

  • A directed graph and its adjacency list can be represented in the form of the following graph:
    insert image description here

3. Code implementation

#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>
#include<string.h>
//#include<math.h>

#define string char*
#define VertexType string
#define MAXSIZE 100 
#define REALLOCSIZE 50
#define INF 0xfffffff

//边表结点
typedef struct ArcNode{
    
    
	int adjvex;		//某条边指向的那个顶点的位置
	ArcNode * next;	//指向下一条弧的指针 
	weight w;		//权值
}ArcNode; 
//顶点表结点
typedef struct VNode{
    
    
	VertexType data;	//顶点信息
	ArcNode * first;	//指向第一条依附该顶点的弧的指针
}VNode;
typedef struct GraphRepr{
    
    
	VNode * node;		//邻接表
	int vexnum, arcnum;	//图的顶点数和弧数 
}Graph, *graph; 

graph graph_create(void) {
    
    
	//初始化一个图的指针 
	graph g = (graph)malloc(sizeof(Graph));
	if(g){
    
    
		//初始化邻接表 
		g->node = (VNode*)malloc(MAXSIZE*sizeof(VNode));
		if(!g->node) {
    
    
			printf("error\n"); 
			return NULL;
		}
		g->arcnum = g->vexnum = 0;
		return g;
	}
	return NULL;
}

void graph_destroy(graph g) {
    
    
    ArcNode *pre, *p;	//定义临时指针 
    char * temp;
	for(int i = 0; i < g->vexnum;i++){
    
    
		pre = g->node[i].first;	//指向边表
		temp = g->node[i].data;
		free(temp);
		//等价于链表的销毁
		if(pre != NULL)	{
    
    
			p = pre->next;
			while(p != NULL) {
    
    
				free(pre);
				pre = p;
				p = pre->next;
			}
			free(pre);
		}
	}
	free(g);
    return;
}

//判断字符串是否相等 
bool is_equal(string s1, string s2){
    
    
	//首先判断长度 
	int len_s1 = strlen(s1);
	int len_s2 = strlen(s2);	
	if(len_s1 != len_s2) return false;
	//长度相等后,判断每一个位置的字符 
	for(int i = 0; i < len_s1; i++)
		if(s1[i] != s2[i]) return false;
	return true;
}

void graph_add_vertex(graph g, string v) {
    
    	
	if(!graph_has_vertex(g, v)){
    
    
		int vlen = strlen(v);
		//判断是否超出邻接表的大小限制 
		if(g->vexnum+1 > MAXSIZE){
    
    
			//重新申请一片空间 
			VNode * temp = (VNode*)malloc((g->vexnum+REALLOCSIZE)*sizeof(VNode));
			//将原邻接表的信息复制到新的内存空间 
			for(int i = 0; i < g->vexnum; i++){
    
    
				temp[i].data = g->node[i].data;
				temp[i].first = g->node[i].first;
			} 
			g->node = temp;	//新的指针赋给邻接表 
		}
		g->node[g->vexnum].data = (char*)malloc(sizeof(char)*vlen+1);
//		printf("%p\t", strcpy(g->node[g->vexnum].data, v));
//		printf("%p\t", g->node[g->vexnum].data);
//		printf("%p\n", v);
//		int i;
//		for(i = 0; i < vlen; i++)
//			g->node[g->vexnum].data[i] = v[i];
//		v[i] = '\0'; 
		g->node[g->vexnum].first = NULL;		//初始化顶点的依附表结点为空 
		g->vexnum++;
	}	
    return;
}

bool graph_has_vertex(graph g, string v) {
    
    
    for(int i = 0; i < g->vexnum; i++)
		if(is_equal(g->node[i].data, v))	//如果能够找到一个顶点的信息为v 
			return true;
	return false;
}

size_t graph_vertices_count(graph g) {
    
    
    return g->vexnum;
}

int get_index(graph g, string v){
    
    
	for(int i = 0; i < g->vexnum; i++)
		if(is_equal(g->node[i].data, v)) return i+1;	//如果能找到这个结点,返回结点位置
	return -1;	//否则返回-1 
}

void graph_add_edge(graph g, string v1, string v2, weight w){
    
        
	//判断是否存在这两个顶点,如果不存在,添加这些顶点 
	if(!graph_has_vertex(g, v1)) graph_add_vertex(g, v1);
	if(!graph_has_vertex(g, v2)) graph_add_vertex(g, v2); 
	int start = get_index(g, v1);
	int end = get_index(g, v2); 
	//判断是否存在这条边 
	if(!graph_has_edge(g, v1, v2)){
    
    	
		//初始化一个边表结点 
		ArcNode * Next = (ArcNode*)malloc(sizeof(ArcNode));
		Next->adjvex = end-1;
		Next->next = NULL;
		Next->w = w;
		//如果start依附的边为空	
		if(g->node[start-1].first == NULL) g->node[start-1].first = Next;
		else{
    
    
			ArcNode * temp = g->node[start-1].first;//临时表结点
			while(temp->next) temp = temp->next;	//找到表结点中start-1这个结点的链表的最后一个顶点
			temp->next = Next;						//在该链表的尾部插入这个边表结点 
		}	
		g->arcnum++;	//边的数量++	
	}
    return;
}

bool graph_has_edge(graph g, string v1, string v2) {
    
    
    int start = get_index(g, v1);
	int end = get_index(g, v2);
	//如果边表为空,则不存在边 
	if(g->node[start-1].first == NULL) return false;
	
	ArcNode * temp = g->node[start-1].first;	//临时表结点
	while(temp) {
    
    
		if(temp->adjvex == end-1) return true;	//如果存在一条v1指向v2的边 
		temp = temp->next;						//指针后移 
	}	
    return false;
}

weight graph_get_edge(graph g, string v1, string v2) {
    
    
    double w;
    //如果不存在这条边,返回0 
    if(!graph_has_edge(g, v1, v2)) return 0.0;
    int start = get_index(g, v1);
	int end = get_index(g, v2);
	
	ArcNode * temp = g->node[start-1].first;
	while(temp){
    
    
		//找到v1指向v2的边,并返回weight 
		if(temp->adjvex == end-1) return temp->w;
		temp = temp->next;
	} 
	return 0.0;
}

void graph_show(graph g, FILE *output) {
    
    
	//先打印每一个顶点信息 
	for(int i = 0; i < g->vexnum; i++){
    
    
		fprintf(output, "%s\n", g->node[i].data);
//		printf("%s\n", g->node[i].data);
	}
	//然后打印每一条边 
    for(int i = 0; i < g->vexnum; i++){
    
        	
        ArcNode * Next = g->node[i].first;
        while (Next) {
    
    
        	fprintf(output, "%s %s %10.2lf\n", g->node[i].data, g->node[Next->adjvex].data, Next->w);
//        	printf("%s %s %10.2lf\n", g->node[i].data, g->node[Next->adjvex].data, Next->w);
            Next = Next->next;
        }        
    }
    return;
}

The characteristics of adjacency list

  1. If   G\G G isan undirected graph,the required storage space is   O ( ∣ V ∣ + 2 ∣ E ∣ ) \ O(|V|+ 2|E|) O(V+2∣ E ) ; if   G \ G G isa directed graph,the required storage space is   O ( ∣ V ∣ + ∣ E ∣ ) \ O(|V|+ |E|) O(V+E ) . Multiples of the former   2 \ 2 2 is due to the undirected graph, each edge appears twice in the adjacency list.
  2. For sparse graphs , adjacency list representation will greatly save storage space.
  3. In the adjacency list, given a vertex, it is easy to find all its adjacent edges , because only its adjacency list needs to be read . In the adjacency matrix, the same operation needs to scan a row, and the time spent is   O ( n ) \ O(n) O ( n )
  4. However, if you want to determine whether there is an edge between two given vertices, you can find it immediately in the adjacency matrix , but in the adjacency list, you need to find another node in the edge table corresponding to the corresponding node, which is more efficient. Low.
  5. In the adjacency list representation of a directed graph , to find the out-degree of a given vertex only needs to calculate the number of nodes in its adjacency list ; but to find the in-degree of its vertex needs to traverse all the adjacency lists. Therefore, some people use the storage method of inverse adjacency list to speed up the calculation of the in-degree of a given vertex. Of course, this is actually similar to the storage method of the adjacency list.
  6. The adjacency list representation of the graph is not unique , because in the singly linked list corresponding to each vertex, the link order of each edge node can be arbitrary, it depends on the algorithm of establishing the adjacency list and the input order of the edges .

Guess you like

Origin blog.csdn.net/z135733/article/details/130173090