1. 程式人生 > >資料結構(16)--圖的儲存及實現

資料結構(16)--圖的儲存及實現

參考書籍:資料結構(C語言版)嚴蔚敏吳偉民編著清華大學出版社    

    圖狀結構是一種比樹形結構更復雜的非線性結構。在樹狀結構中,結點間具有分支層次關係,每一層上的結點只能和上一層中的至多一個結點相關,但可能和下一層的多個結點相關。而在圖狀結構中,任意兩個結點之間都可能相關,即結點之間的鄰接關係可以是任意的。

1.鄰接矩陣

1.1結構定義

    圖和樹一樣,沒有順序映像的儲存結構,但可以藉助陣列的資料型別表示元素之間的關係。所以鄰接矩陣是用於描述圖中頂點之間關係(即弧或邊的權)的矩陣。   假設圖中頂點數為n,則鄰接矩陣An×n:

                1    若Vi和Vj之間有弧或邊
    A[i][j]=
                0    反之
    
    網的鄰接矩陣An×n:
                w (權值

)          若Vi和Vj之間有弧或邊
    A[i][j]=
                無窮大                   反之

注意:
1) 圖中無鄰接到自身的弧,因此鄰接矩陣主對角線為全零。
2) 無向圖的鄰接矩陣必然是對稱矩陣。
3) 無向圖中,頂點的度是鄰接矩陣對應行(或列)的非零元素之和;有向圖中,對應行的非零元素之和是該頂點的出度;對應列的非零元素之和是該頂點的入度;則該頂點的度是對應行和對應列的非零元素之和。

1.2示例

有向網示例:


1.3程式碼實現

#include<stdio.h>
//#include<stdlib.h>
/*
圖的表示方法
DG(有向圖)或者DN(有向網):鄰接矩陣、鄰接表(逆鄰接表--為求入度)、十字連結串列
UDG(無向圖)或者UDN(無向網):鄰接矩陣、鄰接表、鄰接多重表
*/

//1.陣列表示法(鄰接矩陣):將以有向網為例
#define INFINITY 32767//最大值:假定為無窮大
#define MAX_VERTEX_NUM 10//最大頂點數目
//typedef enum GraphKind {DG, DN, UDG, UDN};  //有向圖:0,有向網:1,無向圖:2,無向網:3

typedef int VRType;//頂點關係型別,對於無權圖或網,用0或1表示相鄰否;對於帶權圖或網,則為相應權值
typedef int VertexType;//頂點型別
typedef VRType AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
typedef struct{
	VertexType vexs[MAX_VERTEX_NUM];//頂點向量
	AdjMatrix arcs;//鄰接矩陣
	int vexnum, arcnum;//圖的當前頂點數和弧數
	//GraphKind kind;//圖的種類標誌
}MGraph;//鄰接矩陣表示的圖
//若圖G中存在頂點v,則返回v在圖中的位置資訊,否則返回其他資訊
int locateVex(MGraph G, VertexType v){
	for(int i = 0; i < G.vexnum; i++){
		if(G.vexs[i] == v)
			return i;
	}
	return -1;//圖中沒有該頂點
}
//採用鄰接矩陣表示法構造有向網G
void createDN(MGraph &G){
	printf("輸入頂點數和弧數如:(5,3):");
	scanf("%d,%d", &G.vexnum, &G.arcnum);

	//構造頂點向量
	printf("輸入%d個頂點(以空格隔開如:v1 v2 v3):", G.vexnum);
	getchar();//吃掉換行符
	for(int m = 0; m < G.vexnum; m++){
		scanf("v%d", &G.vexs[m]);
		getchar();//吃掉空格符
	}

	
	//初始化鄰接矩陣
	int i=0, j=0;
	for(i = 0; i < G.vexnum; i++){
		for(j = 0; j < G.vexnum; j++)
			G.arcs[i][j] = INFINITY;
	}

	//構造鄰接矩陣
	VertexType v1, v2;//分別是一條弧的弧尾和弧頭(起點和終點)
	VRType w;//對於無權圖或網,用0或1表示相鄰否;對於帶權圖或網,則為相應權值	
	printf("\n每行輸入一條弧依附的頂點(先弧尾後弧頭)和權值(如:v1 v2 3):\n");
	fflush(stdin);//清除殘餘後,後面再讀入時不會出錯
	for(int k = 0; k < G.arcnum; k++){
		scanf("v%d v%d %d",&v1, &v2, &w);
		fflush(stdin);//清除殘餘後,後面再讀入時不會出錯
		i = locateVex(G, v1);
		j = locateVex(G, v2);
		G.arcs[i][j] = w;
	}
}
//列印鄰接矩陣
void printDN(MGraph G){
	printf("\n列印有向網G的鄰接矩陣:\n");
	for(int i = 0; i < G.vexnum; i++){
		for(int j = 0; j < G.vexnum; j++)
		{
			if(G.arcs[i][j] != INFINITY)
				printf("%9d ", G.arcs[i][j]);
			else
				printf("INFINITY  ");
		}
		printf("\n");
	}
	printf("\n");
}

