「圖」的數據結構有兩種:node
鄰接表適用於稀疏圖(邊的數量遠遠小於頂點的數量),它的抽象描述以下:
上圖是一個含有四個頂點的無向圖,四個頂點V0,V1,V2及V3用一個數組來存取,借用後面的結構體定義來描述,數組元素的類型爲VertexNode
,一個字段info
用來保存頂點的信息,另外一個字段firstEdge
指向與該頂點有關的邊結點,類型爲EdgeNode
,邊結點的toAdjVex
字段表示這條邊的另外一個頂點結點的數組下標,next
字段僅僅用來指向另外一個邊結點,全部經過next
連接起來的邊結點都有相同的起始頂點.
注意:當用一個鄰接表描述圖時,就已經肯定了該圖的遍歷順序,以上圖的鄰接表爲例,從 V0頂點開始遍歷時,不管是深度優先搜索 (DFS)仍是廣度優先搜索 (BFS),都會先找到 V1結點,而從左邊的圖來看,其實V1,V2,V3均可覺得下一個遍歷的結點。算法
鄰接表結構體定義以下:數組
typedef struct EdgeNode { int toAdjVex; // The index of vertex array which this edge points to. float weight; // The edge weight. struct EdgeNode *next; // The next edge, note that it only means the next edge also links to the vertex which this edge links to. } EdgeNode; typedef struct VertexNode { VERTEX_DATA_TYPE info; // The vertex info,. struct EdgeNode* firstEdge; // The first edge which the vertex points to. } VertexNode; typedef struct { VertexNode adjList[VERTEX_NUM]; // Adjacency list, which stores the all vertexes of the graph. int vertextNum; // The number of vertex. int edgeNum; // The number of edge. } AdjListGraph;
鄰接矩陣適用於稠密圖(邊的數量比較多),它的抽象描述以下:
上圖是個無向無權圖,僅用0、1來表示兩個頂點是否相連,固然,一般作法是將邊的權值來代替1,用一個十分大的數字(如∞)來代替0,表示不相連。數據結構
鄰接矩陣的結構體定義以下:less
typedef struct { int number; VERTEX_DATA_TYPE info; } Vertex; typedef struct { float edges[VERTEX_NUM][VERTEX_NUM]; // The value of this two dimensional array is the weight of the edge. int vertextNum; // The number of vertex. int edgeNum; // The number of edge. Vertex vex[VERTEX_NUM]; // To store vertex. } MGraph;
一句話描述就是「一條路走到黑」,它的遞歸與非遞歸的代碼以下:工具
void dfsRecursion(AdjListGraph* graph, int startVertexIndex, bool visit[]) { printf("%c ", (graph -> adjList[startVertexIndex]).info); visit[startVertexIndex] = true; EdgeNode* edgeIndex = (graph -> adjList[startVertexIndex]).firstEdge; while (edgeIndex != NULL) { if (visit[edgeIndex -> toAdjVex] == false) dfsRecursion(graph, edgeIndex -> toAdjVex, visit); edgeIndex = edgeIndex -> next; } }
提示:DFS的遞歸遍歷有些相似於二叉樹的前序遍歷。oop
借用了額外的數據結構——棧。this
void dfsNonRecursion(AdjListGraph* graph, int startVertextIndex, bool visit[]) { linked_stack* stack = NULL; init_stack(&stack); // Visit the start vertex. printf("%c ", (graph -> adjList[startVertextIndex]).info); visit[startVertextIndex] = true; EdgeNode* edgeNode = (graph -> adjList[startVertextIndex]).firstEdge; if (edgeNode != NULL) push(stack, edgeNode); while (!isEmptyStack(stack)) { edgeNode = ((EdgeNode*)pop(stack)) -> next; while (edgeNode != NULL && !visit[edgeNode -> toAdjVex]) { printf("%c ", (graph -> adjList[edgeNode -> toAdjVex]).info); visit[edgeNode -> toAdjVex] = true; push(stack, edgeNode); edgeNode = (graph -> adjList[edgeNode -> toAdjVex]).firstEdge; } } }
BFS是「一圈一圈往外找」的算法,藉助了「循環隊列」來實現:spa
void bfs(AdjListGraph* graph, int startVertexIndex, bool visit[]) { // Loop queue initialization. LoopQueue loopQ; loopQ.front = 0; loopQ.rear = 0; LoopQueue* loopQueue = &loopQ; enqueue(loopQueue, &(graph -> adjList[startVertexIndex])); printf("%c ", (graph -> adjList[startVertexIndex]).info); visit[startVertexIndex] = true; while (!isEmpty(loopQueue)) { VertexNode* vertexNode = dequeue(loopQueue); EdgeNode* edgeNode = vertexNode -> firstEdge; while(edgeNode != NULL) { if (visit[edgeNode -> toAdjVex] == false) { printf("%c ", (graph -> adjList[edgeNode -> toAdjVex]).info); visit[edgeNode -> toAdjVex] = true; enqueue(loopQueue, &(graph -> adjList[edgeNode -> toAdjVex])); } edgeNode = edgeNode -> next; } } }
提示:3d
算法
float prim(MGraph* graph, int startVertex) { float totalCost = 0; float lowCost[VERTEX_NUM]; // The value of lowCost[i] represents the minimum distance from vertex i to current spanning tree. bool treeSet[VERTEX_NUM]; // The value of treeSet[i] represents whether the vertex i has been merged into the spanning tree. // Initialization for (int i = 0; i < (graph -> vertextNum); i++) { lowCost[i] = graph -> edges[startVertex][i]; // Init all cost from i to startVertex. treeSet[i] = false; // No vertex is in the spanning tree set at first. } treeSet[startVertex] = true; // Merge the startVertex into the spanning tree set. printf("%c ", (graph -> vex[startVertex]).info); for (int i = 0; i < (graph -> vertextNum); i++) { int minCost = MAX_COST; // MAX_COST is a value greater than any other edge weight. int newVertex = startVertex; // Find the minimum cost vertex which is out of the spanning tree set. for (int j = 0; j < (graph -> vertextNum); j++) { if (!treeSet[j] && lowCost[j] < minCost) { minCost = lowCost[j]; newVertex = j; } } treeSet[newVertex] = true; // Merge the new vertex into the spanning tree set. /* Some ops, for example you can print the vertex so you will get the sequence of node of minimum spanning tree. */ if (newVertex != startVertex) { printf("%c ", (graph -> vex[newVertex]).info); totalCost += lowCost[newVertex]; } // Judge whether the cost is change between the new spanning tree and the remaining vertex. for (int j = 0; j < (graph -> vertextNum); j++) { if (!treeSet[j] && lowCost[j] > graph -> edges[newVertex][j]) lowCost[j] = graph -> edges[newVertex][j]; // Update the cost between the spanning tree and the vertex j. } } return totalCost; }
並查集能夠很容易判斷幾個不一樣的元素是否屬於同一個集合,正是由於這個特性,並查集是克魯斯卡爾算法( Kruskal's algorithm)的重要工具。
在這個例子中,咱們用數組來做爲並查集工具的「集」,數組的下標表明集合裏元素的序列號,而該下標的值表示這個結點的父結點在該數組的下標。這個數組是一個大集合,大集合裏還劃分了許多小集合,劃分的依據是這些元素是否屬於同一個根結點,這個根節點的值爲負數,其絕對值的大小爲該集合裏元素的個數,所以經過不斷尋找兩個元素的父結點,直至找到根結點,進而判斷根結點是否相等來斷定這兩個元素是否屬於同一個集合(在克魯斯卡爾算法中,也就是經過此來判斷新加入的結點是否會造成環)。
int findRootInSet(int array[], int x) { if (array[x] < 0) { // Find the root index. return x; } else { // Recursively find its parent until find the root, // then recursively update the children node so that they will point to the root. return array[x] = findRootInSet(array, array[x]); } } // For merging the one node into the other set. bool unionSet(int array[], int node1, int node2) { int root1 = findRootInSet(array, node1); int root2 = findRootInSet(array, node2); if (root1 == root2) { // It means they are in the same set return false; } // The value of array[root] is negative and the absolute value is its children numbers, // when merging two sets, we choose to merge the more children set into the less one. if (array[root1] > array[root2]) { array[root1] += array[root2]; array[root2] = root1; } else { array[root2] += array[root1]; array[root1] = root2; } return true; }
注意上面的 unionSet
方法是用來合併集合的,多做爲克魯斯卡爾算法的輔助工具,該方法執行成功說明選的兩個結點及其所在的集合能夠構成一個無環連通份量,每次成功執行該方法,都要更新各個集合的狀態(由於有新的結點加入),以便後續的判斷。
迪傑斯特拉算法是用來計算圖中全部結點到某個特定結點的最短距離的算法,由於執行一次迪傑斯特拉算法,只是計算出所給特定的「源結點」到其餘結點的最短距離,所以該算法也屬於「單源最短路徑算法」。
步驟
void dijkstra(MGraph* graph, int startVertexIndex) { // For storing the minimum cost from the arbitrary node to the start vertex. float minCostToStart[VERTEX_NUM]; // For marking whether the node is in the set. bool set[VERTEX_NUM]; // Initialization for (int i = 0; i < VERTEX_NUM; i++) { minCostToStart[i] = graph -> edges[i][startVertexIndex]; set[i] = false; } // Add the start vertex into the set. set[startVertexIndex] = true; int minNodeIndex = startVertexIndex; for (int count = 1; count < VERTEX_NUM; count++) { int minCost = MAX_COST; // Find the adjacent node which is nearest to the startVertexIndex. for (int i = 0; i < VERTEX_NUM; i++) { if (!set[i] && minCostToStart[i] < minCost) { minCost = minCostToStart[minNodeIndex]; minNodeIndex = i; } } // Add the proper node into the set set[minNodeIndex] = true; // After the new node is added into the set, update the minimum cost of each node which is out of the set. for (int i = 0; i < VERTEX_NUM; i++) { if (!set[i] && (graph -> edges[i][minNodeIndex]) < MAX_COST) { // The new cost of each node to source = the cost of new added node to source + the cost of node i to new added node. float newCost = minCostToStart[minNodeIndex] + graph -> edges[i][minNodeIndex]; if (newCost < minCostToStart[i]) minCostToStart[i] = newCost; } } } printf("The cost of %c to each node:\n", (graph -> vex[startVertexIndex]).info); for (int i = 0; i < VERTEX_NUM; i++) { if (i != startVertexIndex) printf("-----> %c : %f\n", (graph -> vex[i]).info, minCostToStart[i]); } }
提示:迪傑斯特拉算法與普利姆算法十分相似,特別是步驟 2。迪傑斯特拉算法老是從 unvisited set裏選擇距離 源結點
最近的結點加入到 visited set裏,而普利姆算法老是從生成樹集合外,選擇一個距離 生成樹
這個集合最近的結點加入到生成樹中。
與迪傑斯特拉算法不一樣的是,調用一次弗洛伊德算法能夠計算任意兩個結點間的最短距離,固然,你也能夠經過屢次調用迪傑斯特拉算法來計算多源最短路徑。
步驟
minCost[i][j]
。minCost[i][j]
表示結點i到j的距離。minCost[i][j]
得值變小,若是能,則更新minCost[i][j]
的值。void floyd(MGraph* graph) { float minCost[VERTEX_NUM][VERTEX_NUM]; // Store the distance between any two nodes. int path[VERTEX_NUM][VERTEX_NUM]; // Store the intermediate node between the two nodes. int i, j, k; // Initialization for (i = 0; i < VERTEX_NUM; i++) { for (j = 0; j < VERTEX_NUM; j++) { minCost[i][j] = graph -> edges[i][j]; path[i][j] = -1; } } // Find if there is another k node, it makes the distance dis[i][k] + dis[k][j] < dis[i][j]; for (k = 0; k < VERTEX_NUM; k++) for (i = 0; i < VERTEX_NUM; i++) for (j = 0; j < VERTEX_NUM; j++) { if (minCost[i][j] > minCost[i][k] + minCost[k][j]) { minCost[i][j] = minCost[i][k] + minCost[k][j]; path[i][j] = k; } } for (i = 0; i < VERTEX_NUM; i++) for (j = 0; j < VERTEX_NUM; j++) { if (i != j && minCost[i][j] != MAX_COST) printf("%c ---> %c, the minimum cost is %f\n", (graph -> vex[i]).info, (graph -> vex[j]).info, minCost[i][j]); } }