鄰接矩陣存儲法
回顧:圖G = <V,E>
鄰接矩陣存儲法的主要思想如下
1、用一個數組存儲所有頂點,代表集合V中的元素
2、用一個二維數組存邊,代表集合E中的元素
無向圖的鄰接矩陣存儲
我們通過具體的例子來講解,以下圖為例
1、邊使用矩陣來構建模型,這使得每一個頂點和其它頂點之間都有邊的有無 的 表示的機會。若有邊,則他們交點 為1 ,否則為0。
2、無向圖的邊的矩陣一定是一個對稱矩陣,因為無向圖只關心邊是否存在,而不關心方向,V0和V1有邊,那么V1和V0也有邊。
3、因為這里不研究有圈圖,所以主對角線都是0
先牛刀小試 一下,構建數據類型,寫出API。 :)
代碼都很好理解,唯一 一個技巧就是create函數中的二重for循環,利用了選擇排序中的思想,避免了邊信息的重復填充,也就是例如,輸入V0和V1邊的關系后,就不必輸入V1和V0的關系了。
在上一篇文章中提到:
頂點數為n的圖,最多有條邊,這里的二重for循環的時間復雜度恰好也是O(
) = O(n2)
這個技巧后面都會用到。
for (int j = 0; j <MAX_VERTEX; ++j) //填充邊關系 { for (int i = j+1; i < MAX_VERTEX; ++i) { printf("若元素%c和%c有邊,則輸入1,否則輸入0\t",pGraph->vertexArr[j],pGraph->vertexArr[i]); scanf("%d",&( pGraph->edgeArr[j][i])); pGraph->edgeArr[i][j] = pGraph->edgeArr[j][i]; //對稱 } }
代碼:

#include<stdio.h> #define MAX_VERTEX 4 typedef char DataType; //圖中元素的目標數據類型 typedef struct { DataType vertexArr[MAX_VERTEX]; //頂點元素數組 int edgeArr[MAX_VERTEX][MAX_VERTEX]; //邊矩陣二維數組 }ArrayGraph; void ArrayGraph_init(ArrayGraph *pGraph); void ArrayGraph_create(ArrayGraph *pGraph); void ArrayGraph_show(ArrayGraph *pGraph); int main() { ArrayGraph g; ArrayGraph_init(&g); //初始化圖 ArrayGraph_create(&g); //創建圖 ArrayGraph_show(&g); //打印圖 return 0; } //初始化為一個無圈圖 ,也就是邊矩陣中,主對角線元素都是0 void ArrayGraph_init(ArrayGraph *pGraph) { for (int i = 0; i < MAX_VERTEX; i++) pGraph->edgeArr[i][i] = 0; } void ArrayGraph_create(ArrayGraph *pGraph) { for (int i = 0; i < MAX_VERTEX; ++i) //填充頂點數組,也就是輸入頂點元素 { printf("輸入第%d個頂點值\n",i+1); scanf(" %c",&(pGraph->vertexArr[i])); } for (int j = 0; j <MAX_VERTEX; ++j) //填充邊關系 { for (int i = j+1; i < MAX_VERTEX; ++i) { printf("若元素%c和%c有邊,則輸入1,否則輸入0\t",pGraph->vertexArr[j],pGraph->vertexArr[i]); scanf("%d",&( pGraph->edgeArr[j][i])); pGraph->edgeArr[i][j] = pGraph->edgeArr[j][i]; //對稱 } } } void ArrayGraph_show(ArrayGraph *pGraph) { printf("\n\n頂點元素如下\n"); for (int i = 0; i < MAX_VERTEX; ++i) { printf("%-5c", pGraph->vertexArr[i]); } printf("\n\n"); puts("邊矩陣如下\n\n"); printf("%-2c",' '); for(int i=0;i<MAX_VERTEX;++i) printf("%-5c",pGraph->vertexArr[i]); putchar('\n'); for (int j = 0; j <MAX_VERTEX; ++j) { printf("%-2c",pGraph->vertexArr[j]); for (int i = 0; i < MAX_VERTEX; ++i) { printf("%-5d",pGraph->edgeArr[i][j]); } putchar('\n'); } printf("\n\n"); }
有向圖的鄰接矩陣存儲
使用鄰接矩陣呢存儲時,有向圖和無向圖的區別在與 邊和弧矩陣的差別。因為弧是有方向的,所以我們 以對角線為界,將矩陣划分為2個區域:
左下區域表示出弧標記區域,坐上區域代表入弧標記區域(當然也可以交換,看個人習慣)
如 若代表弧的矩陣為arcArr
arcArr[V2][V3] 為1,且在出弧標記區域,則說明 V3<------V2
arcArr[V3][V2] 為0,且在入弧標記區域,則說明 V2---\--->V3
代碼:

