Almacenamiento de gráficos y resumen de operaciones básicas (matriz de adyacencia, lista de adyacencia) e implementación de código C/C++


prefacio

Un gráfico es una estructura de datos relativamente compleja y puede haber múltiples relaciones entre cada nodo.
Por lo tanto, un gráfico puede presentar una variedad de formas extrañas.
Para diferentes formas de gráficos, podemos usar diferentes métodos de almacenamiento para almacenarlos.

Por ejemplo:

  • Para un gráfico con pocas aristas y muchos nodos , necesitamos usar más espacio de almacenamiento para almacenar la información de los vértices.Si no hay arista entre dos vértices, entonces no hay necesidad de gastar espacio de almacenamiento para indicar que no hay arista en este lugar.
  • Para un gráfico con muchas aristas y relativamente pocos vértices , es probable que haya aristas entre cada vértice. Si cada arista se considera por separado, será más engorroso.
  • Para gráficos con más operaciones de inserción y eliminación de bordes , esperamos encontrar la información de este borde en esa posición más rápido, por lo que la estructura de datos con propiedades de acceso aleatorio es más práctica.

Hay muchos más ejemplos como este. Resumamos los dos métodos de almacenamiento más utilizados: matriz de adyacencia y lista de adyacencia .


1. Matriz de adyacencia

1. Concepto

El llamado almacenamiento de matriz de adyacencia se refiere a usar un arreglo unidimensional para almacenar la información de los vértices en el gráfico, usando un arreglo bidimensional para almacenar la información de los bordes en el gráfico (es decir, la relación de adyacencia entre vértices) y almacenar la relación de adyacencia bidimensional entre los vértices. La matriz se denomina matriz de adyacencia.

  • La matriz de adyacencia de un grafo G con n vértices es   n × n \ n×n norte×Para un arreglo bidimensional de n , si los números de vértice son v1, v2, …, vn, entonces para los vértices viy vj, si hay una arista (vi, vj)∈E, entonces A[i][j] = 1, de lo contrario A[i][j] = 0, es decir

