Graph C language implementation of basic data structure and algorithm

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");
}

Tags: C Algorithm data structure Graph Theory

Posted on Fri, 15 Oct 2021 05:31:09 -0400 by wrequed