RodneyX

博客园 首页 新随笔 联系 订阅 管理

首先给出数据结构的定义

#define MAXSIZE 50  //vertexs only support numbering from 0 to 49

// vertex numbering should be sequential numbers and start from zero to simplify find adjacent vertex
// not support negative weight
typedef struct adjMaxtrix {
    int matrix[MAXSIZE][MAXSIZE];  //  也可以采用堆分配 
    int vnum;
}adjMaxtrix;

typedef struct arcNode {
    int adjVex;
    int weight;
    struct arcNode *nextarc;
}arcNode;

typedef struct vexNode {
    int vex;
    struct arcNode *firstarc;
    char *info;  //  在图算法里这个没啥用 
}vexNode;

typedef struct adjList {
    vexNode vexlist[MAXSIZE];
    int vnum;
    int anum;
}adjList;

先说说用于构造最小生成树的prim算法,该算法基本思想是加顶点,即将某个能够构成MST的必要顶点加入MST集合中,而这个顶点具有特点是该顶点不属于MST集合,但是其到MST的距离(到MST的某个顶点的距离)是最短的。这个距离可以由一个dist[]数组进行维护,每次新加入一个顶点后,就重新考虑不属于MST的顶点到MST的最短距离,不过我们只需要验证新加的这个点能否产生新的最短距离即可。并且还需要维护一个数组inMST[],用来标识某个顶点是否属于MST

于是我们可以得到如下代码,这里是一种基于邻接矩阵的朴素实现方法,时间复杂度O(|V|^2)

void prim(adjMaxtrix &mGraph, int T[], int v)
{
    int *dist = (int *)malloc(sizeof(int) * mGraph.vnum);
    int *inMST = (int *)malloc(sizeof(int) * mGraph.vnum);
    for (int i = 0; i < mGraph.vnum; ++i)
    {
        T[i] = -1;
        inMST[i] = 0;
        dist[i] = INT_MAX;
    }
    T[0] = v;
    int Tindex = 1;
    inMST[v] = 1;
    dist[v] = 0;

    int current_vex = v;
    for (int i = 1; i < mGraph.vnum; ++i)
    {
        int min_dist = INT_MAX, next_vex = -1;  //初始化全局最小dist[]为min_dist,并且记录下待加入顶点
        for (int j = 0; j < mGraph.vnum; ++j)
        {
            if (!inMST[j] && dist[j] > mGraph.matrix[current_vex][j])  //只对未在MST中的顶点更新其dist[]
                dist[j] = mGraph.matrix[current_vex][j];
            if (!inMST[j] && dist[j] < min_dist)  //由于dist更新只会发生这里,故可以直接进行更新全局最小dist[]和对应的待加入顶点
            {
                min_dist = dist[j];
                next_vex = j;
            }
        }
        if (next_vex >= 0)  //防御性编程,数据结构维护正确的话按道理next_vex(下一个用于更新距离的顶点,就是本轮加入点)是大于等于0的,因为肯定找得到一个点
        {
            T[Tindex++] = next_vex;
            inMST[next_vex] = 1;
            current_vex = next_vex;
        }
    }

    free(inMST);
    free(dist);
}

而用于搜索单源最短路径的dijkstra算法的思想和prim很相似,区别在于prim维护的dist[]是到整个MST集合的,而dijkstra算法维护的dist[]是到源点的,所以对于dijkstra算法,执行循环的次数和prim一样都是顶点数-1

另外对于路径path[]的构造是和距离更新同步的,一旦从顶点i出发修改了dist[j],则path[j] = i,这样最后在更新完dist[]后我们只需要将最优dist[j]对于的顶点i加入到已找到最短路径的顶点集即可,下一轮循环又更新dist[],其中只考虑与j邻接的顶点构成的边即可,这和prim算法如出一辙。另外我们用一个find[]数组用来标识某个点是否已找到最短路径。

另外值得一提的是该算法的证明可以在坦嫩鲍姆所著的《计算机网络》第五版中路由算法所在的网络层一章中找到

于是我们可以得到如下代码,这里是一种基于邻接表的朴素实现方法,时间复杂度O(|V|^2)

void dijkstra_brutal(adjList &aGraph, int src, int dist[], int path[])
{
    int *find = (int *)malloc(sizeof(int) * aGraph.vnum);
    int current_vex = src;
    for (int i = 0; i < aGraph.vnum; ++i)
    {
        dist[i] = INT_MAX;
        path[i] = -1;	//path[]数组有点像静态单链表,path[i]是src到i最短路径上顶点i的直接前驱顶点
        find[i] = 0;
    }
    dist[src] = 0;
    find[src] = 1;

    for (int i = 1; i < aGraph.vnum; ++i)
    {
        // update dist[] from current vertex
        if (current_vex >= 0)
        {
            for (arcNode *p = aGraph.vexlist[current_vex].firstarc; p != nullptr; p = p->nextarc)
                if (!find[p->adjVex] && dist[p->adjVex] > dist[current_vex] + p->weight)    // !find[p->adjVex] 更新操作是对未找到最短路径的顶点
                {
                    path[p->adjVex] = current_vex;
                    dist[p->adjVex] = dist[current_vex] + p->weight;
                }
        }
        // choose best dist 事实上这一步也可以上提到前一个循环,那样和刚才的prim算法基本上大差不差了
        int min_dist = INT_MAX;
        int new_vex = -1;
        // add to set
        for(int j = 0; j < aGraph.vnum; ++j) 
            if(!find[j] && dist[j] < min_dist) {
                new_vex = j;
                min_dist = dist[j];
            }
        if (new_vex >= 0)  // 有时候图不连通,所以这里必须判断选取的顶点有效性,而非防御性编程
            find[new_vex] = 1;
        current_vex = new_vex;
    }
    free(find);
}
posted on 2025-07-30 16:39  RodneyX  阅读(21)  评论(0)    收藏  举报