zoukankan      html  css  js  c++  java
  • 最小生成树算法总结(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;
    }
    

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

  • 相关阅读:
    线性同余方程模板( A+C*x=B(mod D) )
    线性同余方程组模板( x=r0(mod m0) )
    poj2891(线性同余方程组)
    BestCoder Round #63 (div.2)
    16.实现多个具有相同方法的接口和父类与实现接口有相同方法
    14.Java中的StringTokenizer类的使用方法
    13.Java为什么不支持多继承
    bootstrap使用基础
    Js 百分比进度条
    Js更改样式导致hover效果消失
  • 原文地址:https://www.cnblogs.com/LiveForGame/p/12296967.html
Copyright © 2011-2022 走看看