#include<stdio.h> #define MAX_VERTEX 4 typedef char DataType; //圖中元素的目標數據類型 typedef struct { DataType vertexArr[MAX_VERTEX]; //頂點元素數組 int arcArr[MAX_VERTEX][MAX_VERTEX]; //弧矩陣二維數組 }ArrayGraph; void ArrayGraph_init(ArrayGraph *pGraph); void ArrayGraph_create(ArrayGraph *pGraph); void ArrayGraph_show(ArrayGraph *pGraph); int main() { ArrayGraph g; ArrayGraph_init(&g); ArrayGraph_create(&g); ArrayGraph_show(&g); return 0; } //初始化為一個無圈圖 ,也就是弧矩陣中,主對角線元素都是0 void ArrayGraph_init(ArrayGraph *pGraph) { for (int i = 0; i < MAX_VERTEX; i++) pGraph->arcArr[i][i] = 0; } void ArrayGraph_create(ArrayGraph *pGraph) { for (int i = 0; i < MAX_VERTEX; ++i) //填充頂點數組 { printf("輸入第%d個頂點值\n",i+1); scanf(" %c",&(pGraph->vertexArr[i])); } for (int j = 0; j <MAX_VERTEX; ++j) //填充邊關系 { for (int i = j+1; i < MAX_VERTEX; ++i) { printf("若元素%c有指向%c的弧,則輸入1,否則輸入0\t",pGraph->vertexArr[i],pGraph->vertexArr[j]); scanf("%d",&( pGraph->arcArr[j][i])); printf("若元素%c有指向%c的弧,則輸入1,否則輸入0\t",pGraph->vertexArr[j],pGraph->vertexArr[i]); scanf("%d",&( pGraph->arcArr[i][j])); } } } void ArrayGraph_show(ArrayGraph *pGraph) { printf("\n\n頂點元素如下\n"); for (int i = 0; i < MAX_VERTEX; ++i) { printf("%-5c", pGraph->vertexArr[i]); } printf("\n\n"); puts("弧矩陣如下\n\n"); printf("%-2c",' '); for(int i=0;i<MAX_VERTEX;++i) printf("%-5c",pGraph->vertexArr[i]); putchar('\n'); for (int j = 0; j <MAX_VERTEX; ++j) { printf("%-2c",pGraph->vertexArr[j]); for (int i = 0; i < MAX_VERTEX; ++i) { printf("%-5d",pGraph->arcArr[i][j]); } putchar('\n'); } putchar('\n'); }
無向網的鄰接矩陣存儲
無向網的邊是有權值的,這個值可以是任何一個合法的值,什么樣的值是合法的呢?這需要根據圖的具體用途來定。所以,我們不能用簡單的0,1來代表邊。
如果2個頂點無關聯,他們也不能用0表示,因為0也可能是一個合法的wieght值。可有類比一下:如何地球上2個地方之間不可互通,那么他們之間的車程費是不是無窮大呢?
所以,我們來要根據圖權值類型定義一個相應類型的最大值,來代表2個頂點之間不關聯。
同樣用一個例子。
V0 V1之間的權值為12
V0 V2之間的權值為1
V0 V3之間的權值為5
V2 V3之間的權值為7
代碼:

