理论: 图论(8): 最小生成树

总括

给定一个无向图, 如果它的每个子图中的任意两点之间都相互连接并且是一棵树, 那么这棵树就叫做生成树。如果边上有权值, 那么使得边权值和最小的生成树叫做最小生成树

例如我们有这样一个图, 把顶点看作村庄, 边看作计划要修建的道路。 为了所有的村庄间的同通行, 恰好修建村庄的数目-1条道路时, 这就对应了一棵生成树。修建道路需要投入建设费, 那么求解使得道路建设费用最小的生成树就是最小生成树问题。

关于最小生成树, 常见的有prim算法和Kruskal算法, 这两种算法都是基于贪心策略的一种算法。很显然, 生成树是否存在和图是否联通是等价的,因此我们假定图是连通的。

prim算法

首先我们介绍prim算法。 prim算法和Dijkstra算法十分相似, 都是从某个顶点出发, 不断添加边的算法。

首先我们假设一棵只包含一个顶点S的树T。 然后贪心的选取T和其他顶点之间连接的最小权值的边, 并把它加到T中。 不断进行这的操作, 就可以得到一棵生成树。证明省略
·
·
废话不多说这个代码写得我心累

 //prim队列优化版
 //Q为优先队列 以边的长度从小到大排序
 //node结构体     v代表到达的顶点 len代表道路的长度
 //不定长数组G以邻接表的方式保存图
 //mindist数组初始化为INF  oxfffffff
 //intree初始化为false;
int Prim()
{
    int sum = 0;
    priority_queue<node>Q;
    for (int i = 0; i < G[0].size(); i++)
    {/*将与0号顶点相连的顶点放入队列*/
        minDist[G[0][i].v] = G[0][i].len;   
        Q.push(G[0][i]);
    }/*0号顶点已将在生成树之中*/
    intree[0] = true;

    while (!Q.empty())
    {
        node now = Q.top();
        Q.pop();
        if (intree[now.v])
            continue;
        intree[now.v] = true;
        sum += now.len;
        for (int i = 0; i < G[now.v].size(); i++)
        {
            int vex = G[now.v][i].v;
            int len = G[now.v][i].len;
            if (len < minDist[vex])
            {/*如果顶点的距离len比现有的小, 更新, 压入队列*/
                minDist[vex] = len;
                Q.push(G[now.v][i]);
            }
        }
    }
    return sum;
}

优化之后的时间复杂度为0(E log V)
·
·
·
·

Kruskal算法

下面我们来介绍Kruskal算法。 Kruskal算法按照边的长度的顺序从小到大遍历一遍, 如果不产生圈或者重边就把这条边加入到最小生成树之中。

证明略;

接下来问题的重点就转化为了如何避免在添加边的时候造成圈或者是重边的形成: 并查集
不懂并查集?看这里
假设现在我们要把连接顶点U和W的边放入生成树中, 我们说要做的就是检查现在deU 和W是否在同一个联通分量之中。 如果在同一个连通分量之中, 那么添加之后就会形成环或者重边; 反之, 如果不在同一个连通分量之中, 那么添加之后不会形成圈和重边。

void init(int x)
{
    for (int i = 1; i < x; i++)
    {
        father[i] = i;
        rankk[i] = 0;
    }
}

int find(int x)
{
    int far = x;
    while (far != father[far])
        far = father[far];

    int i = x;
    while (father[i] != far)
    {
        int temp = father[i];
        father[i] = far;
        i = temp;
    }
    return far;
}

bool unoin(int x, int y)
{
    x = find(x);
    y = find(y);
    if (x == y)
        return false;

    if (rankk[x] < rankk[y])
        father[x] = y;
    else
    {
        father[y] = x;
        if (rankk[x] == rankk[y])
            rankk[x]++;
    }
    return true;
}

bool cmp(node a, node b)
{
    return a.price < b.price;
}

int kruskal(void)
{
    int ans = 0;
    sort(a, a + n, cmp);
    for (int i = 1; i <= n; i++)
    {
        if (unoin(a[i].start, a[i].endd))
            ans += a[i].price;
    }
    return ans;
}

重点!!!!!!

稠密图(边多点少)用Kruskal算法
稀疏图(点多边少)用prim算法

E >= V log V 的称为稠密图

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值