1.4演示

/*測試:
6,10
v1 v2 v3 v4 v5 v6

v1,v2,5
v1,v4,7

v2,v3,4

v3,v1,8
v3,v6,9

v4,v3,5
v4,v6,6

v5,v4,5

v6,v1,3
v6,v5,1
*/
void main(){
	MGraph G;
	createDN(G);	
	printDN(G);
}


2.鄰接表

2.1結構定義

類似樹的孩子連結串列。即對圖中的每個頂點vi建立一個單鏈表,表中結點表示依附於該頂點vi的邊或弧。           

          弧結點                                                                                         頂點結點(弧連結串列表頭結點)

                                                       

注意:
在無向圖的鄰接表中,
1)第i個連結串列中結點數目為頂點i的度;
2)所有連結串列中結點數目的一半為圖中邊數;
3)佔用的儲存單元數目為n+2e。
在有向圖的鄰接表中,
1)第i個連結串列中結點數目為頂點i的出度;
2)所有連結串列中結點數目為圖中弧數;
3)佔用的儲存單元數目為n+e。

為求出每一個頂點的入度,必須另外建立有向圖的逆鄰接表。有向圖的逆鄰接表與鄰接表類似,只是它是從入度考慮結點,而不是從出度考慮結點。

2.2示例

無向網示例:

          

2.3程式碼實現

#include<stdio.h>
#include<stdlib.h>
/*
圖的表示方法
DG(有向圖)或者DN(有向網):鄰接矩陣、鄰接表(逆鄰接表--為求入度)、十字連結串列
UDG(無向圖)或者UDN(無向網):鄰接矩陣、鄰接表、鄰接多重表
*/
#define MAX_VERTEX_NUM 10//最大頂點數目
#define NULL 0
typedef int VRType;//對於帶權圖或網,則為相應權值
typedef int VertexType;//頂點型別
//typedef enum GraphKind {DG, DN, UDG, UDN};  //有向圖:0,有向網:1,無向圖:2,無向

typedef struct ArcNode{	
	int adjvex;//該弧所指向的頂點的在圖中位置
	VRType w;//弧的相應權值
	struct ArcNode *nextarc;//指向下一條弧的指標
}ArcNode;//弧結點資訊

typedef struct VNode{
	VertexType data;//頂點資訊
	ArcNode *firstarc;//指向第一條依附該頂點的弧的指標
}VNode, AdjVexList[MAX_VERTEX_NUM];//頂點結點資訊