#include<stdio.h> #define MAX_VERTEX 4 #define INFINITY 65535 typedef char DataType; //存儲的元素類型 typedef int WeightType; //權值的類型 typedef struct { DataType vertexArr[MAX_VERTEX]; //存儲頂點的數組 WeightType edgeArr[MAX_VERTEX][MAX_VERTEX]; //存儲邊的二維數組 }UArrayNet; //數據結構類型:無向網 void UArrayNet_init(UArrayNet*pGraph); void UArrayNet_create(UArrayNet*pGraph); void UArrayNet_show(UArrayNet *pGraph); int main() { UArrayNet net; UArrayNet_init(&net); UArrayNet_create(&net); UArrayNet_show(&net); return 0; } void UArrayNet_init(UArrayNet*pGraph) { for (int i = 0; i < MAX_VERTEX; ++i) { pGraph->edgeArr[i][i] = INFINITY; } } void UArrayNet_create(UArrayNet*pGraph) { for (int i = 0; i < MAX_VERTEX; ++i) //填充頂點數組 { printf("輸入第%d個頂點值\n", i + 1); scanf(" %c", &(pGraph->vertexArr[i])); } for (int j = 0; j <MAX_VERTEX; ++j) //填充邊關系 { for (int i = j + 1; i < MAX_VERTEX; ++i) { printf("若元素%c和%c有邊,則輸入權值,否則輸入無效值%d\t", pGraph->vertexArr[j], pGraph->vertexArr[i], INFINITY); scanf("%d", &(pGraph->edgeArr[j][i])); pGraph->edgeArr[i][j] = pGraph->edgeArr[j][i]; //對稱 } } } void UArrayNet_show(UArrayNet *pGraph) { printf("\n\n頂點元素如下\n"); for (int i = 0; i < MAX_VERTEX; ++i) { printf("%-5c", pGraph->vertexArr[i]); } printf("\n\n"); puts("邊矩陣如下"); printf("%-2c", ' '); for (int i = 0; i<MAX_VERTEX; ++i) printf("%-5c", pGraph->vertexArr[i]); putchar('\n'); for (int j = 0; j <MAX_VERTEX; ++j) { printf("%-2c", pGraph->vertexArr[j]); for (int i = 0; i < MAX_VERTEX; ++i) { if(pGraph->edgeArr[i][j]==INFINITY) { printf("%-5c", '#'); } else printf("%-5d", pGraph->edgeArr[i][j]); } putchar('\n'); } }
有向網的鄰接矩陣存儲
有向網和有向圖的原理是一樣,這里不再擴充。
鄰接表儲存實現
鄰接矩陣存儲很好理解,但是,有時候太浪費空間了,特別是對於頂點數多,但是關聯關系少的圖。
舉個極端的栗子。
下圖中,5個頂點都是孤立的,然而為了存儲邊的信息,要分配一個5X5的矩陣。本來一條邊都沒有,沒必要用存儲空間來存儲邊,但還要分配額外的空間。
無向圖與鄰接表
用鄰接表則可以避免這種浪費。鄰接表用動態表結構存儲邊,更加靈活:有一個邊就分配一個空間去存儲,沒有就不分配。
我們仍然用鄰接矩陣中那個圖的例子來說明鄰接表的構建思想。
鄰接表只需要一個數組,但是這個數組有點復雜,需要解剖一下。如下圖。
可以發現,用鄰接表存儲時,圖中的每一個節點 不僅要存儲本頂點的數據data,好要存儲一個表,這個表存儲了此頂點的所有臨接點的索引。2點確定一線,那么就能存儲此節點相關的所有的邊了。
代碼:
注:代碼中,我並沒有手動編寫鏈表,而是用了C++標准庫中的vector模板,它是一個線性表,具體說是在堆中的動態數組。push_back()就是向表中添加新的元素。
這樣做的目的是為了讓代碼更簡潔,思路更清晰。我不想把書抄一遍,這樣我寫這篇隨便也就失去了意義。
而且,書上的很多例子中,將多種數據結構的構建混雜在一起,根本不知道降低耦合度,如果我以前學過了鏈表,而且也自己實現了,為什么不直接拿來用呢?吐槽完畢。

