高質量學習資源免費獲取,專一但不限於【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