數據結構之圖(存儲結構、遍歷)

 高質量學習資源免費獲取,專一但不限於【Linux】【C/C++/Qt】【FPGA】【數據結構與算法】, 根據多年技術經驗純【原創】,純【乾貨】,分享【技術心得】,力求【授人以魚,更授人以漁】。html

  新學期開始了,開始專心於技術上了,上學期的寒假老是那麼短暫,飄飄乎就這樣逝去,今天補補上學期還沒學完的數據結構---圖,但願能和你們一塊兒探討,共同進步~算法

定義:數組

  圖是由頂點集合及頂點間的關係集合組成的一種數據結構。數據結構

  

圖的存儲結構:函數

1.1 鄰接矩陣學習

    圖的鄰接矩陣存儲方式是用兩個數組來表示圖。一個一維數組存儲圖中頂點信息,一個二維數組(鄰接矩陣)存儲圖中的邊或弧的信息。ui

    設圖G有n個頂點,則鄰接矩陣是一個n*n的方陣,定義爲:this

    

    看一個實例,下圖左就是一個無向圖。spa

    

    從上面能夠看出,無向圖的邊數組是一個對稱矩陣。所謂對稱矩陣就是n階矩陣的元知足aij = aji。即從矩陣的左上角到右下角的主對角線爲軸,右上角的元和左下角相對應的元全都是相等的。設計

    從這個矩陣中,很容易知道圖中的信息。

    (1)要判斷任意兩頂點是否有邊無邊就很容易了;

    (2)要知道某個頂點的度,其實就是這個頂點vi在鄰接矩陣中第i行或(第i列)的元素之和;

    (3)求頂點vi的全部鄰接點就是將矩陣中第i行元素掃描一遍,arc[i][j]爲1就是鄰接點;

    而有向圖講究入度和出度,頂點vi的入度爲1,正好是第i列各數之和。頂點vi的出度爲2,即第i行的各數之和。

    若圖G是網圖,有n個頂點,則鄰接矩陣是一個n*n的方陣,定義爲:

    

    這裏的wij表示(vi,vj)上的權值。無窮大表示一個計算機容許的、大於全部邊上權值的值,也就是一個不可能的極限值。下面左圖就是一個有向網圖,右圖就是它的鄰接矩陣。

    

    那麼鄰接矩陣是如何實現圖的建立的呢?代碼以下。

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <curses.h>
  4  
  5 typedef char VertexType;                //頂點類型應由用戶定義
  6 typedef int EdgeType;                   //邊上的權值類型應由用戶定義
  7  
  8 #define MAXVEX  100             //最大頂點數,應由用戶定義
  9 #define INFINITY    65535               //用65535來表明無窮大
 10 #define DEBUG
 11  
 12 typedef struct
 13 {
 14     VertexType vexs[MAXVEX];            //頂點表
 15     EdgeType   arc[MAXVEX][MAXVEX];         //鄰接矩陣,可看做邊
 16     int numVertexes, numEdges;      //圖中當前的頂點數和邊數
 17 }Graph;
 18  
 19 //定位
 20 int locates(Graph *g, char ch)
 21 {
 22     int i = 0;
 23     for(i = 0; i < g->numVertexes; i++)
 24     {
 25         if(g->vexs[i] == ch)
 26         {
 27             break;
 28         }
 29     }
 30     if(i >= g->numVertexes)
 31     {
 32         return -1;
 33     }
 34      
 35     return i;
 36 }
 37  
 38 //創建一個無向網圖的鄰接矩陣表示
 39 void CreateGraph(Graph *g)
 40 {
 41     int i, j, k, w;
 42     printf("輸入頂點數和邊數:\n");
 43     scanf("%d,%d", &(g->numVertexes), &(g->numEdges));
 44      
 45     #ifdef DEBUG
 46     printf("%d %d\n", g->numVertexes, g->numEdges);
 47     #endif
 48  
 49     for(i = 0; i < g->numVertexes; i++)
 50     {
 51         g->vexs[i] = getchar();
 52         while(g->vexs[i] == '\n')
 53         {
 54             g->vexs[i] = getchar();
 55         }
 56     }
 57      
 58     #ifdef DEBUG
 59     for(i = 0; i < g->numVertexes; i++)
 60     {
 61         printf("%c ", g->vexs[i]);
 62     }
 63     printf("\n");
 64     #endif
 65  
 66  
 67     for(i = 0; i < g->numEdges; i++)
 68     {
 69         for(j = 0; j < g->numEdges; j++)
 70         {
 71             g->arc[i][j] = INFINITY; //鄰接矩陣初始化
 72         }
 73     }
 74     for(k = 0; k < g->numEdges; k++)
 75     {
 76         char p, q;
 77         printf("輸入邊(vi,vj)上的下標i,下標j和權值:\n");
 78          
 79         p = getchar();
 80         while(p == '\n')
 81         {
 82             p = getchar();
 83         }
 84         q = getchar();
 85         while(q == '\n')
 86         {
 87             q = getchar();
 88         }
 89         scanf("%d", &w);    
 90          
 91         int m = -1;
 92         int n = -1;
 93         m = locates(g, p);
 94         n = locates(g, q);
 95         if(n == -1 || m == -1)
 96         {
 97             fprintf(stderr, "there is no this vertex.\n");
 98             return;
 99         }
100         //getchar();
101         g->arc[m][n] = w;
102         g->arc[n][m] = g->arc[m][n];  //由於是無向圖,矩陣對稱
103     }
104 }
105  
106 //打印圖
107 void printGraph(Graph g)
108 {
109     int i, j;
110     for(i = 0; i < g.numVertexes; i++)
111     {
112         for(j = 0; j < g.numVertexes; j++)
113         {
114             printf("%d  ", g.arc[i][j]);
115         }
116         printf("\n");
117     }
118 }
119  
120 int main(int argc, char **argv)
121 {
122     Graph g;
123      
124     //鄰接矩陣建立圖
125     CreateGraph(&g);
126     printGraph(g);
127     return 0;
128 }

     從代碼中能夠獲得,n個頂點和e條邊的無向網圖的建立,時間複雜度爲O(n + n2 + e),其中對鄰接矩陣Grc的初始化耗費了O(n2)的時間。

 