#include<stdio.h> #include<vector> #define MAX_VERTEX 4 using std::vector; //使用C++標准庫中的vector來作為list,它實質就是一個順序表 typedef char DataType; typedef struct node { DataType data; //頂點數據 vector<int> indexList; //存儲頂點鄰接點索引的 表 }Node; typedef Node* UListGraph; /********************************/ void UListGraph_init(UListGraph*pGraph); void UListGraph_create(UListGraph graph); void UListGraph_show(UListGraph graph); void UListGraph_destroy(UListGraph*pGraph); int main(void) { UListGraph g; UListGraph_init(&g); UListGraph_create(g); UListGraph_show(g); UListGraph_destroy(&g); return 0; } void UListGraph_init(UListGraph*pGraph) { (*pGraph) = new Node[MAX_VERTEX]; } void UListGraph_create(UListGraph graph) { for (int i = 0; i < MAX_VERTEX; ++i) //填充頂點數組 { printf("輸入第%d個頂點值\n",i+1); scanf(" %c", &(graph[i].data)); } for(int i=0;i<MAX_VERTEX;++i) for(int j=i+1;j<MAX_VERTEX;++j) { printf("如果元素%c和%c之間有邊,請輸入1,否則輸入0",graph[i].data,graph[j].data); int opt; scanf("%d",&opt); if(opt==1) { graph[i].indexList.push_back(j); graph[j].indexList.push_back(i); } } } void UListGraph_show(UListGraph graph) { printf("頂點元素如下:\n\n"); for(int i=0;i<MAX_VERTEX;i++) { printf("%c\t",graph[i].data); } printf("\n\n"); for(int i=0;i<MAX_VERTEX;i++) { printf("元素%c的鄰接點 :",graph[i].data); Node tnode = graph[i]; for(int k=0;k<tnode.indexList.size();++k) { printf("%c\t",graph[tnode.indexList[k]].data); } putchar('\n'); } } void UListGraph_destroy(UListGraph*pGraph) { delete (*pGraph); *pGraph = NULL; }
無向網的鄰接表
無向網的鄰接表,因為網是帶權值的,所以,還要為邊附加權值信息。
確切的說,就是IndexList表 中存儲的是一個個的結構體,這個結構體不僅保存鄰接點的索引,還用一個成員保存了本頂點和他的鄰接點之間邊的權值。
typedef struct edge { int otherVerTex; //本頂點和索引為otherVertex的頂點形成一條邊 WeightType weight; //邊的權值 }Edge //使用方法如下:用V0和V1來舉例 // Edge edge = {1 , 12}; // V0.IndexList.push_back(edge ); //
有向圖和十字鏈表
鄰接表對於無向圖來說很適用,但是對於有向圖來說就不適用了,因為鄰接表中,每一個節點的IndexList只能存儲一種狀態的弧,出弧或者入弧(逆鄰接表),那怎么將一個頂點的出入弧都存儲起來呢?
那就是將鄰接表和逆鄰接表都用起來,也就是一個節點需要存儲:①data,②inArcList,入弧記錄表,③outArcList,出弧記錄表
可以看出,十字表比鄰接表更復雜,因為十字表要存儲更多的數據。
代碼:

#include<stdio.h> #include<vector> using std::vector; #define MAX_VERTEX 4 typedef char DataType; typedef struct arc{ int headVertex; //此條弧的頭尾結點的index int tailVertex; //此條弧的頭尾結點的index //WeightType weight; //此弧的附加信息,比如權重,這里是有向圖,不是網,所以不需要weight }Arc; typedef struct node{ DataType data; //頂點數據域 vector<Arc> outArc; //此頂點的所有 出度 弧 表 vector<Arc> inArc; //此頂點的所有 入度 弧 表 }Node; typedef Node* Graph; void Graph_init(Graph* pG); void Graph_create(Graph g); void Graph_show(Graph g); void Graph_destroy(Graph*pg); int main(void) { Graph g; Graph_init(&g); Graph_create(g); Graph_show(g); return 0; } void Graph_init(Graph* pG) { (*pG) = new Node[MAX_VERTEX]; if(*pG == NULL) { exit(-1); } } void Graph_create(Graph g) { int opt; for (int i = 0; i < MAX_VERTEX; ++i) //填充頂點數組 { printf("輸入第%d個頂點值\n",i+1); scanf(" %c", &(g[i].data)); } for(int j=0;j<MAX_VERTEX;++j) for(int i=j+1;i<MAX_VERTEX;++i) { printf("若元素%c有指向%c的弧,則輸入1,否則輸入0\t",g[j].data,g[i].data); scanf("%d",&opt); if(opt==1) { Arc* parc = new Arc; parc->headVertex = i; parc->tailVertex = j; g[j].outArc.push_back(*parc); g[i].inArc.push_back(*parc); } printf("若元素%c有指向%c的弧,則輸入1,否則輸入0\t",g[i].data,g[j].data); scanf("%d",&opt); if(opt==1) { Arc* parc = new Arc; parc->headVertex = j; parc->tailVertex = i; g[i].outArc.push_back(*parc); g[j].inArc.push_back(*parc); } } } void Graph_show(Graph g) { for (int j = 0; j <MAX_VERTEX; ++j) { printf("頂點數據%c\n",g[j].data); printf("發射:"); for(int i=0;i<g[j].outArc.size();++i) { printf("%c\t",g[g[j].outArc[i].headVertex].data); } putchar('\n'); printf("接收:"); for(int i=0;i<g[j].inArc.size();++i) { printf("%c\t",g[g[j].inArc[i].tailVertex].data); } printf("\n\n"); } } void Graph_destroy(Graph*pg) { //once you use new or malloc in your C/C++ project,you must remember //to delete or free the memory you allocated //I have no time to complete this code //you can try yourself //:) }
鄰接多重表
沒時間了~以后補!
下一篇:圖的遍歷