首先给出数据结构的定义
#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);
}