1.2 鄰接表

    鄰接矩陣是不錯的一種圖存儲結構,可是,對於邊數相對頂點較少的圖,這種結構存在對存儲空間的極大浪費。所以,找到一種數組與鏈表相結合的存儲方法稱爲鄰接表。

    鄰接表的處理方法是這樣的:

    (1)圖中頂點用一個一維數組存儲,固然,頂點也能夠用單鏈表來存儲,不過,數組能夠較容易的讀取頂點的信息,更加方便。

    (2)圖中每一個頂點vi的全部鄰接點構成一個線性表,因爲鄰接點的個數不定,因此,用單鏈表存儲,無向圖稱爲頂點vi的邊表,有向圖則稱爲頂點vi做爲弧尾的出邊表。

    例如,下圖就是一個無向圖的鄰接表的結構。

    

    從圖中能夠看出,頂點表的各個結點由data和firstedge兩個域表示,data是數據域,存儲頂點的信息,firstedge是指針域,指向邊表的第一個結點,即此頂點的第一個鄰接點。邊表結點由adjvex和next兩個域組成。adjvex是鄰接點域,存儲某頂點的鄰接點在頂點表中的下標,next則存儲指向邊表中下一個結點的指針。

    對於帶權值的網圖,能夠在邊表結點定義中再增長一個weight的數據域,存儲權值信息便可。以下圖所示。

    

    對於鄰接表結構,圖的創建代碼以下。

  1 /* 鄰接表表示的圖結構 */
  2 #include <stdio.h>
  3 #include<stdlib.h>
  4  
  5 #define DEBUG
  6 #define MAXVEX 1000         //最大頂點數
  7 typedef char VertexType;        //頂點類型應由用戶定義
  8 typedef int EdgeType;           //邊上的權值類型應由用戶定義
  9  
 10 typedef struct EdgeNode         //邊表結點
 11 {
 12     int adjvex;         //鄰接點域,存儲該頂點對應的下標
 13     EdgeType weigth;        //用於存儲權值,對於非網圖能夠不須要
 14     struct EdgeNode *next;      //鏈域,指向下一個鄰接點
 15 }EdgeNode;
 16  
 17 typedef struct VertexNode       //頂點表結構
 18 {
 19     VertexType data;        //頂點域,存儲頂點信息
 20     EdgeNode *firstedge;        //邊表頭指針
 21 }VertexNode, AdjList[MAXVEX];
 22  
 23 typedef struct
 24 {
 25     AdjList adjList;
 26     int numVertexes, numEdges;  //圖中當前頂點數和邊數
 27 }GraphList;
 28  
 29 int Locate(GraphList *g, char ch)
 30 {
 31     int i;
 32     for(i = 0; i < MAXVEX; i++)
 33     {
 34         if(ch == g->adjList[i].data)
 35         {
 36             break;
 37         }
 38     }
 39     if(i >= MAXVEX)
 40     {
 41         fprintf(stderr,"there is no vertex.\n");
 42         return -1;
 43     }
 44     return i;
 45 }
 46  
 47 //創建圖的鄰接表結構
 48 void CreateGraph(GraphList *g)
 49 {
 50     int i, j, k;
 51     EdgeNode *e;
 52     EdgeNode *f;
 53     printf("輸入頂點數和邊數:\n");
 54     scanf("%d,%d", &g->numVertexes, &g->numEdges);
 55      
 56     #ifdef DEBUG
 57     printf("%d,%d\n", g->numVertexes, g->numEdges);
 58     #endif
 59      
 60     for(i = 0; i < g->numVertexes; i++)
 61     {
 62         printf("請輸入頂點%d:\n", i);
 63         g->adjList[i].data = getchar();          //輸入頂點信息
 64         g->adjList[i].firstedge = NULL;          //將邊表置爲空表
 65         while(g->adjList[i].data == '\n')
 66         {
 67             g->adjList[i].data = getchar();
 68         }
 69     }
 70     //創建邊表
 71     for(k = 0; k < g->numEdges; k++)
 72     {
 73         printf("輸入邊(vi,vj)上的頂點序號:\n");
 74         char p, q;
 75         p = getchar();
 76         while(p == '\n')
 77         {
 78             p = getchar();
 79         }
 80         q = getchar();
 81         while(q == '\n')
 82         {
 83             q = getchar();
 84         }
 85         int m, n;
 86         m = Locate(g, p);
 87         n = Locate(g, q);
 88         if(m == -1 || n == -1)
 89         {
 90             return;
 91         }
 92         #ifdef DEBUG
 93         printf("p = %c\n", p);
 94         printf("q = %c\n", q);
 95         printf("m = %d\n", m);
 96         printf("n = %d\n", n);
 97         #endif
 98      
 99         //向內存申請空間,生成邊表結點
100         e = (EdgeNode *)malloc(sizeof(EdgeNode));
101         if(e == NULL)
102         {
103             fprintf(stderr, "malloc() error.\n");
104             return;
105         }
106         //鄰接序號爲j
107         e->adjvex = n;
108         //將e指針指向當前頂點指向的結構
109         e->next = g->adjList[m].firstedge;
110         //將當前頂點的指針指向e
111         g->adjList[m].firstedge = e;
112          
113         f = (EdgeNode *)malloc(sizeof(EdgeNode));
114         if(f == NULL)
115         {
116             fprintf(stderr, "malloc() error.\n");
117             return;
118         }
119         f->adjvex = m;
120         f->next = g->adjList[n].firstedge;
121         g->adjList[n].firstedge = f;
122     }
123 }
124  
125  
126 void printGraph(GraphList *g)
127 {
128     int i = 0;
129     #ifdef DEBUG
130     printf("printGraph() start.\n");
131     #endif
132      
133     while(g->adjList[i].firstedge != NULL && i < MAXVEX)
134     {
135         printf("頂點:%c  ", g->adjList[i].data);
136         EdgeNode *e = NULL;
137         e = g->adjList[i].firstedge;
138         while(e != NULL)
139         {
140             printf("%d  ", e->adjvex);
141             e = e->next;
142         }
143         i++;
144         printf("\n");
145     }
146 }
147  
148 int main(int argc, char **argv)
149 {
150     GraphList g;
151     CreateGraph(&g);
152     printGraph(&g);
153     return 0;
154 }

     對於無向圖,一條邊對應都是兩個頂點,因此,在循環中,一次就針對i和j分佈進行插入。

     本算法的時間複雜度,對於n個頂點e條邊來講,很容易得出是O(n+e)。