typedef struct{
	AdjVexList vexs;//頂點向量
	int vexnum, arcnum;//圖的當前頂點數和弧數
	//GraphKind kind;//圖的種類標誌
}ALGraph;//鄰接表表示的圖
//若圖G中存在頂點v,則返回v在圖中的位置資訊,否則返回其他資訊
int locateVex(ALGraph G, VertexType v){
	for(int i = 0; i < G.vexnum; i++){
		if(G.vexs[i].data == v)
			return i;
	}
	return -1;//圖中沒有該頂點
}
//採用鄰接表表示法構造無向網G
void createUDN(ALGraph &G){
	printf("輸入頂點數和弧數如:(5,3):");
	scanf("%d,%d", &G.vexnum, &G.arcnum);

	//構造頂點向量,並初始化
	printf("輸入%d個頂點(以空格隔開如:v1 v2 v3):", G.vexnum);
	getchar();//吃掉換行符
	for(int m = 0; m < G.vexnum; m++){
		scanf("v%d", &G.vexs[m].data);
		G.vexs[m].firstarc = NULL;//初始化為空指標////////////////重要!!!
		getchar();//吃掉空格符
	}

	//構造鄰接表
	VertexType v1, v2;//分別是一條弧的弧尾和弧頭(起點和終點)
	VRType w;//對於無權圖或網,用0或1表示相鄰否;對於帶權圖或網,則為相應權值	
	printf("\n每行輸入一條弧依附的頂點(先弧尾後弧頭)和權值(如:v1 v2 3):\n");
	fflush(stdin);//清除殘餘後,後面再讀入時不會出錯
	int i = 0, j = 0;
	for(int k = 0; k < G.arcnum; k++){
		scanf("v%d v%d %d",&v1, &v2, &w);
		fflush(stdin);//清除殘餘後,後面再讀入時不會出錯
		i = locateVex(G, v1);//弧起點
		j = locateVex(G, v2);//弧終點
		
		//採用“頭插法”在各個頂點的弧鏈頭部插入弧結點
		ArcNode *p1 = (ArcNode *)malloc(sizeof(ArcNode));//構造一個弧結點,作為弧vivj的弧頭(終點)
		p1->adjvex = j;
		p1->w = w;
		p1->nextarc = G.vexs[i].firstarc;
		G.vexs[i].firstarc = p1;
		ArcNode *p2 = (ArcNode *)malloc(sizeof(ArcNode));//構造一個弧結點,作為弧vivj的弧尾(起點)
		p2->adjvex = i;
		p2->w = w;
		p2->nextarc = G.vexs[j].firstarc;
		G.vexs[j].firstarc = p2;
	}
}
//列印鄰接表
void printAdjList(ALGraph G){
	printf("\n");
	for(int i = 0; i < G.vexnum; i++){
		printf("依附頂點v%d的弧為:", G.vexs[i].data);
		ArcNode *p = G.vexs[i].firstarc;
		while(p){
			printf("v%dv%d(weight:%d) ", G.vexs[i].data, G.vexs[p->adjvex].data, p->w);
			p = p->nextarc;
		}
		printf("\n");
	}
	printf("\n");
}

2.4演示

/*測試:
4,4
v1 v2 v3 v4

v1 v2 3
v1 v3 6
v1 v4 4
v2 v4 9
*/
void main(){
	ALGraph G;
	createUDN(G);
	printAdjList(G);
}


3.鄰接多重表

3.1結構定義

    在無向圖的鄰接表中,每條邊(Vi,Vj)由兩個結點表示,一個結點在第 i 個連結串列中,另一個結點在第 j 個連結串列中,當需要對邊進行操作時,就需找到表示同一條邊的兩個結點,這給操作帶來不便,在這種情況下采用鄰接多重表較方便。
    鄰接多重表中結點分為:邊結點和頂點結點 :         

           弧結點                                                                                         頂點結點(弧連結串列表頭結點)

                         

3.2示例

無向圖示例:


3.3程式碼實現

#include<stdio.h>
#include<stdlib.h>
/*
圖的表示方法
DG(有向圖)或者DN(有向網):鄰接矩陣、鄰接表(逆鄰接表--為求入度)、十字連結串列
UDG(無向圖)或者UDN(無向網):鄰接矩陣、鄰接表、鄰接多重表
*/
#define MAX_VERTEX_NUM 10//最大頂點數目
#define NULL 0
//typedef int VRType;//對於帶權圖或網,則為相應權值
typedef int VertexType;//頂點型別
//typedef enum GraphKind {DG, DN, UDG, UDN};  //有向圖:0,有向網:1,無向圖:2,無向

typedef struct ArcNode{	
	int ivex, jvex;//該邊所依附的2個頂點的在圖中位置
	struct ArcNode *ivexNextarc, *jvexNextarc;//分別指向該邊所依附的兩個頂點下一條邊
}ArcNode;//弧結點資訊

