Basic concepts of 1-graph
Graph is a data structure in which there may be some specific relationship between any two vertices.
Graph consists of two sets:
- Non empty but finite vertex set V
- Describes the relationship between vertices - set E of edges (can be an empty set)
G = ( V , E ) G=(V,E) G=(V,E)
Undirected graph: (v,w)=(w,v)
Undirected graph: < V, w >=< w,v>
Spanning tree: a minimal connected subgraph of G containing all n vertices, which must contain and only contain n-1 edges of G.
Storage structure of 2-graph
Adjacency matrix storage mode
Code implementation:
#ifndef GRAPH_ADJACENCY_MATRIX_H #define GRAPH_ADJACENCY_MATRIX_H #ifndef GRAPH_ADJACENCY_LIST_H #include <limits.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #define INFINITY INT_MAX typedef int Vertex; typedef int WeightType; typedef char DataType; struct ENode { Vertex V1, V2; WeightType weight; }; typedef struct ENode* PtrToENode; typedef struct ENode* Edge; #endif struct MGNode { int Nv; int Ne; WeightType** g; DataType* data; bool directed; }; typedef struct MGNode* PtrToMGNode; typedef struct MGNode* MGraph; //Initializes a graph with Vertex vertices but no edges MGraph CreateMGraph(int VertexNum, bool d) { //Highlight the main logic and do not check the malloc return value MGraph graph = (MGraph)malloc(sizeof(struct MGNode)); graph->Ne = 0; graph->Nv = VertexNum; graph->directed = d; graph->g = (WeightType**)malloc(sizeof(WeightType*) * VertexNum); for (int i = 0; i < VertexNum; ++i) { graph->g[i] = (WeightType*)malloc(sizeof(WeightType) * VertexNum); } graph->data = (DataType*)malloc(sizeof(DataType) * VertexNum); for (int i = 0; i < VertexNum; ++i) { graph->data[i] = '0' + i; } for (Vertex v = 0; v < VertexNum; ++v) { for (Vertex w = 0; w < VertexNum; ++w) { graph->g[v][w] = INFINITY; } } for (Vertex v = 0; v < VertexNum; ++v) { graph->g[v][v] = 0; } return graph; } //Building graph from weight matrix m MGraph CreateMGraphFromMatrix(WeightType* m, DataType* data, int vertexNum, bool d) { if (m == NULL) return NULL; MGraph graph = CreateMGraph(vertexNum, d); for (int i = 0; i < vertexNum; ++i) { for (int j = 0; j < vertexNum; ++j) { graph->g[i][j] = m[i * vertexNum + j]; //If the current is an edge if (graph->g[i][j] != 0 && graph->g[i][j] < INFINITY) { if (graph->directed) { //Directed graph, direct++ graph->Ne++; } else { //Undirected graph, I < J++ if (i < j) graph->Ne++; } } } } //If there is data in the vertex if (data != NULL) { for (int i = 0; i < vertexNum; ++i) { graph->data[i] = data[i]; } } return graph; } void DestoryMGraph(MGraph graph) { if (graph == NULL) return; if (graph->g != NULL) { for (int i = 0; i < graph->Nv; ++i) { if (graph->g[i] != NULL) { free(graph->g[i]); } } free(graph->g); } if (graph->data != NULL) free(graph->data); free(graph); return; } void PrintMGraph(MGraph graph) { if (graph == NULL) return; printf("\nPrintMGraph,directed=%d\n", graph->directed); printf(" "); for (int i = 0; i < graph->Nv; ++i) { printf("%3c ", graph->data[i]); } printf("\n\n"); for (int i = 0; i < graph->Nv; ++i) { printf("%2c ", graph->data[i]); for (int j = 0; j < graph->Nv; ++j) { //For convenience of observation, print x to indicate non adjacency (weight infinity) if (graph->g[i][j] < INFINITY) { printf("%3d ", graph->g[i][j]); } else { printf(" x "); } } printf("\n"); } return; } void InsertMGraphEdge(MGraph graph, Edge E) { //Insert the directed edge of v1 to v2 graph->g[E->V1][E->V2] = E->weight; graph->Ne++; //If it is an undirected graph, the other direction should also be inserted if (!graph->directed) graph->g[E->V2][E->V1] = E->weight; return; } //Create diagram from stdin MGraph BuildMGraph(bool isDirected, bool storageDataInVertex) { int Nv; //Enter the number of vertices scanf("%d", &Nv); MGraph graph = CreateMGraph(Nv, isDirected); //Enter the number of sides scanf("%d", &(graph->Ne)); if (graph->Ne != 0) { Edge E = (Edge)malloc(sizeof(struct ENode)); for (int i = 0; i < graph->Ne; ++i) { scanf("%d %d %d", &(E->V1), &(E->V2), &(E->weight)); InsertMGraphEdge(graph, E); } free(E); } //If there is data in the node if (storageDataInVertex) { for (Vertex v = 0; v < graph->Nv; ++v) { scanf("%c", &(graph->data[v])); } } return graph; } #endif
Test procedure:
void test_graph_adiacency_matrix() { WeightType m[5][5] = { {0, 1, 1, 1, INFINITY}, {1, 0, INFINITY, INFINITY, 1}, {1, INFINITY, 0, INFINITY, 2}, {1, INFINITY, INFINITY, 0, 3}, {INFINITY, 1, 2, 3, 0}}; MGraph mg = CreateMGraphFromMatrix(&m[0][0], NULL, 5, false); PrintMGraph(mg); printf("\nnv=%d,ne=%d\n", mg->Nv, mg->Ne); struct ENode e = {2, 3, 23}; InsertMGraphEdge(mg, &e); PrintMGraph(mg); printf("\nnv=%d,ne=%d\n", mg->Nv, mg->Ne); DestoryMGraph(mg); DataType arr[] = "abcde"; m[2][1] = 3; m[1][0] = INFINITY; m[2][0] = INFINITY; m[3][0] = INFINITY; mg = CreateMGraphFromMatrix(&m[0][0], arr, 5, true); PrintMGraph(mg); printf("\nnv=%d,ne=%d\n", mg->Nv, mg->Ne); InsertMGraphEdge(mg, &e); PrintMGraph(mg); printf("\nnv=%d,ne=%d\n", mg->Nv, mg->Ne); DestoryMGraph(mg); }
For any graph, it takes O(V^2) space, which is cost-effective for dense graphs and a waste of space for sparse graphs.
Adjacency table storage method
For each vertex vi in the graph, chain all its adjacent points into a single linked list, and then put all the adjacent table headers in an array (vertex table).
There are two node structures in adjacency table representation:
Code implementation:
#ifndef GRAPH_ADJACENCY_LIST_H #define GRAPH_ADJACENCY_LIST_H #ifndef GRAPH_ADJACENCY_MATRIX_H #include <limits.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #define INFINITY INT_MAX typedef int Vertex; typedef int WeightType; typedef char DataType; struct ENode { Vertex V1, V2; WeightType weight; }; typedef struct ENode* PtrToENode; typedef struct ENode* Edge; #endif typedef struct AdjVNode* PtrToAdjVNode; struct AdjVNode { Vertex AdjV; WeightType weight; PtrToAdjVNode next; }; struct VNode { PtrToAdjVNode firstEdge; //Store vertex data, no DataType data; }; struct LGNode { int Nv; int Ne; //Adjacency table: structure array struct VNode* g; bool directed; }; typedef struct LGNode* PtrToLGNode; typedef struct LGNode* LGraph; LGraph CreateLGraph(int vertexNum, bool d) { //Highlight the main logic and do not check the malloc return value LGraph graph = (LGraph)malloc(sizeof(struct LGNode)); graph->g = (struct VNode*)malloc(sizeof(struct VNode) * vertexNum); graph->Nv = vertexNum; graph->Ne = 0; graph->directed = d; for (Vertex v = 0; v < vertexNum; ++v) { graph->g[v].firstEdge = NULL; graph->g[v].data = '0' + v; } return graph; } void InsertLGraphEdge(LGraph graph, Edge e) { PtrToAdjVNode newNode = (PtrToAdjVNode)malloc(sizeof(struct AdjVNode)); //Itself V2, pointed by V1 newNode->AdjV = e->V2; newNode->weight = e->weight; //Single chain meter insertion newNode->next = graph->g[e->V1].firstEdge; graph->g[e->V1].firstEdge = newNode; graph->Ne++; //If it is an undirected graph, the reverse is also inserted if (!graph->directed) { newNode = (PtrToAdjVNode)malloc(sizeof(struct AdjVNode)); newNode->AdjV = e->V1; newNode->weight = e->weight; //Single chain meter insertion newNode->next = graph->g[e->V2].firstEdge; graph->g[e->V2].firstEdge = newNode; } return; } //If data needs to save data, it can be directly passed into the array pointer, otherwise NULL can be passed LGraph CreateLGraphFromMatrix(WeightType* m, DataType* data, int vertexNum, bool d) { if (m == NULL) return NULL; LGraph graph = CreateLGraph(vertexNum, d); for (int i = 0; i < vertexNum; ++i) { for (int j = 0; j < vertexNum; ++j) { if ((m[i * vertexNum + j] != 0) && (m[i * vertexNum + j] < INFINITY)) { PtrToAdjVNode newNode = (PtrToAdjVNode)malloc(sizeof(struct AdjVNode)); newNode->AdjV = j; newNode->weight = m[i * vertexNum + j]; newNode->next = graph->g[i].firstEdge; graph->g[i].firstEdge = newNode; //Directed graph direct + +, undirected graph check I < J++ if (graph->directed || (!graph->directed && i < j)) { graph->Ne++; } } } } //If there is data in the vertex if (data != NULL) { for (int i = 0; i < vertexNum; ++i) { graph->g[i].data = data[i]; } } return graph; } void DestoryLGraph(LGraph graph) { if (graph == NULL) return; if (graph->g != NULL) { for (int i = 0; i < graph->Nv; ++i) { PtrToAdjVNode p = graph->g[i].firstEdge; PtrToAdjVNode t; while (p) { t = p->next; free(p); p = t; } } free(graph->g); } free(graph); return; } void PrintLGraph(LGraph graph) { if (graph == NULL) return; printf("\nPrintLGraph,directed=%d\n", graph->directed); for (int i = 0; i < graph->Nv; ++i) { printf("%3c->", graph->g[i].data); for (PtrToAdjVNode p = graph->g[i].firstEdge; p != NULL; p = p->next) { printf("%3c ", graph->g[p->AdjV].data); } printf("\n"); } return; } LGraph BuildLGraph(bool isDirected, bool storageDataInVertex) { int Nv; scanf("%d", &Nv); LGraph Graph = CreateLGraph(Nv, isDirected); scanf("%d", &(Graph->Ne)); if (Graph->Ne != 0) { Edge E = (Edge)malloc(sizeof(struct ENode)); for (int i = 0; i < Graph->Ne; ++i) { scanf("%d %d %d", &(E->V1), &(E->V2), &(E->weight)); InsertLGraphEdge(Graph, E); } free(E); } //If there is data in the vertex if (storageDataInVertex) { for (Vertex V = 0; V < Graph->Nv; ++V) { scanf("%c", &(Graph->g[V].data)); } } return Graph; } #endif
Traversal of 3-graph
Deep first search DFS
Starting from a vertex v0, access this vertex, and then recursively perform the same DFS from the unreachable adjacency of v0, straight
All vertices with the same path as v0 in the graph are accessed, which completes the traversal of a connected component in the graph.
One instance:
Recursive DFS
Algorithm idea:
Similar to the prior traversal of a tree, first visit(V) juxtaposes the visited flag, and then for (each adjacency point of V): DFS is executed recursively.
Code implementation:
//DFS is performed on the graph stored in the adjacency table, and only one connected component is traversed void DFS_LGraph_recursively(LGraph g, Vertex v, void (*visit)(Vertex), bool* visited) { visit(v); visited[v] = true; for (PtrToAdjVNode w = g->g[v].firstEdge; w != NULL; w = w->next) { if (!visited[w->AdjV]) { DFS_LGraph_recursively(g, w->AdjV, visit, visited); } } return; } //The graph stored in the adjacency matrix is DFS, and only one connected component is traversed void DFS_MGraph_recursively(MGraph g, Vertex v, void (*visit)(Vertex), bool* visited) { visit(v); visited[v] = true; for (Vertex i = 0; i < g->Nv; ++i) { if ((g->g[v][i] < INFINITY && g->g[v][i] != 0) && !visited[i]) { DFS_MGraph_recursively(g, i, visit, visited); } } return; }
Iterative DFS
Algorithm idea:
Non recursive traversal requires us to maintain a stack ourselves.
First, access the first node and push it into the stack. Then, as long as the stack is not empty, execute the following loop body:
Take the top element t of the stack. For each adjacent point of T, if it has not been accessed, access, mark, put it on the stack, break out the for loop, go to the next while loop, and traverse the node just put on the stack.
If the for loop exits from the termination condition, it means that this t has no unreachable adjacency points. Just pop it up directly, otherwise it will remain in the stack.
Code implementation:
//Although the non recursive algorithm can not pass in the visited array from the outside, it is retained, so that we can see which vertices are not accessed when the graph is disconnected void DFS_LGraph_iteratively(LGraph g, Vertex v, void (*visit)(Vertex), bool* visited) { Stack s = CreateStack(); visit(v); visited[v] = true; Push(s, v); Vertex t; while (!StackEmpty(s)) { Top(s, &t); PtrToAdjVNode w = NULL; //For each adjacency point of t for (w = g->g[t].firstEdge; w != NULL; w = w->next) { //If not accessed: access, mark, stack, end the for loop if (!visited[w->AdjV]) { visit(w->AdjV); visited[w->AdjV] = true; Push(s, w->AdjV); //End the for loop to stop traversing the adjacency points of the current node, and go to the next while loop to traverse the adjacency points of the nodes just stacked break; } } //If the top node of the stack does not have an adjacent point that has not been accessed, it indicates that a path has come to an end and needs to be backtracked out of the stack if (w == NULL) Pop(s, &t); } DestoryStack(s); return; } void DFS_MGraph_iteratively(MGraph g, Vertex v, void (*visit)(Vertex), bool* visited) { Stack s = CreateStack(); visit(v); visited[v] = true; Push(s, v); Vertex t; while (!StackEmpty(s)) { Top(s, &t); Vertex i = 0; for (i = 0; i < g->Nv; ++i) { if ((g->g[t][i] < INFINITY && g->g[t][i] != 0) && !visited[i]) { visit(i); visited[i] = true; Push(s, i); break; } } if (i == g->Nv) Pop(s, &t); } DestoryStack(s); return; }
The process of traversing a graph is essentially a process of finding its adjacent nodes for each vertex, and the time spent depends on the storage structure of the graph.
- When adjacency matrix storage is used, the time complexity is O(V^2)
- When adjacency table storage is used, the time complexity is O(V+E)
Breadth first search BFS
BFS is similar to the sequence traversal process of a tree. Starting from v0, after accessing v0, access each unreachable adjacent point of v0 in turn, and then access their adjacent points from these adjacent points in turn, and make "the adjacent point of the first visited vertex" precede "the next visited adjacent point" until all vertices in the graph are accessed.
The process of BFS traversing the graph is to take v0 as the starting point, from near to far, and successively access the vertices connected with v0 with a path length of 1, 2, 3.
You need to save the accessed vertices in turn with the help of a queue.
One instance:
Algorithm idea:
First queue the first node access, and then execute the following loop as long as the queue is not empty:
Pop up the first element t of the queue, and then for (each adjacency point of T): if it has not been accessed, access and join the queue.
Code implementation:
void BFS_LGraph(LGraph g, Vertex v, void (*visit)(Vertex), bool* visited) { Queue q = CreateQueue(); visit(v); visited[v] = true; EnQueue(q, v); Vertex t; while (!QueueEmpty(q)) { DeQueue(q, &t); for (PtrToAdjVNode w = g->g[t].firstEdge; w != NULL; w = w->next) { if (!visited[w->AdjV]) { visit(w->AdjV); visited[w->AdjV] = true; EnQueue(q, w->AdjV); } } } DestoryQueue(q); return; } void BFS_MGraph(MGraph g, Vertex v, void (*visit)(Vertex), bool* visited) { Queue q = CreateQueue(); visit(v); visited[v] = true; EnQueue(q, v); Vertex t; while (!QueueEmpty(q)) { DeQueue(q, &t); for (Vertex i = 0; i < g->Nv; ++i) { if ((g->g[t][i] < INFINITY && g->g[t][i] != 0) && !visited[i]) { visit(i); visited[i] = true; EnQueue(q, i); } } } DestoryQueue(q); return; }
Complexity analysis:
- When adjacency matrix storage is used, the time complexity is O(V^2)
- When adjacency table storage is used, the time complexity is O(V+E)
Test code:
void visit(Vertex v) { printf("%3d ", v); } void test_graph_traversal() { WeightType m[5][5] = { {0, 1, 1, 1, INFINITY}, {1, 0, INFINITY, INFINITY, 1}, {1, INFINITY, 0, INFINITY, 2}, {1, INFINITY, INFINITY, 0, 3}, {INFINITY, 1, 2, 3, 0}}; LGraph lg = CreateLGraphFromMatrix(&m[0][0], NULL, 5, false); MGraph mg = CreateMGraphFromMatrix(&m[0][0], NULL, 5, false); PrintLGraph(lg); PrintMGraph(mg); printf("\n\n"); bool* visited = (bool*)malloc(sizeof(bool) * 5); DFS_LGraph_iteratively(lg, 0, visit, visited); memset(visited, 0, sizeof(bool) * 5); printf("\n\n"); DFS_LGraph_recursively(lg, 0, visit, visited); memset(visited, 0, sizeof(bool) * 5); printf("\n\n"); DFS_MGraph_recursively(mg, 0, visit, visited); memset(visited, 0, sizeof(bool) * 5); printf("\n\n"); DFS_MGraph_iteratively(mg, 0, visit, visited); memset(visited, 0, sizeof(bool) * 5); printf("\n\n"); BFS_LGraph(lg, 0, visit, visited); memset(visited, 0, sizeof(bool) * 5); printf("\n\n"); BFS_MGraph(mg, 0, visit, visited); memset(visited, 0, sizeof(bool) * 5); printf("\n\n"); printf("\ninsert{1,2,9}\n"); struct ENode e = {1, 2, 9}; InsertLGraphEdge(lg, &e); InsertMGraphEdge(mg, &e); PrintLGraph(lg); PrintMGraph(mg); printf("\n\n"); DFS_LGraph_iteratively(lg, 0, visit, visited); memset(visited, 0, sizeof(bool) * 5); printf("\n\n"); DFS_LGraph_recursively(lg, 0, visit, visited); memset(visited, 0, sizeof(bool) * 5); printf("\n\n"); DFS_MGraph_recursively(mg, 0, visit, visited); memset(visited, 0, sizeof(bool) * 5); printf("\n\n"); DFS_MGraph_iteratively(mg, 0, visit, visited); memset(visited, 0, sizeof(bool) * 5); printf("\n\n"); BFS_LGraph(lg, 0, visit, visited); memset(visited, 0, sizeof(bool) * 5); printf("\n\n"); BFS_MGraph(mg, 0, visit, visited); memset(visited, 0, sizeof(bool) * 5); printf("\n\n"); DestoryLGraph(lg); DestoryMGraph(mg); printf("\ndirected=1:\n"); lg = CreateLGraph(5, true); mg = CreateMGraph(5, true); struct ENode enodes[5] = {{0, 1, 1}, {1, 2, 1}, {2, 0, 1}, {3, 0, 1}, {2, 4, 1}}; for (int i = 0; i < 5; ++i) { InsertLGraphEdge(lg, &enodes[i]); InsertMGraphEdge(mg, &enodes[i]); } PrintLGraph(lg); PrintMGraph(mg); printf("\n\n"); DFS_LGraph_iteratively(lg, 0, visit, visited); memset(visited, 0, sizeof(bool) * 5); printf("\n\n"); DFS_LGraph_recursively(lg, 0, visit, visited); memset(visited, 0, sizeof(bool) * 5); printf("\n\n"); DFS_MGraph_recursively(mg, 0, visit, visited); memset(visited, 0, sizeof(bool) * 5); printf("\n\n"); DFS_MGraph_iteratively(mg, 0, visit, visited); memset(visited, 0, sizeof(bool) * 5); printf("\n\n"); BFS_LGraph(lg, 0, visit, visited); memset(visited, 0, sizeof(bool) * 5); printf("\n\n"); BFS_MGraph(mg, 0, visit, visited); memset(visited, 0, sizeof(bool) * 5); printf("\n\n"); printf("\ninsert{4,3,9}:\n"); e.V1 = 4; e.V2 = 3; e.weight = 9; InsertLGraphEdge(lg, &e); InsertMGraphEdge(mg, &e); PrintLGraph(lg); PrintMGraph(mg); printf("\n\n"); DFS_LGraph_iteratively(lg, 0, visit, visited); memset(visited, 0, sizeof(bool) * 5); printf("\n\n"); DFS_LGraph_recursively(lg, 0, visit, visited); memset(visited, 0, sizeof(bool) * 5); printf("\n\n"); DFS_MGraph_recursively(mg, 0, visit, visited); memset(visited, 0, sizeof(bool) * 5); printf("\n\n"); DFS_MGraph_iteratively(mg, 0, visit, visited); memset(visited, 0, sizeof(bool) * 5); printf("\n\n"); BFS_LGraph(lg, 0, visit, visited); memset(visited, 0, sizeof(bool) * 5); printf("\n\n"); BFS_MGraph(mg, 0, visit, visited); memset(visited, 0, sizeof(bool) * 5); printf("\n\n"); DestoryLGraph(lg); DestoryMGraph(mg); free(visited); }
4-minimum spanning tree
What is the minimum spanning tree:
In a word, the minimum spanning tree is the tree with the smallest total weight of edges in all spanning trees {T} of graph G (like nonsense)
The minimum spanning tree is not unique. Only when the weights of each edge in the graph are not equal, the minimum spanning tree of the graph is unique.
Prim algorithm
Algorithm idea:
Code implementation:
//Returns the smallest dist of all unrecorded vertices Vertex findMinDist(int vertexNum, WeightType dist[]) { WeightType minDist = INFINITY; Vertex MinV; //Traverse all vertices for (Vertex v = 0; v < vertexNum; ++v) { //If not included and dist is smaller if (dist[v] != 0 && dist[v] < minDist) { MinV = v; minDist = dist[v]; } } if (minDist < INFINITY) return MinV; else return ERROR; } //The book should be wrong. A minimum spanning tree should be established in the function and pointed to it with MST, so the pointer of MST should be passed in, that is, a secondary pointer int prim_MGraph(MGraph graph, LGraph* MST) { WeightType* dist = (WeightType*)malloc(sizeof(WeightType) * graph->Nv); WeightType TotalWeight = 0; Vertex* parent = (Vertex*)malloc(sizeof(Vertex) * graph->Nv); Vertex v; int VCount = 0; *MST = CreateLGraph(graph->Nv, graph->directed); struct ENode e; for (v = 0; v < graph->Nv; ++v) { //Start from 0 dist[v] = graph->g[0][v]; parent[v] = 0; } dist[0] = 0; VCount++; parent[0] = -1; while (1) { v = findMinDist(graph->Nv, dist); if (v == ERROR) break; e.V1 = parent[v]; e.V2 = v; e.weight = dist[v]; InsertLGraphEdge(*MST, &e); TotalWeight += dist[v]; dist[v] = 0; VCount++; //For each newly added adjacency point v, update dist for (int i = 0; i < graph->Nv; ++i) { if (dist[i] != 0 && graph->g[v][i] < INFINITY) { //If it is not included, and the weight of its edge is < dist[w], update dist[w] if (graph->g[v][i] < dist[i]) { dist[i] = graph->g[v][i]; parent[i] = v; } } } } free(parent); free(dist); if (VCount < graph->Nv) TotalWeight = ERROR; return TotalWeight; } //It's hard for C language not to support function overloading int prim_LGraph(LGraph graph, LGraph* MST) { WeightType* dist = (WeightType*)malloc(sizeof(WeightType) * graph->Nv); WeightType TotalWeight = 0; Vertex* parent = (Vertex*)malloc(sizeof(Vertex) * graph->Nv); Vertex v; int VCount = 0; *MST = CreateLGraph(graph->Nv, graph->directed); struct ENode e; for (int i = 0; i < graph->Nv; ++i) { dist[i] = INFINITY; parent[i] = 0; } for (PtrToAdjVNode p = graph->g[0].firstEdge; p != NULL; p = p->next) { dist[p->AdjV] = p->weight; } dist[0] = 0; VCount++; parent[0] = -1; while (1) { v = findMinDist(graph->Nv, dist); if (v == ERROR) break; e.V1 = parent[v]; e.V2 = v; e.weight = dist[v]; InsertLGraphEdge(*MST, &e); TotalWeight += dist[v]; dist[v] = 0; VCount++; for (PtrToAdjVNode p = graph->g[v].firstEdge; p != NULL; p = p->next) { if (dist[p->AdjV] != 0 && p->weight < dist[p->AdjV]) { dist[p->AdjV] = p->weight; parent[p->AdjV] = v; } } } free(parent); free(dist); if (VCount < graph->Nv) TotalWeight = ERROR; return TotalWeight; }
Test procedure:
void test_prim() { WeightType m[5][5] = { {0, 4, 2, 3, INFINITY}, {4, 0, INFINITY, INFINITY, 1}, {2, INFINITY, 0, INFINITY, 3}, {3, INFINITY, INFINITY, 0, 1}, {INFINITY, 1, 3, 1, 0}}; LGraph lg = CreateLGraphFromMatrix(&m[0][0], NULL, 5, false); MGraph mg = CreateMGraphFromMatrix(&m[0][0], NULL, 5, false); PrintLGraph(lg); PrintMGraph(mg); printf("\n\n"); LGraph mst; int cost; cost = prim_LGraph(lg, &mst); printf("\ncost=%d\n", cost); PrintLGraph(mst); DestoryLGraph(mst); mst = NULL; cost = prim_MGraph(mg, &mst); printf("\ncost=%d\n", cost); PrintLGraph(mst); DestoryLGraph(mst); mst = NULL; DestoryLGraph(lg); DestoryMGraph(mg); //Insert several large enough edges and measure again printf("\nchange-1:\n"); m[0][2] = m[2][0] = 20; m[0][3] = m[3][0] = 30; lg = CreateLGraphFromMatrix(&m[0][0], NULL, 5, false); mg = CreateMGraphFromMatrix(&m[0][0], NULL, 5, false); PrintLGraph(lg); PrintMGraph(mg); printf("\n\n"); cost = prim_LGraph(lg, &mst); printf("\ncost=%d\n", cost); PrintLGraph(mst); DestoryLGraph(mst); mst = NULL; cost = prim_MGraph(mg, &mst); printf("\ncost=%d\n", cost); PrintLGraph(mst); DestoryLGraph(mst); mst = NULL; DestoryLGraph(lg); DestoryMGraph(mg); //Geodesic digraph printf("\nchange-2:\n"); m[0][3] = 30; m[3][0] = INFINITY; m[3][4] = 40; m[4][3] = INFINITY; lg = CreateLGraphFromMatrix(&m[0][0], NULL, 5, true); mg = CreateMGraphFromMatrix(&m[0][0], NULL, 5, true); PrintLGraph(lg); PrintMGraph(mg); printf("\n\n"); cost = prim_LGraph(lg, &mst); printf("\ncost=%d\n", cost); PrintLGraph(mst); DestoryLGraph(mst); mst = NULL; cost = prim_MGraph(mg, &mst); printf("\ncost=%d\n", cost); PrintLGraph(mst); DestoryLGraph(mst); mst = NULL; DestoryLGraph(lg); DestoryMGraph(mg); }
Time complexity: O(V^2), independent of E, so it is suitable for solving dense graphs.
Kruskal algorithm
Algorithm idea:
Pseudo code Description:
Code implementation:
Core algorithm
int kruskal_LGraph(LGraph lg, LGraph* MST) { WeightType totalWeight = 0; int eCount = 0; int nextEdge = lg->Ne; SetType vSet = (Vertex*)malloc(sizeof(Vertex) * lg->Nv); for (int i = 0; i < lg->Nv; ++i) { vSet[i] = -1; } Edge eSet = (Edge)malloc(sizeof(struct ENode) * lg->Ne); InitializeESet_LGraph(lg, eSet); *MST = CreateLGraph(lg->Nv, lg->directed); while (eCount < lg->Nv - 1) { nextEdge = GetEdge(eSet, nextEdge); if (nextEdge < 0) break; if (CheckCycle(vSet, eSet[nextEdge].V1, eSet[nextEdge].V2)) { InsertLGraphEdge(*MST, &eSet[nextEdge]); totalWeight += eSet[nextEdge].weight; eCount++; } } free(vSet); free(eSet); if (eCount < lg->Nv - 1) totalWeight = -1; return totalWeight; } int kruskal_MGraph(MGraph mg, LGraph* MST) { WeightType totalWeight = 0; int eCount = 0; int nextEdge = mg->Ne; SetType vSet = (Vertex*)malloc(sizeof(Vertex) * mg->Nv); for (int i = 0; i < mg->Nv; ++i) { vSet[i] = -1; } Edge eSet = (Edge)malloc(sizeof(struct ENode) * mg->Ne); InitializeESet_MGraph(mg, eSet); *MST = CreateLGraph(mg->Nv, mg->directed); while (eCount < mg->Nv - 1) { nextEdge = GetEdge(eSet, nextEdge); if (nextEdge < 0) break; if (CheckCycle(vSet, eSet[nextEdge].V1, eSet[nextEdge].V2)) { InsertLGraphEdge(*MST, &eSet[nextEdge]); totalWeight += eSet[nextEdge].weight; eCount++; } } free(vSet); free(eSet); if (eCount < mg->Nv - 1) totalWeight = -1; return totalWeight; }
Correlation auxiliary function
//On the filter function of the minimum heap of Edge weights void PercDown(Edge ESet, int p, int N) { int parent, child; struct ENode x; x = ESet[p]; for (parent = p; (parent * 2 + 1) < N; parent = child) { child = 2 * parent + 1; if (child < (N - 1) && ESet[child + 1].weight < ESet[child].weight) { child++; } if (x.weight <= ESet[child].weight) break; else ESet[parent] = ESet[child]; } ESet[parent] = x; } void InitializeESet_LGraph(LGraph lg, Edge eSet) { int eCount = 0; for (Vertex v = 0; v < lg->Nv; ++v) { for (PtrToAdjVNode w = lg->g[v].firstEdge; w != NULL; w = w->next) { if ((!lg->directed && v < w->AdjV) || lg->directed) { eSet[eCount].V1 = v; eSet[eCount].V2 = w->AdjV; eSet[eCount++].weight = w->weight; } } } for (int p = lg->Ne / 2; p >= 0; --p) { PercDown(eSet, p, lg->Ne); } return; } void InitializeESet_MGraph(MGraph mg, Edge eSet) { int eCount = 0; for (int i = 0; i < mg->Nv; ++i) { for (int j = 0; j < mg->Nv; ++j) { if ((!mg->directed && i < j) || mg->directed) { if (mg->g[i][j] != 0 && mg->g[i][j] < INFINITY) { eSet[eCount].V1 = i; eSet[eCount].V2 = j; eSet[eCount++].weight = mg->g[i][j]; } } } } for (int p = mg->Ne / 2; p >= 0; --p) { PercDown(eSet, p, mg->Ne); } } void swap(Edge e1, Edge e2) { struct ENode e = *e1; *e1 = *e2; *e2 = e; } int GetEdge(Edge ESet, int currentSize) { swap(&ESet[0], &ESet[currentSize - 1]); PercDown(ESet, 0, currentSize - 1); return currentSize - 1; } //Check whether they belong to the same connected set. If not, merge them bool CheckCycle(SetType VSet, Vertex v1, Vertex v2) { Vertex root1 = Find(VSet, v1); Vertex root2 = Find(VSet, v2); if (root1 == root2) { return false; } else { Union_tssn(VSet, root1, root2); return true; } }
It mainly completes the problem of finding the minimum edge and judging whether to form a loop with the help of the minimum heap and parallel search set.
The related operations of merging query set are as follows:
typedef int SetElemType; typedef int SetName; typedef SetElemType* SetType; //Look up the parent node pointer until you find the root node SetName Find(SetType S, SetElemType X) { while (S[X] >= 0) X = S[X]; return X; } SetName Find_PathCompression(SetType S, SetElemType X) { if (S[X] < 0) return X; else return S[X] = Find(S, S[X]); } void Union_tssn(SetType S, SetName Root1, SetName Root2) { //The default Root1 and Root2 are the root nodes of two different collections S[Root2] = Root1; }
5-shortest path algorithm
Dijkstra algorithm
Algorithm idea:
Code implementation:
bool dijkstra_MGraph(MGraph mg, int dist[], int path[], Vertex s) { bool* collected = (bool*)malloc(sizeof(bool) * mg->Nv); for (int i = 0; i < mg->Nv; ++i) { dist[i] = mg->g[s][i]; if (mg->g[s][i] < INFINITY) { path[i] = s; } else { path[i] = -1; } collected[i] = false; } dist[s] = 0; collected[s] = true; Vertex v, w; while (1) { v = searchMinDist(dist, collected, mg->Nv); if (v == ERROR) break; collected[v] = true; for (w = 0; w < mg->Nv; ++w) { if (!collected[w] && mg->g[v][w] < INFINITY) { if (mg->g[v][w] < 0) { return false; } if (dist[v] + mg->g[v][w] < dist[w]) { dist[w] = dist[v] + mg->g[v][w]; path[w] = v; } } } } free(collected); return true; } bool dijkstra_LGraph(LGraph lg, int dist[], int path[], Vertex s) { bool* collected = (bool*)malloc(sizeof(bool) * lg->Nv); for (int i = 0; i < lg->Nv; ++i) { dist[i] = INFINITY; path[i] = -1; collected[i] = false; } for (PtrToAdjVNode p = lg->g[s].firstEdge; p != NULL; p = p->next) { dist[p->AdjV] = p->weight; path[p->AdjV] = s; } dist[s] = 0; collected[s] = true; Vertex v; while (1) { v = searchMinDist(dist, collected, lg->Nv); if (v == ERROR) break; collected[v] = true; for (PtrToAdjVNode p = lg->g[v].firstEdge; p != NULL; p = p->next) { if (!collected[p->AdjV]) { if (p->weight < 0) { return false; } if (dist[v] + p->weight < dist[p->AdjV]) { dist[p->AdjV] = dist[v] + p->weight; path[p->AdjV] = v; } } } } free(collected); return true; }
Test code:
void test_dijkstra() { WeightType m[9][9]; for (int i = 0; i < 9; ++i) { for (int j = 0; j < 9; ++j) { m[i][j] = INFINITY; } } m[0][1] = 2; m[0][3] = 5; m[1][3] = 2; m[1][2] = 5; m[3][5] = 4; m[3][6] = 2; m[5][6] = 3; m[2][5] = 4; m[5][8] = 6; m[6][8] = 7; m[2][4] = 8; m[4][5] = 2; m[5][7] = 9; m[7][8] = 3; m[4][7] = 5; for (int i = 0; i < 9; ++i) { for (int j = 0; j < 9; ++j) { m[j][i] = m[i][j]; } } MGraph mg = CreateMGraphFromMatrix(&m[0][0], NULL, 9, false); LGraph lg = CreateLGraphFromMatrix(&m[0][0], NULL, 9, false); PrintMGraph(mg); PrintLGraph(lg); int* dist = (int*)malloc(sizeof(int) * mg->Nv); int* path = (int*)malloc(sizeof(int) * mg->Nv); bool result; result = dijkstra_MGraph(mg, dist, path, 0); printf("\nresult=%d\n", result); for (int i = 0; i < mg->Nv; ++i) { printf("%d\t%d\t%d\n", i, dist[i], path[i]); } memset(dist, 0, sizeof(int) * 9); memset(path, 0, sizeof(int) * 9); result = dijkstra_LGraph(lg, dist, path, 0); printf("\nresult=%d\n", result); for (int i = 0; i < mg->Nv; ++i) { printf("%d\t%d\t%d\n", i, dist[i], path[i]); } free(path); free(dist); }
Time complexity: O(V^2)
Floyd algorithm
Algorithm idea:
Code implementation:
bool floyd_MGraph(MGraph mg, WeightType** d, Vertex** path) { int i, j, k; for (i = 0; i < mg->Nv; ++i) { for (j = 0; j < mg->Nv; ++j) { d[i][j] = mg->g[i][j]; path[i][j] = -1; } } for (k = 0; k < mg->Nv; ++k) { for (i = 0; i < mg->Nv; ++i) { for (j = 0; j < mg->Nv; ++j) { if (d[i][k] + d[k][j] < d[i][j]) { d[i][j] = d[i][k] + d[k][j]; if (i == j && d[i][j] < 0) { return false; } path[i][j] = k; } } } } return true; } bool floyd_LGraph(LGraph lg, WeightType** d, Vertex** path) { int i, j, k; PtrToAdjVNode p; for (i = 0; i < lg->Nv; ++i) { for (j = 0; j < lg->Nv; ++j) { if (i == j) { d[i][j] = 0; } else { d[i][j] = INFINITY; } path[i][j] = -1; } } for (i = 0; i < lg->Nv; ++i) { for (p = lg->g[i].firstEdge; p != NULL; p = p->next) { d[i][p->AdjV] = p->weight; } } printf("\n--d--\n"); for(int i=0;i<3;++i){ for(int j=0;j<3;++j){ printf("%3d ",d[i][j]); } printf("\n"); } printf("\n--path--\n"); for(int i=0;i<3;++i){ for(int j=0;j<3;++j){ printf("%3d ",path[i][j]); } printf("\n"); } for (k = 0; k < lg->Nv; ++k) { for (i = 0; i < lg->Nv; ++i) { for (p = lg->g[i].firstEdge; p != NULL; p = p->next) { if (d[i][k] + d[k][p->AdjV] < d[i][p->AdjV]) { d[i][p->AdjV] = d[i][k] + d[k][p->AdjV]; if (i == p->AdjV && d[i][p->AdjV] < 0) { return false; } path[i][p->AdjV] = k; } } } } return true; }
Test code:
void test_floyd() { WeightType m[3][3] = { {0, 4, 11}, {6, 0, 2}, {3, INFINITY, 0}}; MGraph mg = CreateMGraphFromMatrix(&m[0][0], NULL, 3, true); LGraph lg = CreateLGraphFromMatrix(&m[0][0], NULL, 3, true); PrintMGraph(mg); PrintLGraph(lg); int i, j; Vertex** path = (Vertex**)malloc(sizeof(Vertex*) * 3); for (i = 0; i < 3; ++i) { path[i] = (Vertex*)malloc(sizeof(Vertex) * 3); } WeightType** d = (WeightType**)malloc(sizeof(WeightType*) * 3); for (i = 0; i < 3; ++i) { d[i] = (WeightType*)malloc(sizeof(WeightType) * 3); } bool result1, result2; result1 = floyd_MGraph(mg, d, path); printf("\nresult1=%d\n", result1); printf("\nd:\n"); for (i = 0; i < 3; ++i) { for (j = 0; j < 3; ++j) { printf("%3d ", d[i][j]); } printf("\n"); } printf("\npath\n"); for (i = 0; i < 3; ++i) { for (j = 0; j < 3; ++j) { printf("%3d ", path[i][j]); } printf("\n"); } result2 = floyd_LGraph(lg, d, path); printf("\nresult2=%d\n", result2); printf("\nd:\n"); for (i = 0; i < 3; ++i) { for (j = 0; j < 3; ++j) { printf("%3d ", d[i][j]); } printf("\n"); } printf("\npath:\n"); for (i = 0; i < 3; ++i) { for (j = 0; j < 3; ++j) { printf("%3d ", path[i][j]); } printf("\n"); } }
floyd algorithm finds the shortest path between each pair of vertices, and the time complexity is O(V^3)
6-topological sorting
Algorithm idea:
Code implementation:
bool topological_sort_LGraph(LGraph lg, Vertex topOrder[]) { int* inDegree = (int*)malloc(sizeof(int) * lg->Nv); memset(inDegree, 0, sizeof(int) * lg->Nv); Queue q = CreateQueue(); for (Vertex v = 0; v < lg->Nv; ++v) { for (PtrToAdjVNode p = lg->g[v].firstEdge; p != NULL; p = p->next) { inDegree[p->AdjV]++; } } for (int i = 0; i < lg->Nv; i++) { if (inDegree[i] == 0) { EnQueue(q, i); } } int cnt = 0; int x; while (!QueueEmpty(q)) { DeQueue(q, &x); topOrder[cnt++] = x; for (PtrToAdjVNode p = lg->g[x].firstEdge; p != NULL; p = p->next) { inDegree[p->AdjV]--; if (inDegree[p->AdjV] == 0) { EnQueue(q, p->AdjV); } } } DestoryQueue(q); free(inDegree); return cnt == lg->Nv; } bool topological_sort_MGraph(MGraph mg, Vertex topOrder[]) { int* inDegree = (int*)malloc(sizeof(int) * mg->Nv); memset(inDegree, 0, sizeof(int) * mg->Nv); Queue q = CreateQueue(); for (int i = 0; i < mg->Nv; ++i) { for (int j = 0; j < mg->Nv; ++j) { if (mg->g[j][i] < INFINITY && mg->g[j][i] != 0) { inDegree[i]++; } } } for (int i = 0; i < mg->Nv; i++) { if (inDegree[i] == 0) { EnQueue(q, i); } } int cnt = 0; int x; while (!QueueEmpty(q)) { DeQueue(q, &x); topOrder[cnt++] = x; for (int i = 0; i < mg->Nv; ++i) { if (mg->g[x][i] < INFINITY && mg->g[x][i] != 0) { inDegree[i]--; if (inDegree[i] == 0) { EnQueue(q, i); } } } } DestoryQueue(q); free(inDegree); return cnt == mg->Nv; }
Test code:
void test_topological_sort() { WeightType m[6][6]; for (int i = 0; i < 6; ++i) { for (int j = 0; j < 6; ++j) { m[i][j] = INFINITY; if (i == j) { m[i][j] = 0; } } } m[0][1] = 1; m[0][2] = 1; m[1][2] = 1; m[2][5] = 1; m[3][4] = 1; m[4][5] = 1; LGraph lg = CreateLGraphFromMatrix(&m[0][0], NULL, 6, true); MGraph mg = CreateMGraphFromMatrix(&m[0][0], NULL, 6, true); PrintLGraph(lg); PrintMGraph(mg); Vertex topOrder[6]; topological_sort_LGraph(lg, topOrder); for (int i = 0; i < 6; ++i) { printf("%3d ", topOrder[i]); } printf("\n"); memset(topOrder, 0, sizeof(int) * 6); topological_sort_MGraph(mg, topOrder); for (int i = 0; i < 6; ++i) { printf("%3d ", topOrder[i]); } printf("\n"); }