資料結構(16)--圖的儲存及實現
參考書籍:資料結構(C語言版)嚴蔚敏吳偉民編著清華大學出版社
圖狀結構是一種比樹形結構更復雜的非線性結構。在樹狀結構中,結點間具有分支層次關係,每一層上的結點只能和上一層中的至多一個結點相關,但可能和下一層的多個結點相關。而在圖狀結構中,任意兩個結點之間都可能相關,即結點之間的鄰接關係可以是任意的。
1.鄰接矩陣
1.1結構定義
圖和樹一樣,沒有順序映像的儲存結構,但可以藉助陣列的資料型別表示元素之間的關係。所以鄰接矩陣是用於描述圖中頂點之間關係(即弧或邊的權)的矩陣。 假設圖中頂點數為n,則鄰接矩陣An×n:
1 若Vi和Vj之間有弧或邊
A[i][j]=
0 反之
網的鄰接矩陣An×n:
w (權值
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);
}