1.3 十字鏈表

    對於有向圖來講,鄰接表是有缺陷的。關心了出度問題,想了解入度就必需要遍歷整個圖才知道,反之,逆鄰接表解決了入度卻不瞭解出度狀況。下面介紹的這種有向圖的存儲方法:十字鏈表,就是把鄰接表和逆鄰接表結合起來的。

    從新定義頂點表結點結構,以下所示。

    

    其中firstin表示入邊表頭指針,指向該頂點的入邊表中第一個結點,firstout表示出邊表頭指針,指向該頂點的出邊表中的第一個結點。

    從新定義邊表結構,以下所示。

    

    其中,tailvex是指弧起點在頂點表的下表,headvex是指弧終點在頂點表的下標,headlink是指入邊表指針域,指向終點相同的下一條邊,taillink是指邊表指針域,指向起點相同的下一條邊。若是是網,還能夠增長一個weight域來存儲權值。

    好比下圖,頂點依然是存入一個一維數組,實線箭頭指針的圖示徹底與鄰接表相同。就以頂點v0來講,firstout指向的是出邊表中的第一個結點v3。因此,v0邊表結點hearvex = 3,而tailvex其實就是當前頂點v0的下標0,因爲v0只有一個出邊頂點,全部headlink和taillink都是空的。

    

    重點須要解釋虛線箭頭的含義。它其實就是此圖的逆鄰接表的表示。對於v0來講,它有兩個頂點v1和v2的入邊。所以的firstin指向頂點v1的邊表結點中headvex爲0的結點,如上圖圓圈1。接着由入邊結點的headlink指向下一個入邊頂點v2,如上圖圓圈2。對於頂點v1,它有一個入邊頂點v2,因此它的firstin指向頂點v2的邊表結點中headvex爲1的結點,如上圖圓圈3。

    十字鏈表的好處就是由於把鄰接表和逆鄰接表整合在一塊兒,這樣既容易找到以v爲尾的弧,也容易找到以v爲頭的弧,於是比較容易求得頂點的出度和入度。

    並且除告終構複雜一點外,其實建立圖算法的時間複雜度是和鄰接表相同的,所以,在有向圖應用中,十字鏈表是很是好的數據結構模型。

    這裏就介紹以上三種存儲結構,除了第三種存儲結構外,其餘的兩種存儲結構比較簡單。

 

