數據結構之圖(一)圖的存儲結構


  圖的存儲結構相對於線性表和樹來說更為復雜,因為圖中的頂點具有相對概念,沒有固定的位置。那我們怎么存儲圖的數據結構呢?我們知道,圖是由(VE)來表示的,對於無向圖來說,其中 = (v0v1, ... , vn),= { (vi,vj) (0 <=  i, j <=  n且i 不等於j)},對於有向圖= { < vi,vj > (0 <=  i, j <=  n且i 不等於j)}V是頂點的集合,E是邊的集合。所以我們只要把頂點和邊的集合儲存起來,那么該圖的所有數據就能夠存儲起來了。

  本文只介紹兩種比較常見和重要的圖的存儲結構:鄰接矩陣和鄰接表。

  鄰接矩陣,顧名思義,是一個矩陣,一個存儲着邊的信息的矩陣,而頂點則用矩陣的下標表示。對於一個鄰接矩陣M,如果M(i,j)=1,則說明頂點i和頂點j之間存在一條邊,對於無向圖來說,M (j ,i) = M (i, j),所以其鄰接矩陣是一個對稱矩陣;對於有向圖來說,則未必是一個對稱矩陣。鄰接矩陣的對角線元素都為0。下圖是一個無向圖和對應的臨街矩陣:

  圖1:無向圖

圖2:鄰接矩陣