typedef struct VNode{
	VertexType data;//頂點資訊
	ArcNode *firstedge;//指向第一條依附該頂點的弧的指標
}VNode, AdjMuitiVexList[MAX_VERTEX_NUM];//頂點結點資訊

typedef struct{
	AdjMuitiVexList vexs;//頂點向量
	int vexnum, arcnum;//圖的當前頂點數和弧數
	//GraphKind kind;//圖的種類標誌
}AMLGraph;//鄰接表表示的圖
//若圖G中存在頂點v,則返回v在圖中的位置資訊,否則返回其他資訊
int locateVex(AMLGraph G, VertexType v){
	for(int i = 0; i < G.vexnum; i++){
		if(G.vexs[i].data == v)
			return i;
	}
	return -1;//圖中沒有該頂點
}
//採用鄰接多重表表示法構造無向圖G
void createUDG(AMLGraph &G){
	printf("輸入頂點數和弧數如:(5,3):");
	scanf("%d,%d", &G.vexnum, &G.arcnum);

	//構造頂點向量,並初始化
	printf("輸入%d個頂點(以空格隔開如:v1 v2 v3):", G.vexnum);
	getchar();//吃掉換行符
	for(int m = 0; m < G.vexnum; m++){
		scanf("v%d", &G.vexs[m].data);
		G.vexs[m].firstedge = NULL;//初始化為空指標////////////////重要!!!
		getchar();//吃掉空格符
	}

	//構造鄰接多重表
	VertexType v1, v2;//分別是一條的兩個頂點
	printf("\n每行輸入一條邊依附的頂點(如:v1v2):\n");
	fflush(stdin);//清除殘餘後,後面再讀入時不會出錯
	int i = 0, j = 0;
	for(int k = 0; k < G.arcnum; k++){
		scanf("v%dv%d",&v1, &v2);
		fflush(stdin);//清除殘餘後,後面再讀入時不會出錯
		i = locateVex(G, v1);//邊依附的兩個頂點的在圖中的位置
		j = locateVex(G, v2);
		
		//採用“頭插法”在各個頂點的邊鏈頭部插入邊結點
		ArcNode *p = (ArcNode *)malloc(sizeof(ArcNode));//構造一個邊結點,它依附於vivj兩個頂點
		p->ivex = i;
		p->ivexNextarc = G.vexs[i].firstedge;
		G.vexs[i].firstedge = p;
		p->jvex = j;
		p->jvexNextarc = G.vexs[j].firstedge;
		G.vexs[j].firstedge = p;
	}
}
//列印鄰接多重表
void printAdjMultiList(AMLGraph G){
	printf("\n");
	for(int i = 0; i < G.vexnum; i++){
		printf("依附頂點v%d的邊為:", G.vexs[i].data);
		ArcNode *p = G.vexs[i].firstedge;

		while(p){
			if(p->ivex == i){
				printf("v%dv%d ", G.vexs[i].data, G.vexs[p->jvex].data);
				p = p->ivexNextarc;
			}else if(p->jvex == i){
				printf("v%dv%d ", G.vexs[i].data, G.vexs[p->ivex].data);
				p = p->jvexNextarc;
			}
		}
		printf("\n");
	}
	printf("\n");
}

3.4演示

/*
測試:
4,3
v1 v2 v3 v4

  v1v2
  v1v3
  v2v4
*/
void main(){
	AMLGraph G;
	createUDG(G);
	printAdjMultiList(G);
}


4.十字連結串列

4.1結構定義

十字連結串列是有向圖的另一種鏈式儲存結構。可以理解成有向圖的鄰接表和逆鄰接表的結合,在十字連結串列中,有兩種結點結構:         

             弧結點                                                                                      頂點結點(弧連結串列表頭結點)

          

4.2示例

有向圖示例:


4.3程式碼實現

