算法和数据结构

图的基本算法及其C语言的实现

2019-05-27  本文已影响28人  JerryShieh

数据结构

“图”的数据结构有两种:

邻接表结构体定义如下:

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;

邻接矩阵的结构体定义如下:

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;

算法

深度优先搜索 (Depth First Search)

一句话描述就是“一条路走到黑”,它的递归与非递归的代码如下:

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的递归遍历有些类似于二叉树的前序遍历。

借用了额外的数据结构——栈。

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

广度优先搜索 (Breadth First Search)

BFS是“一圈一圈往外找”的算法,借助了“循环队列”来实现:

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

提示:

  1. BFS算法类似于二叉树的层次遍历。
  2. BFS遍历的最后一个结点是离起始结点“最远”的结点。

最小生成树 (Minimum Spanning Tree)

Prim

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

并查集 (Union Find Set)

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方法是用来合并集合的,多作为克鲁斯卡尔算法的辅助工具,该方法执行成功说明选的两个结点及其所在的集合可以构成一个无环连通分量,每次成功执行该方法,都要更新各个集合的状态(因为有新的结点加入),以便后续的判断。

最短路径算法

单源最短路径算法——迪杰斯特拉算法(Dijkstra Algorithm)

迪杰斯特拉算法是用来计算图中所有结点到某个特定结点的最短距离的算法,因为执行一次迪杰斯特拉算法,只是计算出所给特定的“源结点”到其他结点的最短距离,因此该算法也属于“单源最短路径算法”。

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里,而普利姆算法总是从生成树集合外,选择一个距离 生成树这个集合最近的结点加入到生成树中。

多源最短路径算法——弗洛伊德算法(Floyd Algorithm)

与迪杰斯特拉算法不同的是,调用一次弗洛伊德算法可以计算任意两个结点间的最短距离,当然,你也可以通过多次调用迪杰斯特拉算法来计算多源最短路径。

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]);
        }
}
上一篇 下一篇

猜你喜欢

热点阅读