• 最小生成树算法总结(Kruskal,Prim)


    今天复习最小生成树算法。

    最小生成树指的是在一个图中选择n-1条边将所有n个顶点连起来,且n-1条边的权值之和最小。形象一点说就是找出一条路线遍历完所有点,不能形成回路且总路程最短。

    Kurskal算法

    kurskal算法的核心思想是将边按权值排序,每次选出权值最小的边,只要不会形成回路就加入结果集,如果形成了回路就不选这条边,类似于贪心的思想。

    具体做法是先将边按权值升序排序然后依次遍历,判断是否形成回路的方法是将点划分不同集合,初始状态每个点为一个集合,只有当一条边的两端分别位于两个集合时才选择这条边,否则就丢弃,这里用到了并查集来处理集合关系,可以参考这篇博文:https://www.cnblogs.com/czc1999/p/11823820.html,选择一条边之后要将两个点合并到同一集合。

    模板题: https://www.luogu.com.cn/problem/P3366

    代码如下,还是比较好理解的,时间复杂度为(O(MlogM))

    #include <iostream>
    #include <algorithm>
    using namespace std;
    
    int n, m;
    int pre[5005];
    int Find(int x) { return pre[x] == x ? pre[x] : pre[x] = Find(pre[x]); }
    
    struct line
    {
    	int from, to, val;
    	bool operator<(line a) { return val < a.val; }
    }Arr[200005];
    
    int Kruskal()
    {
    	sort(Arr, Arr + m);
    	int cnt = n, res = 0;
    	for (int i = 0; i < m && cnt > 1; i++)
    	{
    		int x = Find(Arr[i].from), y = Find(Arr[i].to);
    		if (x != y)//x和y不在一个集合
    		{
    			pre[x] = y;//合并两个集合
    			cnt--;//找到了一条边
    			res += Arr[i].val;
    		}
    	}
    	return cnt == 1 ? res : -1;//如果cnt不等于1说明没找到n-1条边,无最小生成树
    }
    
    int main() {
    	cin >> n >> m;
        for (int i = 0; i < n; i++)pre[i] = i;
    	for (int i = 0; i < m; i++)cin >> Arr[i].from >> Arr[i].to >> Arr[i].val;
    	int res = Kruskal();
    	if (-1 == res)cout << "orz"; else cout << res;
    	return 0;
    }
    

    prim算法

    Kruskal算法是选择边的思路,而prim算法通过选择点来得到最小生成树,有点类似于Dijkstra的感觉,初始源点可以任意选择,把点划分成已选择的点和未选择的点两个集合,需要维护一个dis数组代表每个点到已选择点的最短距离,不断把dis最小的未选择点加入已选择点集合然后更新dis,当所有点都变成已选择点(dis==0)的时候就得到了最小生成树。

    代码如下,真的是跟Dijkstra很像了。

    #include <iostream>
    #include <algorithm>
    using namespace std;
    
    #define inf 2000000000
    int n, m;
    int dis[5005];
    int total = 0;
    int head[5005], val[400005], to[400005], nextL[400005];
    
    void AddLine(int a, int b, int c)
    {
    	total++;
    	to[total] = b;
    	val[total] = c;
    	nextL[total] = head[a];
    	head[a] = total;
    }
    
    int Prim()
    {
    	int res = 0;
    	for (int i = 2; i <= n; i++)dis[i] = inf;
    
    	for (int i = 0; i < n; i++)//循环n次找n个点
    	{
    		int Min = inf, u = 1;
    		for (int j = 1; j <= n; j++)//找下一个最近的未选择点
    		{
    			if (dis[j] != 0 && dis[j] < Min)
    			{
    				Min = dis[j]; u = j;
    			}
    		}
    		if (Min == inf && u != 1)return -1;//如果遍历之后未选择点dis都为inf,说明该图是非连通图
    		res += dis[u];
    		dis[u] = 0;
    		for (int j = head[u]; j; j = nextL[j])//更新该点周围的dis
    		{
    			if (dis[to[j]] > val[j])dis[to[j]] = val[j];
    		}
    	}
    	return res;
    }
    
    int main() {
    	cin >> n >> m;
    	int a, b, c;
    	for (int i = 0; i < m; i++)
    	{
    		cin >> a >> b >> c;
    		AddLine(a, b, c);
    		AddLine(b, a, c);
    	}
    	int res = Prim();
    	if (-1 == res)cout << "orz"; else cout << res;
    	return 0;
    }
    

    堆优化

    既然prim也是要每次取dis最小的点,当然也和Dijkstra一样可以用堆优化,朴素的prim时间复杂度为(O(n^2)),优化后达到(O(nlogn)),代码如下:

    #include <iostream>
    #include <algorithm>
    #include <queue>
    using namespace std;
    
    #define inf 2000000000
    int n, m;
    int dis[5005];
    int total = 0;
    int head[5005], val[400005], to[400005], nextL[400005];
    bool mark[5005];
    
    void AddLine(int a, int b, int c)
    {
    	total++;
    	to[total] = b;
    	val[total] = c;
    	nextL[total] = head[a];
    	head[a] = total;
    }
    
    typedef pair<int, int> p;
    priority_queue<p, vector<p>, greater<p>> q;
    
    int Prim()
    {
    	int res = 0;
    	for (int i = 2; i <= n; i++)dis[i] = inf;
    	q.push(p(0, 1));
    	while (!q.empty())
    	{
    		int u=q.top().second,v=q.top().first;
    		q.pop();
    		if (mark[u])continue;
    		mark[u] = true;
    		dis[u] = 0;
    		res += v;
    		for (int i = head[u]; i ; i=nextL[i])
    		{
    			if (dis[to[i]] > val[i])
    			{
    				dis[to[i]] = val[i];
    				q.push(p(val[i], to[i]));
    			}
    		}
    	}
    	return res;
    }
    
    int main() {
    	cin >> n >> m;
    	int a, b, c;
    	for (int i = 0; i < m; i++)
    	{
    		cin >> a >> b >> c;
    		AddLine(a, b, c);
    		AddLine(b, a, c);
    	}
    	int res = Prim();
    	if (-1 == res)cout << "orz"; else cout << res;
    	return 0;
    }
    

    所以选择使用哪个算法就看是点多还是边多了。

  • 相关阅读:
    bzoj-2748 2748: [HAOI2012]音量调节(dp)
    bzoj-2338 2338: [HNOI2011]数矩形(计算几何)
    bzoj-3444 3444: 最后的晚餐(组合数学)
    codeforces 709E E. Centroids(树形dp)
    codeforces 709D D. Recover the String(构造)
    codeforces 709C C. Letters Cyclic Shift(贪心)
    codeforces 709B B. Checkpoints(水题)
    codeforces 709A A. Juicer(水题)
    Repeat Number
    hdu 1003 Max Sum (动态规划)
  • 原文地址:https://www.cnblogs.com/LiveForGame/p/12296967.html
Copyright © 2020-2023  润新知