图——一种多对多的数据结构

1.图的概念

1.1什么是图

图是一种描述多对多数据关系的数据结构,线性表和树都可以看作是图的一种特殊情况。

图中包含以下元素:
1.一组定点:通常用V(Vertex)表示顶点的集合。
2.一组边:通常用E(Edge)表示边的集合,便是顶点对(v,w)∈E,其中v,w∈V。有向边<v,w>表示从v指向w的边(单行线)。不考虑重边和自回路。

1.2图的抽象数据类型定义

数据名称:图(Graph)
数据对象集:G(V,E)由一个非空的有限定点集合V,和一个有限边集合E组成。
操作集:
·Graph Create():初始化并返回一个空图。
·Graph InsertVertex(Graph G,Vertex v):将顶点v插入。
·Graph InsertEdge(Graph G,Edge e):将边e插入。
·void DFS(Graph G,Vertex v):从顶点v出发,深度优先遍历图G。
·void BFS(Graph G,Vertex v):从顶点v出发,宽度优先遍历图G。
·void ShortesPath(Graph G,Vertex v,int Dist[]):计算图G中的顶点v到任意其他顶点的最短距离。
·void MST(Graph G):计算图G的最小生成树。
除此之外,图还有许多其他的操作,在此不逐一罗列。

2.图的表示及实现

图有两种最基本表示方法,分别是邻接矩阵表示法和邻接表表示法。除这两种表示方法外,图还有其他的表示方法,但在此不做介绍。

2.1图的邻接矩阵表示

可以用矩阵一个N行N列的矩阵G[N][N],来表示一个图,其中N为图中的顶点数,通常用从0到N-1的常数来对N个顶点进行编号。若 < v i , v j > <v_i,v_j> <vi,vj>是G中的边则 G [ i ] [ j ] = 1 G[i][j]=1 G[i][j]=1,否则 G [ i ] [ j ] = 0 G[i][j]=0 G[i][j]=0

