下面寫的代碼可能感覺會很亂,所以加上了目錄,意在爲了引導,也就是你需要回顧哪個算法,就能夠快速定位到該位置。
另外,閱讀代碼是件非常枯燥的事,所以儘量每一行都加的註釋,方便閱讀理解。
四、圖
(一)圖的順序存儲結構——鄰接矩陣
-
結構體的聲明
typedef enum { DG_GRAPH = 0, //有向圖 DN_GRAPH //無向圖 }GRAPH_TYPE; //圖的類型結構體 typedef struct { int no; //頂點的編號 int data; //頂點存放的數據 }VERTEX_TYPE; //頂點類型結構體 typedef struct { int n, e; //n爲頂點數,e爲邊數 int edge[MAXSIZE][MAXSIZE]; //存放鄰接矩陣的邊信息 VERTEX_TYPE ver_type[MAXSIZE]; //存放結點信息 GRAPH_TYPE graph_type; //圖的類型(可寫可不寫) }MGRAPH; //圖的鄰接矩陣結構體
-
圖的鄰接矩陣表示
/* method: 構造圖的鄰接矩陣 param: m_graph 圖 vex_num 頂點的數量 edges 邊的信息 */ int CreateDG(MGRAPH** m_graph,GRAPH_TYPE graph_type ,int vex_num, int edges[][MAXSIZE]) { int edge_num = 0; int i = 0, j = 0; (*m_graph)->n = vex_num; //結點個數賦值 for (i = 0; i < vex_num; i++) { (*m_graph)->ver_type[i].no = i; //頂點序號爲下標 (*m_graph)->ver_type[i].data = i; //頂點數據爲下標 for (j = 0; j < vex_num; j++) { (*m_graph)->edge[i][j] = edges[i][j]; //給圖的邊賦值 if (graph_type == DG_GRAPH) //判斷有向圖還是無向圖 { if (edges[i][j] == 1) //計算邊的數量 ++edge_num; } else if(graph_type == DN_GRAPH) //判斷有向圖還是無向圖 { if (edges[i][j] != MAX_NUM && edges[i][j] != 0) //計算邊的數量 ++edge_num; } } } if(graph_type == DG_GRAPH) (*m_graph)->e = edge_num; //邊的個數 else if(graph_type == DN_GRAPH) (*m_graph)->e = edge_num / 2; //無向圖邊的個數爲 1 的一半 return OK; }
(二)圖的鏈式存儲結構——鄰接表
-
結構體的聲明
#define OK 1 #define ERROR -1 #define MAXSIZE 6 typedef struct ArcNode { int adj_vertex; //該邊所指向的頂點信息 struct ArcNode* nextarc; //指向下一條邊的指針 int info; //該邊的相關信息,如權值 }ArcNode; //弧指針結構體 typedef struct { int data; //頂點信息 ArcNode* first_arc; //指向第一條邊的指針 }V_NODE; //頂點指針結構體 typedef struct { V_NODE adj_list[MAXSIZE]; //鄰接表 int n, e; //n爲頂點數,e爲邊數 }L_GRAPH; //圖的鄰接表結構體
-
構造圖的鄰接表
/* method: 構造圖,將鄰接矩陣轉化爲鄰接表 param: l_graph 圖的鄰接表 m_graph 圖的鄰接矩陣 */ int Atrix_To_List(L_GRAPH** l_graph,MGRAPH *m_graph) { int i = 0, j = 0; int flag = 0; //第一次是將頂點表的指針指向邊表,第二次是邊表指針指向邊 ArcNode* arc_node; //存放結點 ArcNode* temp = NULL; //臨時存放結點 (*l_graph)->e = m_graph->e; //邊數 (*l_graph)->n = m_graph->n; //頂點數 for (i = 0; i < (*l_graph)->n; i++) //根據頂點數來循環 { (*l_graph)->adj_list[i].data = m_graph->ver_type[i].data; //得到頂點的值 (*l_graph)->adj_list[i].first_arc = NULL; //置空 flag = 0; //每個頂點表都是 第一次是頂點表的指針指向邊表結點 for (j = 0; j < m_graph->n; j++) { if(m_graph->edge[i][j] == 1) //如果該頂點連接的有邊 { arc_node = (ArcNode*)__vcrt_malloc_normal(sizeof(ArcNode)); //創建內存 arc_node->adj_vertex = m_graph->ver_type[j].data; //存放該頂點信息 arc_node->nextarc = NULL; //將其像一個結點置位空 if (flag == 0) //第一次是將頂點表指向邊表 { (*l_graph)->adj_list[i].first_arc = arc_node; //將頂點表的指針指向邊表 temp = arc_node; //將臨時指針指向當前連接的結點 flag = 1; //第二次不執行這裏 } else { temp->nextarc = arc_node; //常規鏈表操作 temp = arc_node; } } } } return OK; }
-
十字鏈表的結構體聲明
typedef struct ARC_BOX { int tailvex, headvex; //十字鏈表的弧尾,弧頭結點 int info; //弧上的信息 struct ARC_BOX* hlink, *tlink; //終點相同指針,起點相同指針 }ARC_BOX; //弧的結構體 typedef struct { int data; //頂點信息 ARC_BOX* first_in,*first_out; //入邊表的第一個結點,出邊表的第一個結點 }VEX_NODE; //頂點結構體 typedef struct { VEX_NODE cross_list[MAXSIZE]; //頂點結點,表頭 int vexnum, arcnum; //有向圖結點數,弧數 }OL_GRAPH; //十字鏈表結構體
- 鄰接多重表結構體聲明
typedef struct EBOX { int mark; //訪問標記 int ivex, jvex; //該邊依附的兩個頂點信息 struct EBOX* ilink, * jlink; //指針域 int info; //該邊的信息 }EBOX; typedef struct { int data; //頂點的數據 EBOX* first_edge; //第一條依附該頂點的邊 }VEX_BOX; //頂點的結構體 typedef struct { VEX_BOX adj_mulist[MAXSIZE]; //鄰接多重表 int vexnum, edgenum; //結點數量,邊的數量 }AML_GRAPH; //結構體
(三)圖的遍歷
-
深度優先遍歷(DFS)
/* method: 深度優先搜索遍歷 param: l_graph 圖的鄰接表 current_vex 當前結點位置 */ void DFS(L_GRAPH* l_graph,int current_vex) { ArcNode* arc_node; Vex_Visit[current_vex] = 1; //將當前結點標記爲已訪問 Visit(current_vex); //訪問當前結點的頂點表的數據 arc_node = l_graph->adj_list[current_vex].first_arc; //指向頂點表的第一條邊 while (arc_node != NULL) //邊表不爲空 { if (Vex_Visit[arc_node->adj_vertex] == 0) //判斷這個邊表的結點是否訪問過 DFS(l_graph, arc_node->adj_vertex); arc_node = arc_node->nextarc; } }
-
廣度優先搜索遍歷(BFS)
/* method: 廣度優先搜索遍歷 param: l_graph 圖的鄰接表 current_vex 當前結點位置 */ void BFS(L_GRAPH* l_graph, int current_vex, int* visit) { int temp_vex = 0; ArcNode* arc_node; int queue[MAXSIZE], front = 0, rear = 0; //定義一個隊列 Vex_Visit_Init(visit); //清空訪問數組內容,初始狀態 Visit(l_graph->adj_list[current_vex].data); //訪問頂點表結點 visit[current_vex] = 1; //標記該結點已訪問過 rear = (rear + 1) % MAXSIZE; //入隊 queue[rear] = current_vex; while (rear != front) //隊內有元素 { front = (front+1) % MAXSIZE; //一個結點出隊 temp_vex = queue[front]; arc_node = l_graph->adj_list[temp_vex].first_arc; //取得邊結點 while (arc_node != NULL) { if (visit[arc_node->adj_vertex] == 0) { rear = (rear + 1) % MAXSIZE; //入隊 queue[rear] = arc_node->adj_vertex; Visit(arc_node->adj_vertex); //訪問該結點 visit[arc_node->adj_vertex] = 1; } arc_node = arc_node->nextarc; //指針指向下一個結點 } } }
-
訪問函數
/* method: 訪問操作 param: data 結點的數據 */ void Visit(int data) { printf("%d ", data); }
-
判斷兩頂點是否有路徑
/* method: 判斷兩個頂點之間是否有路徑 param: l_graph 圖的鄰接表 vex1,vex2 兩結點 */ int Trace(L_GRAPH* l_graph, int vex1, int vex2) { Vex_Visit_Init(Vex_Visit); //清空標記數組 DFS(l_graph, vex1); //遍歷圖 for (int i = 0; i < l_graph->e; i++) //遍歷整個標記數組 { if (Vex_Visit[vex2] == 1) //如果vex2被訪問過,其標記爲1 return OK; else return ERROR; } }
(四)圖的應用
-
普里姆算法
/* method: 構造最小生成樹,普里姆算法 param: m_graph 有向圖的鄰接矩陣,兩個結點直接無聯繫的,權值取無窮,最大 start_vex 選取的第一個頂點 sum 返回權值的總和 */ void Prim(MGRAPH* m_graph, int start_vex, int* sum) { int low_cost[MAXSIZE], //存放侯選邊的全部權值,也就是與查找結點相連接的邊的權值 vset[MAXSIZE], v; int i = 0, j = 0, k = 0, min = 0; v = start_vex; //將任意選擇一個結點賦給v, for (i = 0; i < m_graph->n; ++i) { low_cost[i] = m_graph->edge[start_vex][i]; //獲取該結點與其他結點之間的全部的權值,用於後面的篩選 vset[i] = 0; //全部置零 } vset[start_vex] = 1; //選取該頂點,將其置爲1,標記入樹 (*sum) = 0; //最小生成樹的權值,這裏先清零 for (i = 0; i < m_graph->n; ++i) //搜索侯選邊最小權值 { min = MAX_NUM; for (j = 0; j < m_graph->n; ++j) //選出所有與該結點相關的全部邊的最小權值,並獲得最小權值的結點 { if (vset[j] == 0 && low_cost[j] < min) //判斷結點j是否被查找過,並且判斷該權值是否最小 { min = low_cost[j]; //更新最小權值 k = j; //記錄最小權值的結點 } } if (min != MAX_NUM) //還有結點沒有檢測過 { vset[k] = 1; //將該結點併入樹中 v = k; //以該結點爲起始結點 (*sum) += min; //計算全部的權值 } for (j = 0; j < m_graph->n; ++j) //在新加入樹的結點下 尋找與該樹最關的最小權值 { if (vset[j] == 0 && m_graph->edge[v][j] < low_cost[j]) //判斷哪個邊最短 { low_cost[j] = m_graph->edge[v][j]; //更新最短邊 } } } }
-
克魯斯卡爾算法
typedef struct { int vex_a, vex_b; //vex_a和vex_b爲一條邊上的兩個頂點 int wight; //wight爲邊上的權重 }ROAD;
/* method: 快速排序算法 param: road 邊的信息數組,對lows-highs之間的數據進行排序 lows 最低下標 highs 最高下標 */ void quick_Sort(ROAD road[], int lows,int highs) { ROAD temp = { 0 }; int low = lows, high = highs; if (lows < highs) { temp = road[lows]; while (low < high) { while (low < high && road[high].wight >= temp.wight) //從右往左掃描 --high; if (low < high) { road[low] = road[high]; //將 該值放到左邊 ++low; //向後移動一個位置 } while (low < high && road[low].wight < temp.wight) ++low; if (low < high) { road[high] = road[low]; --high; } } road[low] = temp; //將temp放到最終位置 quick_Sort(road, lows,low-1); //遞歸,對temp左邊的關鍵字排序 quick_Sort(road, low+1, highs); //遞歸,對temp右邊的關鍵字排序 } }
int parents[MAXSIZE] = { 0 }; //建立並查集 ,下標 表示頂點編號,元素代表它的上級 int get_Root(int a) { while (a != parents[a]) //如果傳進來的值已經是根結點,則直接輸出,如果不是,一直尋找它的父結點 a = parents[a]; //將父結點的值傳進去 return a; //返回找到的根結點, } /* method: 克魯斯卡爾算法 param: m_graph 無向有權圖 sum 最小生成樹的權值總和 road road爲結點之間的信息 */ void Kruskal(MGRAPH * m_graph,int *sum,ROAD *road) { int i = 0; int vex_num = 0, edge = 0, vex_a = 0, vex_b = 0; //分別定義 頂點數,邊數,兩個結點vex_a,vex_b vex_num = m_graph->n; //獲取結點數 edge = m_graph->e; //獲取邊數 *sum = 0; //最小生成樹權值清零 for (i = 0; i < vex_num; i++) //初始化並查集 { parents[i] = i; //剛開始,默認每個頂點都沒有聯繫,每個頂點都是一個集合 } quick_Sort(road, 0, m_graph->e - 1); //將權值從小到大排序,使用的是快速排序法 for (i = 0; i < edge; ++i) { vex_a = get_Root(road[i].vex_a); //取出最小的權值的邊相連的結點A vex_b = get_Root(road[i].vex_b); //取出最小的權值的邊相連的結點B if (vex_a != vex_b) { parents[vex_a] = vex_b; //將頂點合併進同一個樹中,因爲上面road裏面的兩個邊已經直到是連接的了,所以這裏合併進一個樹中 (*sum) += road[i].wight; } } }