2、圖的遍歷

    圖的遍歷和樹的遍歷相似,但願從圖中某一頂點出發訪遍圖中其他頂點,且使每個頂點僅被訪問一次,這一過程就叫圖的遍歷。

    對於圖的遍從來說,如何避免因迴路陷入死循環,就須要科學地設計遍歷方案,經過有兩種遍歷次序方案:深度優先遍歷和廣度優先遍歷。

2.1 深度優先遍歷

    深度優先遍歷,也有稱爲深度優先搜索,簡稱DFS。其實,就像是一棵樹的前序遍歷。

    它從圖中某個結點v出發,訪問此頂點,而後從v的未被訪問的鄰接點出發深度優先遍歷圖,直至圖中全部和v有路徑相通的頂點都被訪問到。若圖中尚有頂點未被訪問,則另選圖中一個不曾被訪問的頂點做起始點,重複上述過程,直至圖中的全部頂點都被訪問到爲止。

    咱們用鄰接矩陣的方式,則代碼以下所示。

 1 #define MAXVEX  100     //最大頂點數
 2 typedef int Boolean;            //Boolean 是布爾類型,其值是TRUE 或FALSE
 3 Boolean visited[MAXVEX];        //訪問標誌數組
 4 #define TRUE 1
 5 #define FALSE 0
 6  
 7 //鄰接矩陣的深度優先遞歸算法
 8 void DFS(Graph g, int i)
 9 {
10     int j;
11     visited[i] = TRUE;
12     printf("%c ", g.vexs[i]);                           //打印頂點,也能夠其餘操做
13     for(j = 0; j < g.numVertexes; j++)
14     {
15         if(g.arc[i][j] == 1 && !visited[j])
16         {
17             DFS(g, j);                  //對爲訪問的鄰接頂點遞歸調用
18         }
19     }
20 }
21  
22 //鄰接矩陣的深度遍歷操做
23 void DFSTraverse(Graph g)
24 {
25     int i;
26     for(i = 0; i < g.numVertexes; i++)
27     {
28         visited[i] = FALSE;         //初始化全部頂點狀態都是未訪問過狀態
29     }
30     for(i = 0; i < g.numVertexes; i++)
31     {
32         if(!visited[i])             //對未訪問的頂點調用DFS,如果連通圖,只會執行一次
33         {
34             DFS(g,i);
35         }
36     }
37 }

    若是使用的是鄰接表存儲結構,其DFSTraverse函數的代碼幾乎是相同的,只是在遞歸函數中由於將數組換成了鏈表而有不一樣,代碼以下。

 1 //鄰接表的深度遞歸算法
 2 void DFS(GraphList g, int i)
 3 {
 4     EdgeNode *p;
 5     visited[i] = TRUE;
 6     printf("%c ", g->adjList[i].data);   //打印頂點,也能夠其餘操做
 7     p = g->adjList[i].firstedge;
 8     while(p)
 9     {
10         if(!visited[p->adjvex])
11         {
12             DFS(g, p->adjvex);           //對訪問的鄰接頂點遞歸調用
13         }
14         p = p->next;
15     }
16 }
17  
18 //鄰接表的深度遍歷操做
19 void DFSTraverse(GraphList g)
20 {
21     int i;
22     for(i = 0; i < g.numVertexes; i++)
23     {
24         visited[i] = FALSE;
25     }
26     for(i = 0; i < g.numVertexes; i++)
27     {
28         if(!visited[i])
29         {
30             DFS(g, i);
31         }
32     }
33 }

     對比兩個不一樣的存儲結構的深度優先遍歷算法,對於n個頂點e條邊的圖來講,鄰接矩陣因爲是二維數組,要查找某個頂點的鄰接點須要訪問矩陣中的全部元素,由於須要O(n2)的時間。而鄰接表作存儲結構時,找鄰接點所需的時間取決於頂點和邊的數量,因此是O(n+e)。顯然對於點多邊少的稀疏圖來講,鄰接表結構使得算法在時間效率上大大提升。

 