#include<stdio.h>
#include<stdlib.h>
/*
圖的表示方法
DG(有向圖)或者DN(有向網):鄰接矩陣、鄰接表(逆鄰接表--為求入度)、十字連結串列
UDG(無向圖)或者UDN(無向網):鄰接矩陣、鄰接表、鄰接多重表
*/
#define MAX_VERTEX_NUM 10//最大頂點數目
#define NULL 0
//typedef int VRType;//對於帶權圖或網,則為相應權值
typedef int VertexType;//頂點型別
//typedef enum GraphKind {DG, DN, UDG, UDN};  //有向圖:0,有向網:1,無向圖:2,無向

typedef struct ArcNode{	
	int tailvex, headvex;//該弧的弧尾(起點)和弧頭(終點)所指向的頂點的在圖中位置
	struct ArcNode *tailNextarc, *headNextArc;//分別為指向弧尾(起點)相同的弧的下一條弧的指標、弧頭(終點)相同的弧的下一條弧的指標
}ArcNode;//弧結點資訊

typedef struct VNode{
	VertexType data;//頂點資訊
	ArcNode *firstIn;//指向第一條以該頂點為弧尾(起點)的指標
	ArcNode *firstOut;//指向第一條以該頂點的弧頭(終點)的指標
}VNode, VexList[MAX_VERTEX_NUM];//頂點結點資訊

typedef struct{
	VexList vexs;//頂點向量
	int vexnum, arcnum;//圖的當前頂點數和弧數
	//GraphKind kind;//圖的種類標誌
}OLGraph;//鄰接表表示的圖
//若圖G中存在頂點v,則返回v在圖中的位置資訊,否則返回其他資訊
int locateVex(OLGraph G, VertexType v){
	for(int i = 0; i < G.vexnum; i++){
		if(G.vexs[i].data == v)
			return i;
	}
	return -1;//圖中沒有該頂點
}
//採用十字連結串列表示法構造有向圖G
void createDG(OLGraph &G){
	printf("輸入頂點數和弧數如:(5,3):");
	scanf("%d,%d", &G.vexnum, &G.arcnum);

	//構造頂點向量,並初始化
	printf("輸入%d個頂點(以空格隔開如:v1 v2 v3):", G.vexnum);
	getchar();//吃掉換行符
	for(int m = 0; m < G.vexnum; m++){
		scanf("v%d", &G.vexs[m].data);
		G.vexs[m].firstIn = NULL;//初始化為空指標////////////////重要!!!
		G.vexs[m].firstOut = NULL;
		getchar();//吃掉空格符
	}

	//構造十字連結串列
	VertexType v1, v2;//分別是一條弧的弧尾和弧頭(起點和終點)
	printf("\n每行輸入一條弧依附的頂點(先弧尾後弧頭)(如:v1v2):\n");
	fflush(stdin);//清除殘餘後,後面再讀入時不會出錯
	int i = 0, j = 0;
	for(int k = 0; k < G.arcnum; k++){
		scanf("v%dv%d",&v1, &v2);
		fflush(stdin);//清除殘餘後,後面再讀入時不會出錯
		i = locateVex(G, v1);//弧起點
		j = locateVex(G, v2);//弧終點
		
		//採用“頭插法”在各個頂點的弧鏈頭部插入弧結點
		ArcNode *p = (ArcNode *)malloc(sizeof(ArcNode));//構造一個弧結點,作為弧vivj的弧頭(終點)
		p->tailvex = i;
		p->tailNextarc = G.vexs[i].firstOut;
		G.vexs[i].firstOut = p;
		p->headvex = j;
		p->headNextArc = G.vexs[j].firstIn;
		G.vexs[j].firstIn = p;
	}
}
//列印十字連結串列
void printOrthogonalList(OLGraph G){
	printf("\n");
	for(int i = 0; i < G.vexnum; i++){
		printf("以頂點v%d為弧尾的弧有為:", G.vexs[i].data);
		ArcNode *p = G.vexs[i].firstOut;
		while(p){
			printf("v%dv%d ", G.vexs[i].data, G.vexs[p->headvex].data);
			p = p->tailNextarc;
		}
		printf("\n");
	}
	printf("\n");
}

4.4演示

/*測試:
4,7
v1 v2 v3 v4

v1v2
v1v3

v3v1
v3v4

v4v1
v4v2
v4v3
*/
void main(){
	OLGraph G;
	createDG(G);
	printOrthogonalList(G);
}