图的存储方式(上)

文章目录

    • 图的相关概念
    • 图的类型
    • 图的常见存储方式
      • 边集数组
      • 邻接矩阵
      • 邻接表

图的相关概念

对于图的相关概念实在太多,请移步,【图论】图的概念和基本术语(顶点、边、度、路径等)

图的类型

按照类型对图进行分类可以分为以下几种:1.无向图和有向图,2.加权图和无权图,3.连通图和非连通图。

图的常见存储方式

图的常见存储方式有边集数组,邻接矩阵,邻接表,十字链表,多重邻接表。这篇文章先讲前面3种。

边集数组

首先是边集数组,顾名思义,这个数组是存储边的结构体数组,每一个边节点包含了起点,终点以及边权三个元素,它的效率不高,实现简单,常用于最小生成树中的Kruskal算法。
下面来看实现方式:

#include
using namespace std;
//边集数组
typedef struct edge {
	//无向图
	int v1;
	int v2;
	int w;//权值
}Edge;
Edge e[105];
int v[105];
int main() {
	int n, m;//n个点,m组关系
	cin >> n >> m;
	for (int i = 1;i <= n;i++) {
		cin >> v[i];
	}
	int x, y, w;
	for (int i = 1;i <= m;i++) {
		cin >> x >> y >> w;
		e[i].v1 = x;
		e[i].v2 = y;
		e[i].w = w;
	}
	//求x的度
	cin >> x;
	int d = 0;
	for (int i = 1;i <= n;i++) {
		if (e[i].v1 == x || e[i].v2 == x) {
			d++;//如果为有向图,e[i].v1 == x----点x的出度,e[i].v2 == x---x的入度
		}
	}
	cout << d;
	return 0;
}

这里需要注意的是求点x的度,我的代码是无向图的例子,对于一个节点x,它要么是一条边的起点,要么是一条边的终点,而不可能同时是起点和终点,因此只要遍历整个边集数组,看他的x或y是不是和节点相同即可。而对于有向图呢,e[i].v1是一条边的起点,如果x==e[i].v1,那么x是这一条边的起点,终点未知,所以x的出度就确定了,同理,入度也确定了。

邻接矩阵

邻接矩阵在《离散数学》中应该是很常见的,比如关系矩阵,关联矩阵等。它则是通过二维数组来存图,思路简单,适用于稠密图,在稀疏图中会很浪费空间。话不多说,直接上代码:

#include
#define INF 10001
using namespace std;
char v[105];
int e[105][105];
int n, m;
int find(char x) {
	for (int i = 1;i <= n;i++) {
		if (v[i] == x) {
			return i;
		}
	}
	return -1;//不存在
}
int main() {
	//带权无向图(如果无权只要权值为0 1 即可)
	cin >> n >> m;
	for (int i = 1;i <= n;i++) {
		cin >> v[i];
	}
	char x, y;
	int w;
	for (int i = 1;i <= n;i++) {
		for (int j = 1;j <= n;j++) {
			if (i == j)e[i][j] = 0;
			else e[i][j] = INF;
		}
	}
	for (int i = 1;i <= m;i++) {
		cin >> x >> y >> w;
		int xi = find(x);
		int  yi= find(y);
		e[xi][yi] = e[yi][xi] = w;//无向图建边相当于两条相反的有向边
	}
	//求x的度
	cin >> x;
	int d = 0;
	int xi = find(x);
	for (int i = 1;i <= n;i++) {
		if (e[xi][i] < INF && xi != i) {
			d++;
		}
	}
	cout << d;
	return 0;
}

这里的存边方式是这样的,以无向带权图举例,比如起点为x,终点为y,边x->y的权值为w,那么e[x][y]=w,因为是无向图,所以e[y][x]=w。如果是有向图呢,那么就只要确定一个方向就好了,如果是无权图呢,就只需要每输入一次x,y,e[x][y]=1,即可(未连接的边为0)。
对于求点x的度,如果是无向图,只要循环遍历以x为起点的边e[x][i]是不是存在权值即可(同时x!=i),如果是有向图,那么e[x][i]就是用来计算x的出度,e[i][x]计算x的入度。

邻接表

邻接表的实现效率较高,在图的遍历上可以减少遍历所用的时间(相比邻接矩阵)
。它的组成包括顶点数组,和终点数组,顶点数组用来存储每一个顶点的数据以及他们连接的第一个终点的地址,终点数组以链表的形式挂在起点之后,每次建新边使用头插法。直接上代码:

#include
#define INF 10001
//带权无向图
using namespace std;
typedef struct Node {
	int adj;
	int w;
	struct Node* next;
}Enode;
typedef struct Vertex {
	char data;//顶点数据
	Enode* first;
}Vertex;
Vertex G[105];
int n, m;
int find(char x) {
	for (int i = 1;i <= n;i++) {
		if (G[i].data == x) {
			return i;
		}
	}
	return -1;
}
int main() {
	cin >> n >> m;
	for (int i = 1;i <= n;i++) {
		cin >> G[i].data;
		G[i].first = NULL;
	}
	char x, y;
	int w;
	for (int i = 1;i <= m;i++) {
		cin >> x >> y >> w;
		int xi = find(x);
		int yi = find(y);
		//无向图 y-->x  x-->y
		Enode* s = new Enode;
		s->adj = yi;
		s->w = w;
		s->next = G[xi].first;//头插
		G[xi].first = s;
		//如果是有向图则只建一边(上)
		Enode* t = new Enode;
		t->adj = xi;
		t->w = w;
		t->next = G[yi].first;
		G[yi].first = s;
		//逆邻接表代码(上)
	}
	//求x的度
	int d = 0;
	cin >> x;
	int xi = find(x);
	Enode* p = G[xi].first;
	while (p) {
	    d++;
		p = p->next;
	}
	cout << d;
	return 0;
}

这里求x的度就只要找到x的下表再找后面挂着的节点个数就行了。
当然,邻接表也可以用c++ stl中的vector容器实现,比如有1000个节点,那么就创建一个vectorv[1000],对于每一对x,y只需要v[x].push_back(y)即可。

你可能感兴趣的:(数据结构,算法,数据结构,图论)