邻接矩阵对于一个无向图来说,显然一半存储空间是被浪费掉的(因为 < 1 , 2 > = < 2 , 1 > <1,2>=<2,1> <1,2>=<2,1>。为了节省空间,可以只存一半的数据。用一个长度为N(N+1)/2的数组A来存储 G 00 , G 10 , G 11...... , G ( n − 1 ) ( n − 1 ) {G00,G10,G11......,G(n-1)(n-1)} G00,G10,G11......,G(n1)(n1)。则元素Gij在A中对应的下标就是 ( i ∗ ( i + 1 ) / 2 + j ) (i*(i+1)/2+j) (i(i+1)/2+j)。对于网络来说只要把 G [ i ] [ j ] G[i][j] G[i][j]定义为边 < v i , v j > <v_i,v_j> <vi,vj>的权重即可。

使用邻接矩阵的方法来表示一个图具有直观、简单、好理解,方便检查一对给定的顶点之间是否存在边,方便查找一个顶点的所有邻接点和方便计算顶点的出入度的优势。但是,对于一个稀疏图(顶点多但边少)来说,使用邻接矩阵的表示方法不仅浪费空间而且对于查找操作来说很费时。

代码实现

//建立图(邻接矩阵)
typedef int WeightType;
typedef int DataType;
typedef int Vertex;

//1.图的结构
typedef struct GNode *PtrToGNode;
struct GNode {
	int Nv;//边的数量
	int Ne;//矩阵的数量
	WeightType G[MaxVertexNum][MaxVertexNum];
	DataType Data[MaxVertexNum];
};
typedef PtrToGNode MGraph;

//边结点的定义
typedef struct ENode *PtrToENode;
struct ENode {
	Vertex V1,V2;
	WeightType Weight;
};
typedef PtrToENode Edge;

//2.图的初始化
MGraph CreatGraph(int VertexNum) {
	Vertex V, W;
	PtrToGNode M = (PtrToGNode)malloc(sizeof(struct GNode));
	M->Nv = VertexNum;
	M->Ne = 0;
	for (V = 0; V < M->Nv; V++) {
		for (W = 0; W < M->Nv; W++) {
			M->G[V][W] = 0;
		}
	}
	return M;
}

//插入操作
void InsertEdge(MGraph M,Edge E) {
	M->Ne++;
	M->G[E->V1][E->V2] = E->Weight;
	M->G[E->V2][E->V1] = E->Weight;
}

//建图函数
MGraph BuildGraph(){
	MGraph M;
	Edge E;
	int Nv;
	scanf_s("%d", &Nv);
	M = CreatGraph(Nv);
	scanf_s("%d", &(M->Ne));
	if (M->Ne != 0) {
		E = (Edge)malloc(sizeof(struct ENode));
		for (int i = 0; i < M->Ne; i++) {
			scanf_s("%d%d%d", &(E->V1), &(E->V2), &(E->Weight));
			InsertEdge(M, E);
		}
	}
	for (int i = 0; i < M->Nv; i++) {
		scanf_s("%d", &(M->Data[i]));
	}
	return M;
}

还有一种简化版的表示方法:

//建立图(邻接矩阵)(简化版)
int G[MaxVertexNum][MaxVertexNum], Ne,Nv;
void BuildGraph() {
	int v1, v2,Weight;
	scanf_s("%d", &Nv);
	for (int i = 0; i < Nv; i++) {
		for (int j = 0; j < Nv; j++) {
			G[i][j] = 0;
		}
	}
	scanf_s("%d", &Nv);
	if (!Nv == 0) {
		scanf_s("%d%d%d", &v1, &v2, &Weight);
		G[v1][v2] = Weight;
		G[v2][v1] = Weight;
	}
}

2.2图的邻接表表示

邻接表是一个链表的集合,首先定义一个指针数组G[N],之后将每一行的非零元素存成一个链表,邻接表的表示是不唯一的。在邻接表中一条边会被存两次,所以对于一个稠密矩阵(边多)来说,邻接表并不节省空间。而且在存放一条边时,不仅要存放顶点的编号,还要存放指向下一个顶点的指针,对于一个网络来说,还要在增加一个权重的域。所以,使用邻接表的话,图一定要够稀疏才划算。

使用邻接表表示图,可以很方便的找到一个顶点的所有邻接点,节约系数图的空间,方便计算无向图中任一结点的度(有向图的话,只能计算出度。若要计算入度,需要使用邻接矩阵的每一列构造一个逆邻接表来计算入度)。而且,邻接表不便于检查任一顶点之间是否存在边。

代码表示


//图的表示:邻接表
typedef int Datatype;
typedef int VerTex;
typedef int WeightType;

typedef struct ENode *PtrToENode;
struct ENode {
	VerTex V1, V2;
	WeightType Weight;
};
typedef PtrToENode Edge;


typedef struct AdjVNode *PtrToAdjVNode;
struct AdjVNode {
	VerTex AdjV;
	WeightType Weight;
	PtrToAdjVNode Next;
};

typedef struct VNode {
	PtrToAdjVNode FirstEdge;
	Datatype Data;
}AdjList[MaxVertexNum];

typedef struct GNode *PtrToGNode;
struct GNode {
	int Ne;
	int Nv;
	AdjList G;
};
typedef PtrToGNode LGraph;
//建立图:邻接表
LGraph CreatLGraph(int Nv) {
	LGraph L = (LGraph)malloc(sizeof(struct GNode));
	L->Nv = Nv;
	L->Ne = 0;
	for (int i = 0; i < L->Nv; i++) {
		L->G[i].FirstEdge->Next = NULL;
	}
	return L;
}

//插入边
void InsertLGraph(LGraph L, Edge E) {
	//插入边<V1,V2>;
	PtrToAdjVNode NewNode1=(PtrToAdjVNode)malloc(sizeof(struct ENode));
	NewNode1->Weight = E->Weight;
	NewNode1->AdjV = E->V2;
	NewNode1->Next = L->G[E->V1].FirstEdge;
	L->G[E->V1].FirstEdge = NewNode1;
	//若是无向图还需要插入边<V2,V1>
	PtrToAdjVNode NewNode2 = (PtrToAdjVNode)malloc(sizeof(struct ENode));
	NewNode2->Weight = E->Weight;
	NewNode2->AdjV = E->V1;
	NewNode2->Next = L->G[E->V2].FirstEdge;
	L->G[E->V2].FirstEdge = NewNode2;
	L->Nv++;
}

//建图函数
LGraph BuildGraph() {
	int Nv;
	LGraph L;
	Edge E;
	scanf_s("%d", &Nv);
	L=CreatLGraph(Nv);
	scanf_s("%d", &(L->Ne));
	if (L->Ne != 0) {
		E = (Edge)malloc(sizeof(struct ENode));
		for (int i = 0; i < L->Ne; i++) {
			scanf_s("%d%d%d", &(E->V1), &(E->V2), &(E->Weight));
			InsertLGraph(L, E);
		}
	}
	for (int i = 0; i < L->Nv; i++) {
		scanf_s("%d", &(L->G[i].Data));
	}
	return L;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值