圖表示是一種多對多的關係的數據結構。因爲線性表表示的是一種一對一的關係的數據結構,樹表示的是一種一對多的數據結構,所以圖把線性表和樹都包含在內。圖由一個非空的有限頂點集合和一個有限邊集合組成。當我們描述圖的時候,一定要包含以下兩個元素:
1、一組頂點:例如用Vertex表示頂點的集合。
2、一組邊:用Edge表示邊的集合,一條邊是一組頂點對。
•無向邊 :(i,j)∈Edge,i,j∈Vertex。即雙向的,即可從i走到j,也可以從j走到i。
•有向邊 :< i,j >∈Edge,i,j∈Vertex。即單向的(單行線),只可從i走到j或從j走到i。
•圖不考慮重邊和自己連自己的情況。
初始化一個圖是,可以一條邊也沒有,但是不能一個頂點也沒有。
那麼怎麼用代碼表示一個圖?
第一種方法是鄰接矩陣表示法:即用矩陣,一個二維數組來表示一個圖。
#include <stdio.h>
#include <stdlib.h>
#define MaxVertex 100 /*最大頂點數*/
#define INFINITY 65535 /*雙字節無符號整數的最大值*/
typedef int Vertex; /*二維數組的下標表示頂點,爲整型*/
typedef int WeightType; /*邊的權值的類型*/
typedef char DataType; /*頂點存儲的數據類型爲字符型*/
/*圖的數據結構*/
typedef struct GraphNode *PtrlGraphNode;
struct GraphNode {
int numv; /*頂點數*/
int nume; /*邊數*/
WeightType wt[MaxVertex][MaxVertex]; /*鄰接矩陣*/
DataType Data[MaxVertex]; /*存放頂點的數據*/
/*很多情況下,頂點無數據,此時Data[]可以不用*/
};
typedef PtrlGraphNode Graph;/*用鄰接矩陣存儲的圖*/
用一個二維數組來表示圖中一組頂點對的關係,還有一個一維數組來存放頂點的數據。
/*邊的數據結構*/
typedef struct EdgeNode *PtrlEdgeNode;
struct EdgeNode {
Vertex V1, V2; /*有向邊<V1, V2>*//*無向邊(V1, V2)*/
WeightType weight; /*權重*/
};
typedef PtrlEdgeNode Edge;
邊的數據結構,一條邊由一組頂點對和權重表示(即頂點對的關係)。
表示圖的各個數據結構都建立好後,就開始建立一個圖,思路是先創建一個有全部頂點但沒有邊的圖,在逐條插入邊。
/*建立一個有VertexNum個頂點但沒有邊的空圖*/
Graph CreatGraph(int VertexNum)
{
Vertex i,j;
Graph MyGraph;
MyGraph=(Graph)malloc(sizeof(struct GraphNode));/*申請空間建立圖*/
MyGraph->numv=VertexNum;
MyGraph->nume=0; //初始化邊數爲0;
/*對二維數組矩陣初始化*/
for (i=0;i<MyGraph->numv;i++) {
for (j=0;j<MyGraph->numv;j++) {
MyGraph->wt[i][j]=INFINITY;
}
}
return MyGraph;
}
CreatGraph函數中輸入一個值來創建有Vertexnum個結點的圖,然後初始化二維數組中的元素爲無窮(或0)。
/*插入邊*/
void InsertEdge(Graph MyGraph, Edge MyEdge)
{
/*插入邊<V1, V2>*/
MyGraph->wt[MyEdge->V1][MyEdge->V2]=MyEdge->weight;
/*如果是無向圖,還要加上插入邊<V2, V1>*/
MyGraph->wt[MyEdge->V2][MyEdge->V1]=MyEdge->weight;
}
然後插入邊,把邊的V1,V2,也就是兩個頂點(對應二維數組的兩個下標),以及權值插入到對應二維數組的位置。
Graph BuildGraph()
{
Graph MyGraph; /*建圖*/
Edge MyEdge; /*建邊*/
Vertex v;
int VertexNum,i;
printf("請輸入頂點個數");
scanf("%d", &VertexNum);
MyGraph=CreatGraph(VertexNum);/*建立一個有numv個頂點,沒有邊的圖*/
printf("請輸入邊數:");
scanf("%d", &MyGraph->nume); /* 讀入邊數 */
MyEdge=(Edge)malloc(sizeof(struct EdgeNode));/*建立邊結點*/
/*插入邊:格式爲:"起點->終點->權重"。插入到鄰接矩陣*/
for (i=0;i<MyGraph->nume;i++) {
scanf("%d %d %d",&MyEdge->V1, &MyEdge->V2, &MyEdge->weight);
InsertEdge(MyGraph, MyEdge);
}
/*如果頂點有數據,就讀入數據*/
for (i=0;i<MyGraph->numv;i++) {
scanf("%c", &MyGraph->Data[i]);
}
return MyGraph;
}
Graph[ n ][ n ]二維數組中,用n個頂點從0到n-1編號。如果一組頂點對< i,j >是圖Graph中的邊的話,就令Graph[ i ][ j ]=1,否則等於0。
用鄰接矩陣表示法表示出來的矩陣圖,有以下特點:
1、矩陣上的對角線全都是0。因爲圖中的頂點對不會出現自己連自己的情況,所以對於矩陣圖中Graph[ n ][ n ]一定是等於0。
2、鄰接矩陣表示法表示出來的矩陣無向圖,一定是沿對角線對稱的,也就是說一定是一個對稱矩陣。這是因爲,假設圖中結點1和2是有一條邊的,所以2和1之間也是有一條邊的,所以Graph[ 1 ][ 2 ]和Graph[ 2 ][ 1 ]一定是相等都等於1的。
用矩陣的方式,好處是容易檢查某條邊(頂點對)是否存在,容易找出一個頂點的鄰接點,方便計算頂點的度。不過矩陣表示的不好在於,對於稀疏圖來說,鄰接矩陣的方式會浪費空間,因爲稀疏圖即邊很少,也就是二維數組裏很多元素都是0。矩陣在稀疏圖的情況下浪費空間也浪費時間,比如我想統計稀疏圖裏一共有多少條邊,把二維數組遍歷一遍,統計元素1有多少個,問題是1很少,所以造成很多對0的遍歷,是浪費時間的。
那麼如何解決鄰接矩陣浪費空間和時間的問題?我們知道線性表,線性表順序存儲需要實現分配好內存,這樣有可能造成空間浪費或空間不夠用,於是就有了鏈表的方法,同樣圖的表示方法也可以用鏈表的方式,就是鄰接表表示法。
鄰接表表示法,即用一個鏈表的集合,一個一維指針數組,依然用數組下標表示頂點。所以數組的大小就是圖中頂點的個數。
#include <stdio.h>
#include <stdlib.h>
#define MaxVertex 100 /*最大頂點數*/
typedef int Vertex; /*頂點的下標表示頂點*/
typedef int WeightType; /*邊的權值類型爲整型*/
typedef char DataType; /*頂點存儲的數據的類型*/
/*邊的數據結構*/
typedef struct EdgeNode *PtrlEdgeNode;
struct EdgeNode {
Vertex V1, V2; /* 向邊<V1, V2>*/
WeightType weight; /*權重*/
};
typedef PtrlEdgeNode Edge;
/*鄰接點的數據結構*/
typedef struct PointNode *PtrlPointNode;
struct PointNode {
Vertex index; /*鄰接點的下標*/
WeightType weight; /*邊的權重*/
PtrlPointNode Next; /*指向下一個鄰接點的指針*/
};
/*頂點表頭結點的數據結構*/
typedef struct HeadNode {
PtrlPointNode HeadEdge; /*邊的表頭結點指針*/
DataType Data; /*頂點的數據*/
} PointList[MaxVertex]; /*鄰接表類型*/
/*圖結點的數據結構*/
typedef struct GraphNode *PtrlGraphNode;
struct GraphNode {
int numv; /*頂點數*/
int nume; /*邊數*/
PointList PL; /*鄰接表*/
};
typedef PtrlGraphNode ListGraphNode; /*鄰接表方式存儲圖*/
第26到29行,頂點的表頭就是一個結構體指針數組。指針類型是第18到22行的鄰接點的數據結構。
/*創建一個有Vertexnum個頂點但沒有邊的圖*/
ListGraphNode CreatGraph(int Vertexnum)
{
Vertex i;
ListGraphNode MyGraph;
MyGraph=(ListGraphNode)malloc(sizeof(struct GraphNode));
MyGraph->nume=0;
MyGraph->numv=Vertexnum;
for (i=0;i<MyGraph->numv;i++) {
MyGraph->PL[i].HeadEdge=NULL; //初始化數組裏各個頭結點
}
return MyGraph;
}
/*插入邊*/
void InsertEdge(ListGraphNode MyGraph, Edge E)
{
PtrlPointNode Node; /*鄰接點*/
/*插入邊<V1, V2>*/
/*爲 V2建立新的鄰接點*/
Node=(PtrlPointNode)malloc(sizeof(struct PointNode));
Node->index=E->V2; /*將V2插入V1的表頭*/
Node->weight=E->weight;
Node->Next=MyGraph->PL[E->V1].HeadEdge;
MyGraph->PL[E->V1].HeadEdge=Node;
/*如果是無向圖,還要插入邊<V2, V1>*/
Node=(PtrlPointNode)malloc(sizeof(struct PointNode));
Node->index=E->V1; /*將V2插入V1的表頭*/
Node->weight=E->weight;
Node->Next=MyGraph->PL[E->V2].HeadEdge;
MyGraph->PL[E->V2].HeadEdge=Node;
}
/*創建圖*/
ListGraphNode BuildGraph()
{
ListGraphNode MyGraph;
Edge E;
Vertex i,j,numv;
printf("請輸入要讀入的頂點個數:");
scanf("%d", &numv);
MyGraph=CreatGraph(numv);
printf("請輸入要讀入的邊數:");
scanf("%d", &MyGraph->nume);
if (MyGraph->nume!=0) {
E=(Edge)malloc(sizeof(struct EdgeNode)); /*建立邊結點*/
/*讀入邊:格式爲"起點 終點 權重"*/
for (i=0;i<MyGraph->numv;i++) {
scanf("%d %d %d", &E->V1, &E->V2, &E->weight);
/*插入邊到圖裏*/
InsertEdge(MyGraph, E);
}
}
/*如果頂點有數據的話,讀入數據*/
for (i=0;i<MyGraph->numv;i++) {
scanf("%c", &MyGraph->PL[i].Data);
}
return MyGraph;
}
但是對於鄰接表表示法來說,一條邊必定是被存了兩次的,例如頂點對< 5, 9 >,在指針數組5的鏈表裏必定有9,在指針數組9的鏈表裏也必定有5。所以用鄰接表的話,表示的圖要是稀疏圖纔算省空間,越稀疏越好。而對於完全圖來說,用鄰接矩陣的方式更方便後面的操作。