图论算法-最小生成树

和自甴很熟 提交于 2020-02-14 00:28:29

图论算法-最小生成树

在一个无向图中找出一颗最小生成树(minimum spanning tree),这个问题对有向图也是有意义的,不过找起来更困难。最小生成树存在当且仅当图GG是连通的。在最小生成树中边的条数为V1\left | V \right |-1

Prim算法

在每一步,都要把一个节点当作根并往上边加。在算法的任一时刻,我们都可以看到一个已经添加到树上的顶点集,而其余顶点尚未加到这棵树中,此时,算法在每一阶段都可以通过选择边(u,v)(u,v),使得(u,v)(u,v)的值是所有uu在树上但vv不在树上的边值中的最小者,而找出一个新的顶点并把它添加到这棵树中。

代码

这里使用的邻接矩阵。

void Prim(Graph g)
{
    VertexType adjver[VERNUM];
    WeightType lowcost[VERNUM];
    int i, j, k;
    WeightType min;

    // 初始化,一开始生产树中只有开始的顶点
    // 这里开始的顶点的编号为 0
    for (i = 0; i < g->vernum; i++)
    {
        lowcost[i] = g->arc[0][i];
        adjver[i] = 0;
    }

    for (i = 1; i < g->vernum; i++)
    {
        // 在已生成树的顶点中查找权值最小的邻接点的边
        k = 0;
        min = INFINITY;
        for (j = 1; j < g->vernum; j++)
            if (lowcost[j] != 0 && lowcost[j] < min)
            {
                min = lowcost[j];
                k = j;
            }
		
        // 输出新增加的顶点
        printf("(%d, %d)\n", adjver[k] + 1, k + 1);
        lowcost[k] = 0;	// 标记添加到树中的顶点不再使用

        // 更新图中顶点邻接点的权值及顶点
        for (j = 1; j < g->vernum; j++)
            if (lowcost[j] != 0 && g->arc[k][j] < lowcost[j])
            {
                adjver[j] = k;
                lowcost[j] = g->arc[k][j];
            }
    }
}

输入/输出

Input vernum: 7
Input arcnum: 12
1 2 2
1 3 4
1 4 1
2 4 3
2 5 10
3 4 2
3 6 5
4 5 7
4 6 8
4 7 4
5 7 6
6 7 1
Prim's algorithm minmum spanning tree:
(1, 4)
(1, 2)
(4, 3)
(4, 7)
(7, 6)
(7, 5)
请按任意键继续. . .

Kruskal算法

第二种贪婪策略是连续地按照最小边的权选择边,并且当所选的边不产生圈时就把它作为取定的边。

代码1

图使用邻接矩阵表示。

void Kruskal(Graph G)
{
    int i, vs1, vs2;
    VertexType v1, v2;
    // 辅助数组
    VertexType head;	// 边的始点
    VertexType tail;	// 边的终点
    ArcType lowcost;	// 边的权值
    int Vexset[VerNum];	// 实际上是一个并查集(不相交集)
    
    // 按图中的边的权值大小排序,存储到前三个数组中
   	Sort(Edge);	// 将前3个数组中的元素按权值从小到大排序
    for(i = 0; i < G->vernum; i++)
    {
        // 获取边始点和终点的下标
        v1 = LocateVex(G, head[i]);
        v2 = LocateVex(G, tail[i]);
        // 获取两个顶点所在的连通分量
        vs1 = Vexset[v1];
        vs2 = Vexset[v2];
        if(vs1 != vs2)
        {
            // 将新添加的边打印输出
            printf("(%d, %d)\n", head[i], tail[i]);
            // 合并 vs1 和 vs2 两个分量,将两个集合统一
            for(j = 0; j < G->vernum; j++)
                if(Vexset[j] == vs2)	// 集合编号为 vs2 的都改为 vs1
                    Vexset[j] = vs1;
        }
    }
}

代码来自严蔚敏《数据结构(C语言版)(第2版)》,实际上这个代码并没有交代清楚其背后的运行,还有阉割了两个数据结构的介绍。

代码2

void Kruskal(Graph G)
{
    int EdgeAccepted;
    DisjSet S;	// 并查集(不相交集)
    PriorityQueue H;	// 优先队列
    Vertex U, V;
    SetType Uset, Vset;	// 并查集根的数据类型
    Edge E;
    
    // 初始化并查集,一开始每个顶点都在一个单独的集合中
    Intialize(S);
    // 构造优先队列(实际上就是对边的权值进行排序)
    ReadGraphIntoHeapArray(G, H);	// 将每条边放到优先队列中(此时未排序)
    BuildHeap(H);	// 对优先队列进行“排序”
    
    // 最小生成树中边的个数是顶点的个数减1
    EdgeAccepted = 0;
    while(EdgeAccepted < NumVertex - 1)
    {
        // 从优先队列取出权值为最小值的边并删除
        // E中有两个点,始点和终点,E = (U, V)
        E = DeleteMin(H);
        // 查找两个顶点所在的集合
        Uset = Find(U, S);
        Vset = Find(V, S);
        if(Uset != Vset)
        {
            // 输出生产的边
            printf("(%d, %d)\n", U, V);
            EdgeAccepted++;
            // 合并两个集合
            SetUnion(S, Uset, Vset);
        }
    }
    
}

代码来自Mark Allen Weiss的《数据结构与算法分析 C语言描述》。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!