圖有兩種表示方法,鄰接矩陣和鄰接表,接下來我們講解鄰接矩陣和用c實現一個鄰接矩陣.
我們先看一個圖:
我們想將這樣一個圖信息存儲起來,我們有兩個必須存儲的數據,節點信息(a,b,c,d,e)和權值(3,5,4,1,6,7)和節點之間的關系.權值也就是路徑.
鄰接矩陣表示法,用兩個數組表示,一個一維數組和一個二維數組.
一維數組存儲節點信息,二維數組存儲節點之間的關系.
將上圖轉換成一個鄰接矩陣
接下來我們看一看鄰接矩陣是的結構體.
typedef struct matrix { node_type vertex[MAX_NUM];//節點信息 int arcs[MAX_NUM][MAX_NUM];//矩陣 int vertexs, brim;//節點數,邊數 } Graph;
我們將node_type設為char類型,方便明了.但在輸入字符后,我們通常回輸出空格或者回車符,在向字符變量輸入字符時,我們要將之前輸入的空格或者回車符從緩沖區中清楚,這也就是程序中getchar()的用處.
我們先構造一個鄰接矩陣
1)輸入節點數和節點信息,構造上圖的兩個數組.(將二維數組初始化MAX)
2)輸入某個節點的相鄰節點和權值(通過下標找到在二維數組中相應的位置,將權值輸入)
void g_create(Graph * graph) { int num; int i, j, k; char c; printf("輸入節點個數:"); scanf("%d", &graph->vertexs); getchar();//接受回車鍵 printf("輸入節點信息:"); for ( i = 0; i < graph->vertexs; i++ ) { scanf("%c", &graph->vertex[i]); getchar(); } for ( i = 0; i < graph->vertexs; i++ )//初始化矩陣 for ( j = 0; j < graph->vertexs; j++ ) graph->arcs[i][j] = MAX_VALUE; graph->brim = 0;//初始化邊數 // i 代表行數, j 是用來循環的, k 代表列數 for ( i = 0; i < graph->vertexs; i++ ) { printf("輸入與%c節點相鄰的節點與權值,輸入#號鍵結束\n", graph->vertex[i]); for ( j = 0; j < graph->vertexs; j++ ) { scanf("%c", &c); if ( c == '#' ) { getchar(); break; } scanf("%d", &num); for ( k = 0; k < graph->vertexs; k++ ) { if ( graph->vertex[k] != c ) continue; graph->arcs[i][k] = num; graph->brim++; } getchar(); } } graph->brim /= 2; }
"// i 代表行數, j 是用來循環的, k 代表列數"下面的代碼是將輸入的邊和權值的信息存儲在二維數組中.
i 代表的是在一維數組中的節點的位置,也是二維數組中行.(例如:與a相鄰的節點有兩個,b和c.這時i=0,代表以為數組中第一個位置,j用來循環輸入邊和權值,因為我們不知道每個節點都有多少個相鄰節點.當輸入'#'號時結束輸入.k是為了將輸入的相鄰節點與一維數組中的節點信息匹配,如果存在這個節點,這時將權值輸入到二維數組中,而 i 是二維數組的行, k 是二維數組的列.然后我們將邊數+1,因為a 的相鄰節點是 b ,而 b 的相鄰節點是 a, 這時我們對邊增加了兩次,所以在構造鄰接矩陣最后將邊數除以2.)
深度優先遍歷
深度優先遍歷是通過遞歸來實現,與二叉樹的遍歷有點像.
1)以某一節點開始,訪問該節點
2)以該節點開始,重復1.(這里需要定義一個節點數大小的bool類型數組,來記錄哪些節點訪問過了,哪些節點沒有訪問過.)
3)當某個節點的鄰接節點都訪問過了,回退到上一個節點,訪問上一個節點的其他相鄰節點.
4)重復3.直至返回開始節點.
這是連通圖的遍歷方法,對於非連通圖,我們只需循環調用遞歸,直至所有節點都訪問過.
實現算法:
1)先創建一個visited數組,初始化為false.
2)調用遍歷函數,實現遞歸.
3)當相鄰節點為false時,以該節點進行遞歸.
4)否則返回上一節點
1 //深度優先遍歷 2 static void dfs_graph(Graph * graph, bool visited[], const int i); 3 void g_depth_first_search(Graph * graph) 4 { 5 bool visited[graph->vertexs]; 6 int i; 7 for ( i = 0; i < graph->vertexs; i++ ) 8 visited[i] = false; 9 visited[0] = true; 10 dfs_graph(graph, visited, 0); 11 printf("\n"); 12 } 13 14 static void dfs_graph(Graph * graph, bool visited[], const int i) 15 { 16 int j; 17 printf("%c\t", graph->vertex[i]); 18 for ( j = 0; j < graph->vertexs; j++ )//依次檢查矩陣 19 { 20 if ( graph->arcs[i][j] != MAX_VALUE && !visited[j] )//i 代表矩陣的行, j 代表矩陣的列 21 { 22 visited[j] = true; 23 dfs_graph(graph, visited, j); 24 } 25 } 26 }
圖例:
廣度優先遍歷
廣度優先遍歷通過隊列實現.
1)從摸一節點開始,將該節點入隊,找到該節點的所有相鄰節點,將他們入隊.
2)將該節點出隊,再將隊頭節點的所有相鄰節點入隊.(這里也需要一個visited數組,已經入隊過的節點不再入隊.)
3)檢查隊列,對隊頭元素進行操作,出隊.
1 void g_breadth_first_search(Graph * graph) 2 { 3 Queue queue;//隊列存儲的是節點數組的下標(int) 4 bool visited[graph->vertexs]; 5 int i, pos; 6 7 q_init(&queue); 8 for ( i = 0; i < graph->vertexs; i++ ) 9 visited[i] = false; 10 11 visited[0] = true; 12 q_push(&queue, 0); 13 while ( !q_empty(&queue) ) 14 { 15 pos = q_front(&queue); 16 printf("%c\t", graph->vertex[pos]); 17 for ( i = 0; i < graph->vertexs; i++ )//把隊頭元素的鄰接點入隊 18 { 19 if ( !visited[i] && graph->arcs[pos][i] != MAX_VALUE ) 20 { 21 visited[i] = true; 22 q_push(&queue, i); 23 } 24 } 25 q_pop(&queue); 26 } 27 printf("\n"); 28 }
如果隊列有問題的同學,可以參考我之前寫的隊列,我把隊列文件直接拷貝過來了.
源碼
graph.c
#include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include <limits.h> #include "aqueue.h" #define MAX_VALUE INT_MAX #define MAX_NUM 100 typedef char node_type; typedef struct matrix { node_type vertex[MAX_NUM];//節點信息 int arcs[MAX_NUM][MAX_NUM];//矩陣 int vertexs, brim;//節點數,邊數 } Graph; void g_create(Graph * graph) { int num; int i, j, k; char c; printf("輸入節點個數:"); scanf("%d", &graph->vertexs); getchar();//接受回車鍵 printf("輸入節點信息:"); for ( i = 0; i < graph->vertexs; i++ ) { scanf("%c", &graph->vertex[i]); getchar(); } for ( i = 0; i < graph->vertexs; i++ )//初始化矩陣 for ( j = 0; j < graph->vertexs; j++ ) graph->arcs[i][j] = MAX_VALUE; graph->brim = 0;//初始化邊數 // i 代表行數, j 是用來循環的, k 代表列數 for ( i = 0; i < graph->vertexs; i++ ) { printf("輸入與%c節點相鄰的節點與權值,輸入#號鍵結束\n", graph->vertex[i]); for ( j = 0; j < graph->vertexs; j++ ) { scanf("%c", &c); if ( c == '#' ) { getchar(); break; } scanf("%d", &num); for ( k = 0; k < graph->vertexs; k++ ) { if ( graph->vertex[k] != c ) continue; graph->arcs[i][k] = num; graph->brim++; } getchar(); } } graph->brim /= 2; } void g_printMatrix(Graph * graph)//打印矩陣狀態 { int i, j; for ( i = 0; i < graph->vertexs; i++ ) { for ( j = 0; j < graph->vertexs; j++ ) { printf("%-10d ", graph->arcs[i][j]); } printf("\n"); } } //深度優先遍歷 static void dfs_graph(Graph * graph, bool visited[], const int i); void g_depth_first_search(Graph * graph) { bool visited[graph->vertexs]; int i; for ( i = 0; i < graph->vertexs; i++ ) visited[i] = false; visited[0] = true; dfs_graph(graph, visited, 0); printf("\n"); } static void dfs_graph(Graph * graph, bool visited[], const int i) { int j; printf("%c\t", graph->vertex[i]); for ( j = 0; j < graph->vertexs; j++ )//依次檢查矩陣 { if ( graph->arcs[i][j] != MAX_VALUE && !visited[j] )//i 代表矩陣的行, j 代表矩陣的列 { visited[j] = true; dfs_graph(graph, visited, j); } } } //廣度優先遍歷 void g_breadth_first_search(Graph * graph) { Queue queue;//隊列存儲的是節點數組的下標(int) bool visited[graph->vertexs]; int i, pos; q_init(&queue); for ( i = 0; i < graph->vertexs; i++ ) visited[i] = false; visited[0] = true; q_push(&queue, 0); while ( !q_empty(&queue) ) { pos = q_front(&queue); printf("%c\t", graph->vertex[pos]); for ( i = 0; i < graph->vertexs; i++ )//把隊頭元素的鄰接點入隊 { if ( !visited[i] && graph->arcs[pos][i] != MAX_VALUE ) { visited[i] = true; q_push(&queue, i); } } q_pop(&queue); } printf("\n"); } int main(void) { Graph graph; g_create(&graph); g_printMatrix(&graph); g_depth_first_search(&graph); g_breadth_first_search(&graph); return 0; }