2.2 廣度優先遍歷

    廣度優先遍歷,又稱爲廣度優先搜索,簡稱BFS。圖的廣度優先遍歷就相似於樹的層序遍歷了。

    鄰接矩陣作存儲結構時,廣度優先搜索的代碼以下。

 1 //鄰接矩陣的廣度遍歷算法
 2 void BFSTraverse(Graph g)
 3 {
 4     int i, j;
 5     Queue q;
 6     for(i = 0; i < g.numVertexes; i++)
 7     {
 8         visited[i] = FALSE;
 9     }
10     InitQueue(&q);
11     for(i = 0; i < g.numVertexes; i++)//對每一個頂點作循環
12     {
13         if(!visited[i])               //如果未訪問過
14         {
15             visited[i] = TRUE;
16             printf("%c ", g.vexs[i]); //打印結點,也能夠其餘操做
17             EnQueue(&q, i);           //將此結點入隊列
18             while(!QueueEmpty(q))     //將隊中元素出隊列,賦值給
19             {
20                 int m;
21                 DeQueue(&q, &m);        
22                 for(j = 0; j < g.numVertexes; j++)
23                 {
24                     //判斷其餘頂點若與當前頂點存在邊且未訪問過
25                     if(g.arc[m][j] == 1 && !visited[j])
26                     {
27                         visited[j] = TRUE;
28                         printf("%c ", g.vexs[j]);
29                         EnQueue(&q, j);
30                     }
31                 }
32             }
33         }
34     }
35 } <span style="line-height:2;font-family:'sans serif', tahoma, verdana, helvetica;">  </span>

    對於鄰接表的廣度優先遍歷,代碼與鄰接矩陣差別不大, 代碼以下。

 1 //鄰接表的廣度遍歷算法
 2 void BFSTraverse(GraphList g)
 3 {
 4     int i;
 5     EdgeNode *p;
 6     Queue q;
 7     for(i = 0; i < g.numVertexes; i++)
 8     {
 9         visited[i] = FALSE;
10     }
11     InitQueue(&q);
12     for(i = 0; i < g.numVertexes; i++)
13     {
14         if(!visited[i])
15         {
16             visited[i] = TRUE;
17             printf("%c ", g.adjList[i].data);   //打印頂點,也能夠其餘操做
18             EnQueue(&q, i);
19             while(!QueueEmpty(q))
20             {
21                 int m;
22                 DeQueue(&q, &m);
23                 p = g.adjList[m].firstedge;     找到當前頂點邊錶鏈表頭指針
24                 while(p)
25                 {
26                     if(!visited[p->adjvex])
27                     {
28                         visited[p->adjvex] = TRUE;
29                         printf("%c ", g.adjList[p->adjvex].data);
30                         EnQueue(&q, p->adjvex);
31                     }
32                     p = p->next;
33                 }
34             }
35         }
36     }
37 }<span style="font-family:'sans serif', tahoma, verdana, helvetica;line-height:1.5;">   </span>

   對比圖的深度優先遍歷與廣度優先遍歷算法,會發現,它們在時間複雜度上是同樣的,不一樣之處僅僅在於對頂點的訪問順序不一樣。可見二者在全圖遍歷上是沒有優劣之分的,只是不一樣的狀況選擇不一樣的算法。

 

  版權全部,轉載請註明轉載地址:http://www.cnblogs.com/lihuidashen/p/4325282.html

相關文章
相關標籤/搜索