A [ i ] [ j ] = { 1 , si ( vi , vj ) o ⟨ vi , vj ⟩ es la arista 0 en E ( G ), si ( vi , vj ) o ⟨ vi , vj ⟩ no es E ( G ) Arista 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 { es el borde en} E(G) \text {} \\ 0, & \text { if}\left(v_{i }, v_{j}\right) \text { o}\left\langle v_{i}, v_{j}\right\rangle \text { no es un borde en }E(G) \text {} \end{ array} \bien.un [ yo ] [ j ]={ 1 ,0 , como ( vyo,vj) o ⟨v _yo,vj es  una arista en E ( G )   como ( vyo,vj) o ⟨v _yo,vj no es    una arista en E ( G )

  • Para un gráfico ponderado , si hay una arista entre los vértices v y v;, el elemento correspondiente en la matriz de adyacencia almacena el peso correspondiente a la arista Si los vértices V y V no están conectados, use   ∞ \ \infty  para representar que no hay arista entre estos dos vértices:

A [ i ] [ j ] = { wij , si ( vi , vj ) o ⟨ vi , vj ⟩ es la arista 0 o ∞ en E ( G ), si ( vi , vj ) o ⟨ vi , vj ⟩ no es E ( G ) lado A[i][j]=\left\{\begin{array}{ll} w_{ij}, & \text { 如}\left(v_{i}, v_{j}\right ) \ text { o}\left\langle v_{i}, v_{j}\right\rangle \text { es una arista en} E(G) \text{} \\ 0 \text { o}\infty, & \ texto {si}\left(v_{i}, v_{j}\right) \text {o}\left\langle v_{i}, v_{j}\right\rangle \text { not} E( G) Bordes en \text{} \end{array}\right.un [ yo ] [ j ]={ wyo,0 o   , como ( vyo,vj) o ⟨v _yo,vj es  una arista en E ( G )   como ( vyo,vj) o ⟨v _yo,vj no es    una arista en E ( G )

2. Ejemplo de imagen

  • Un grafo no dirigido y su matriz de adyacencia se pueden representar en la forma del siguiente grafo:
    inserte la descripción de la imagen aquí
  • Un grafo dirigido y su matriz de adyacencia se pueden representar en la forma del siguiente grafo:
    inserte la descripción de la imagen aquí

3. Implementación del código

La implementación del código de matriz de adyacencia del grafo:

#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;
}

Por supuesto, también puede escribir algunas funciones personalizadas para enriquecer las funciones del gráfico según sus necesidades. Por ejemplo, graph_destroyse utiliza para destruir el gráfico, graph_get_edgepara obtener el peso de la arista y graph_edges_countpara calcular el número de aristas relacionadas con el vértice v...

Aviso

  • En aplicaciones simples , una matriz bidimensional se puede usar directamente como la matriz de adyacencia del gráfico ( se puede omitir la información de vértice, etc. ).
  • Cuando los elementos de la matriz de adyacencia solo indican si existe el borde correspondiente, EdgeType puede tomar el tipo de enumeración con valores 0 y 1.
  • La matriz de adyacencia de un gráfico no dirigido es una matriz simétrica, y la matriz de adyacencia con una gran escala se puede almacenar en compresión .
  • La complejidad espacial de la notación de matriz de adyacencia es   O ( n 2 ) \ O(n ^{2}) O ( n2 ), donde n es el número de vértices del grafo   ∣ V ∣ \ |V| V

Características de la matriz de adyacencia

  1. La matriz de adyacencia de un grafo no dirigido debe ser una matriz simétrica (y única). Por lo tanto, solo los elementos de la matriz triangular superior (o inferior) deben almacenarse cuando se almacena la matriz de adyacencia .
  2. Para un gráfico no dirigido , el   i \ i de la matriz de adyacencia yo línea (o   yo \ yo columna i ) elementos distintos de cero (o distintos de   -0\0 0 elementos) es exactamente el número de vértices   i \ i i的度   TD ( vi ) \ TD(v _{i}) TD ( v _yo)
  3. Para grafos dirigidos , el   i \ i de la matriz de adyacencia Elementos distintos de cero en la fila i (o no   -∞ \ ∞  elementos) es exactamente el número de vértices   i \ i i的出度   OD ( vi ) \ OD(v _{i}) Acerca de D ( vyo) ; yo   \ yo i columna elementos distintos de cero (o no   -∞ \ ∞  elemento) es exactamente el ID en grado   del vértice i ( vi ) \ ID(v _{i}) Yo D ( vyo)
  4. Usando una matriz de adyacencia para almacenar un gráfico, es fácil determinar si dos vértices cualesquiera del gráfico están conectados por una arista. Sin embargo, para determinar cuántos bordes hay en el gráfico , cada elemento debe probarse fila por fila y columna por columna, lo que lleva mucho tiempo .
  5. Los gráficos densos son adecuados para la representación de almacenamiento utilizando una matriz de adyacencia.
  6. Diseño   G\G La matriz de adyacencia de G es   A\A A   A n \ A ^{n} Aelemento similar a n   A n [ i ] [ j ] \ A ^{n}[i][j] An [i][j]es igual al vértice   i\i i al vértice   j\j j tiene longitud   n\n n es el número de caminos. Es suficiente para comprender la conclusión, consulte el libro de texto de matemáticas discretas para conocer el método de prueba.

2. Lista de adyacencias

1. Concepto

Cuando un gráfico es un gráfico disperso, usar el método de matriz de adyacencia obviamente desperdicia mucho espacio de almacenamiento, y el método de tabla de adyacencia del gráfico combina métodos de almacenamiento secuencial y almacenamiento en cadena, lo que reduce en gran medida este desperdicio innecesario.

  • La llamada lista de adyacencia se refiere al gráfico   G \ G    Cada vértice v\ven G v construir unalista enlazada individualmente,   i \ i Los nodos en la lista i enlazada individualmente están unidos al vértice   v \ v La arista de v (para un gráfico dirigido es el vértice   v \ v v es el arco de la cola), estalista unida se llama vértice   v \ v La lista de aristas de v (para gráficos dirigidos, se llama lista de aristas).
  • El puntero principal de la tabla de vértices y la información de datos de los vértices se almacenan secuencialmente (llamada tabla de vértices) , por lo que hay dos tipos de nodos en la lista de adyacencia: nodos de tabla de vértices y nodos de tabla de vértices.

2. Ejemplo de imagen

  • La estructura de datos del nodo de la tabla de vértices se muestra en la siguiente figura:
    inserte la descripción de la imagen aquí

  • La estructura de datos del nodo de la tabla de bordes se muestra en la siguiente figura:
    inserte la descripción de la imagen aquí
    el nodo de la tabla de vértices se compone del campo de vértices (datos) y el puntero (primer arco) que apunta al primer borde adyacente , y el nodo de la tabla de bordes (tabla de adyacencia) se compone del campo de punto adyacente (adjvex) y un campo de puntero .

  • Un gráfico no dirigido y su lista de adyacencia se pueden representar en la forma del siguiente gráfico:
    inserte la descripción de la imagen aquí

  • Un gráfico dirigido y su lista de adyacencia se pueden representar en la forma del siguiente gráfico:
    inserte la descripción de la imagen aquí

3. Implementación del código

#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;
}

Las características de la lista de adyacencia

  1. Si   G\G G esun gráfico no dirigido,el espacio de almacenamiento requerido es   O ( ∣ V ∣ + 2 ∣ E ∣ ) \ O(|V|+ 2|E|) O ( V +2∣ mi ) ; si   sol \ sol G esun gráfico dirigido,el espacio de almacenamiento requerido es   O ( ∣ V ∣ + ∣ E ∣ ) \ O(|V|+ |E|) O ( V +mi ) . Múltiplos del anterior   2 \ 2 2 se debe al gráfico no dirigido, cada borde aparece dos veces en la lista de adyacencia.
  2. Para gráficos dispersos , la representación de la lista de adyacencia ahorrará mucho espacio de almacenamiento.
  3. En la lista de adyacencia, dado un vértice, es fácil encontrar todas sus aristas adyacentes , porque solo se necesita leer su lista de adyacencia . En la matriz de adyacencia, la misma operación necesita escanear una fila, y el tiempo empleado es   O ( n ) \ O(n) O ( n )
  4. Sin embargo, si desea determinar si hay un borde entre dos vértices dados, puede encontrarlo inmediatamente en la matriz de adyacencia , pero en la lista de adyacencia, necesita encontrar otro nodo en la tabla de bordes correspondiente al nodo correspondiente, que es más eficiente Bajo.
  5. En la representación de lista de adyacencia de un grafo dirigido , para encontrar el grado de salida de un vértice dado solo se necesita calcular el número de nodos en su lista de adyacencia ; pero para encontrar el grado de entrada de su vértice se necesita recorrer todas las listas de adyacencia . Por lo tanto, algunas personas usan el método de almacenamiento de la lista de adyacencia inversa para acelerar el cálculo del grado de entrada de un vértice dado. Por supuesto, esto es similar al método de almacenamiento de la lista de adyacencia.
  6. La representación de la lista de adyacencia del grafo no es única , porque en la lista de enlace único correspondiente a cada vértice, el orden de enlace de cada nodo de borde puede ser arbitrario, depende del algoritmo para establecer la lista de adyacencia y el orden de entrada de los bordes .

Supongo que te gusta

Origin blog.csdn.net/z135733/article/details/130173090
Recomendado
Clasificación