需要注意的是,當邊上有權值的時候,稱之為網圖,則鄰接矩陣中的元素不再僅是0和1了,鄰接矩陣M中的元素定義為:

  以下用C語言創建一個無向圖的鄰接矩陣:

  頭文件是:GraphStruct.h

 1 /*GraphStruct.h
 2 * 圖的鄰接矩陣存儲方式,結構由頂點數量、邊數量、頂點集合和邊集合組成。
 3 * 其中頂點集合一維數組,根據頂點的數量動態分配數組大小。
 4 * 邊集合是二維數組,根據頂點的數量來動態分配數組大小,對於無向圖來說,該鄰接矩陣是對稱矩陣。
 5 * 鄰接矩陣比較適用於稠密圖
 6 */
 7 typedef char vertexType;
 8 typedef int edgeType;
 9 typedef struct GraphMatrix{
10 
11     int vertexNumber;            // 頂點數量
12     int edgeNumber;                // 邊的數量
13     vertexType *vertex;            // 頂點集合,動態數組
14     edgeType** edge;            // 邊集合,二維動態數組
15 
16 } GraphMatrix;
17 
18 void GraphMatrix_create(GraphMatrix *g);

  該頭文件包含了鄰接矩陣的數據結構,結構體的成員變量包括頂點數量、邊的數量、頂點集合和邊的集合。為了節省空間,將頂點集合和邊集設為動態數組,根據頂點數量來分配空間。

  實現文件是:GraphStruct.c

 1 #include <stdio.h>
 2 #include <malloc.h>
 3 #include"GraphStruct.h"
 4 
 5 void GraphMatrix_create(GraphMatrix *g){
 6 
 7     printf("請分別輸入圖的頂點數量和邊的數量,用空格隔開:");
 8     scanf("%d %d", &g->vertexNumber, &g->edgeNumber);  //將頂點數量和邊的數量存儲在結構體g中相應的變量
 9     g->vertex = (vertexType*)malloc(g->vertexNumber * sizeof(vertexType)); //為動態數組申請空間
10     //二維動態數組申請空間
11     g->edge = (edgeType**)malloc(g->vertexNumber * sizeof(edgeType));        
12     for (int i = 0; i < g->vertexNumber; i++){
13         g->edge[i] = (edgeType*)malloc(g->vertexNumber * sizeof(edgeType));
14     }
15     //初始化鄰接矩陣的所有元素
16     for (int i = 0; i < g->vertexNumber; i++){
17         for (int j = 0; j < g->vertexNumber; j++)
18             g->edge[i][j] = 0;
19     }
20     
21     //輸入圖的信息
22     for (int k = 0; k < g->edgeNumber; k++){
23 
24         int i, j;
25         printf("請輸入邊(vi,vj)的下標, i和j,用空格隔開:");
26         scanf("%d%d", &i, &j);
27         g->edge[i][j] = 1;    //(i,j)和(j,i)指的是一條邊
28         g->edge[j][i] = 1;
29     }
30     
31     //輸出圖的信息
32     printf("Your graph matrix is :\n");
33     for (int i = 0; i < g->vertexNumber; i++){
34         for (int j = 0; j < g->vertexNumber; j++){
35             printf("%d\t", g->edge[i][j]);
36         }
37         printf("\n");
38     }
39 

  測試文件為:main.c

 

 1 #include<stddef.h>
 2 #include "GraphStruct.h"
 3 
 4 int main(){
 5     
 6     GraphMatrix *gm;
 7     gm = (GraphMatrix *)malloc(sizeof(GraphMatrix));
 8     GraphMatrix_create(gm);
 9    10 
11     return 0;
12 }

 

 

 

  運行結果為:

 

圖3 運行結果

  對於有向圖,網圖的代碼,只要將上面的代碼稍微修改就行了,本文末尾附上代碼的下載地址。

  對於頂點數很多但是邊數很少的圖來說,用鄰接矩陣顯得略為“奢侈”,因為矩陣元素為1的很少,即其中的有用信息很少,但卻占了很大的空間。所以下面我們來看看鄰接表,以圖1的無向圖為例,我們先不講理論的知識,先把圖1的鄰接表畫出來,如圖4。

 

 

圖4 鄰接表 

  作為頂點0,它的鄰接頂點有1,3,4,形成的邊有(0,1),(0,3)和(0,4),所以頂點0將其指出來了;對於頂點1,它的鄰接頂點有0,2,4,所以頂點1將其指出來了,以此類推。他們的邊沒有先后順序之分。對於邊(i,j),鄰接表如下:

圖5 (i,j)的鄰接表

  左邊的節點稱為頂點節點,其結構體包含頂點元素和指向第一條邊的指針;右邊的為邊節點,結構體包含邊的頂點對應的下標,和指向下一個邊節點的指針。對於有權值的網圖,只需要在邊節點增加一個權值的成員變量即可。

  實現鄰接表存儲結構的代碼如下:

  頭文件是:GraphStruct.h

 1 /*
 2  *圖的另一種存儲結構是鄰接表
 3 
 4 */
 5 typedef struct ListEdgeNode{
 6     int index;                    // 邊的下標
 7     struct ListEdgeNode *next;            // 指向下一個節點的指針
 8 }ListEdgeNode;
 9 
10 typedef struct ListVertexNode {
11     vertexType vertex;            // 頂點
12      ListEdgeNode *fistEdge;        // 指向第一條邊
13 } ListVertexNode;
14 
15 typedef struct GraphList{
16     int vertexNumber;            // 頂點的數量
17     int edgeNumber;                // 邊的數量
18     ListVertexNode *vertex;        // 頂點集合,動態數組
19 }GraphList;
20 
21 void GraphList_create(GraphList *g); 

  該文件定義的結構體如上所述,GraphList是鏈接表的結構體,包含了頂點數,邊數和頂點集,其中頂點集根據頂點個數分配內存空間。

  實現文件是:GraphStruct.c

void GraphList_create(GraphList *g){
    printf("請分別輸入圖的頂點數量和邊的數量,用空格隔開:");
    scanf("%d %d", &g->vertexNumber, &g->edgeNumber);        //將頂點數量和邊的數量存儲在結構體g中相應的變量
    //為動態數組申請空間
    g->vertex = (ListVertexNode*)malloc(g->vertexNumber * sizeof(ListVertexNode));
    //初始化頂點指的第一條邊
    for (int i = 0; i < g->edgeNumber; i++){
        g->vertex[i].fistEdge = NULL;
    }

    //輸入圖的信息
    ListEdgeNode *listEdgeNode;
    for (int k = 0; k < g->edgeNumber; k++){
        int i, j;
        printf("請輸入邊(vi,vj)的下標, i和j,用空格隔開:");
        scanf("%d%d", &i, &j);
        //始終將插入的節點放在頂點所指的地一條邊
        listEdgeNode = (ListEdgeNode *)malloc(sizeof(ListEdgeNode));
        listEdgeNode->index = j;
        listEdgeNode->next = g->vertex[i].fistEdge;
        g->vertex[i].fistEdge = listEdgeNode;

        listEdgeNode = (ListEdgeNode*)malloc(sizeof(ListEdgeNode));
        listEdgeNode->index = i;
        listEdgeNode->next = g->vertex[j].fistEdge;
        g->vertex[j].fistEdge = listEdgeNode;

    }

    //輸出圖的信息
    ListEdgeNode * len = NULL;
    for (int i = 0; i < g->vertexNumber; i++){
        
        if (g->vertex[i].fistEdge != NULL)
            len = g->vertex[i].fistEdge;
        while (len!= NULL){
            printf("%d --- %d\t", i,len->index);
            len = len->next;
        }
        printf("\n");
    }
}

  測試文件是:main.c

 1 #include<stddef.h>
 2 #include "GraphStruct.h"
 3 
 4 int main(){
 5     
 6     
 7     GraphList *gl;
 8     gl = (GraphList*)malloc(sizeof(GraphList));
 9     GraphList_create(gl);
10     return 0;
11 }

  運行結果為:

 

  

  

  鄰接矩陣適合於點少邊多的圖,而對於邊少的圖,可以考慮用鄰接表。但是對於有向圖,鄰接表的表示有多種,有些更為復雜,在這里不一一闡述,有興趣的可以跟本人交流。

   源代碼下載:http://pan.baidu.com/s/1hqeYahQ

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM