当前位置: 首页 > news >正文

数据结构与算法:最小生成树

前言

从图开始的每个算法都挺重要的,用途都很广。

一、最小生成树

1.内容

最小生成树是,在无向有权图中选择一些边,保证所有节点都连通且所有边的总权值最小

2.Kruskal算法——【模板】最小生成树

#include<bits/stdc++.h>
using namespace std;

//并查集
vector<int>father; 

void build(int n)
{
	father.resize(n+1);
	for(int i=1;i<=n;i++)
	{
		father[i]=i;
	}
}

int find(int i)
{
	if(i!=father[i])
	{
		father[i]=find(father[i]);
	}
	return father[i];
}

bool Union(int x,int y)//Union还要负责判断是否为环 -> 在同一集合 
{
	int fx=find(x);
	int fy=find(y);
	if(fx!=fy)
	{
		father[fx]=fy;
		return true;
	}
	else
	{
		return false;
	}
}

static bool cmp(vector<int>&a,vector<int>&b)
{
	return a[2]<b[2];
}

void solve(int n,int m,vector<vector<int>>&edges)
{
	build(n);
	
	//先按边权从小到大排序
	sort(edges.begin(),edges.end(),cmp); 
	
	int ans=0;
	int edgeCnt=0;
	for(int i=0;i<m;i++)
	{
		if(Union(edges[i][0],edges[i][1]))
		{
			edgeCnt++;
			ans+=edges[i][2];
		}
	}
	
	if(edgeCnt==n-1)
	{
		cout<<ans;	
	} 
	else
	{
		cout<<"orz";
	}
}

void read()
{
	int n,m;
	cin>>n>>m;
	vector<vector<int>>edges(m,vector<int>(3));
	for(int i=0;i<m;i++)
	{
		cin>>edges[i][0]>>edges[i][1]>>edges[i][2];
	}
	
	solve(n,m,edges);
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	read();
	return 0;	
} 

Kruskal就是用来最小生成树的算法,其实还有个prim算法,但过程太过复杂了,所以这篇里没写,以后看情况要是真的必须掌握的话再说吧。

Kruskal算法无需建图,但需要借助并查集数据结构与算法:并查集。过程就是先按边权从小到大排序,然后在保证不生成环的情况下,逐渐合并节点,统计总边权。

其中,可以优化Union函数,加入判断是否会生成环,即在同一个集合里。具体方法是让其返回一个bool值,当fx和fy不相等时,在合并后返回true,表示不生成环;否则不合并,返回false,表示会生成环。

二、题目

1.买礼物

#include<bits/stdc++.h>
using namespace std;

vector<int>father; 

void build(int n)
{
	father.resize(n+1);
	for(int i=0;i<n;i++)
	{
		father[i]=i;
	}
}

int find(int i)
{
	if(i!=father[i])
	{
		father[i]=find(father[i]);
	}
	return father[i];
}

bool Union(int x,int y)
{
	int fx=find(x);
	int fy=find(y);
	if(fx!=fy)
	{
		father[fx]=fy;
		return true;
	}
	
	return false;
}

static bool cmp(vector<int>&a,vector<int>&b)
{
	return a[2]<b[2];
}

void solve(int a,int n,int cnt,vector<vector<int>>&edges)
{
	build(cnt);
	
	sort(edges.begin(),edges.end(),cmp);
	
	int ans=0;
	for(int i=0;i<cnt;i++)
	{
		if(Union(edges[i][0],edges[i][1]))
		{
			ans+=edges[i][2];
		}
	}
	
	cout<<ans;
}

void read()
{
	int a,n;
	cin>>a>>n;
	vector<vector<int>>edges(n*n+n+1,vector<int>(3));
	int cnt=0;
	for(int i=1;i<=n;i++,cnt++)
	{
		edges[cnt][0]=0;
		edges[cnt][1]=i;
		edges[cnt][2]=a;
	}
	for(int i=0;i<n;i++)
	{
		for(int j=0,k;j<n;j++,cnt++)
		{
			edges[cnt][0]=i;
			edges[cnt][1]=j;
			cin>>k;
			edges[cnt][2]=k==0?a:k;
		}
	}
	
	solve(a,n,cnt,edges);
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	read();
	return 0;
}

这个题唯一的难点就在于单独买一个时的处理,略微思考就能想到,只需要加一个零号节点,让其与所有节点相连,其中每条边就是对应节点自己的权重即可。

处理好数据后就是Kruskal的模板了。

2.检查边长度限制的路径是否存在

class Solution {
public:

    vector<int>father;

    vector<bool> distanceLimitedPathsExist(int n, vector<vector<int>>& edgeList, vector<vector<int>>& queries) {
        int m=edgeList.size();
        int q=queries.size();        

        build(n);

        //记应该填的位置
        for(int i=0;i<q;i++)
        {
            queries[i].push_back(i);
        }

        //根据limit排序
        sort(queries.begin(),queries.end(),
        [&](vector<int>&a,vector<int>&b){return a[2]<b[2];});
        //根据边权排序
        sort(edgeList.begin(),edgeList.end(),
        [&](vector<int>&a,vector<int>&b){return a[2]<b[2];});

        vector<bool>ans(q);
        for(int i=0,j=0;i<q;i++)
        {
            //合并小于limit的边
            for(;j<m&&edgeList[j][2]<queries[i][2];j++)
            {
                Union(edgeList[j][0],edgeList[j][1]);
            }
            ans[queries[i][3]]=isSameSet(queries[i][0],queries[i][1]);
        }

        return ans;
    }

    void build(int n)
    {
        father.resize(n);
        for(int i=0;i<n;i++)
        {
            father[i]=i;
        }
    }

    void Union(int x,int y)
    {
        int fx=find(x);
        int fy=find(y);
        if(fx!=fy)
        {
            father[fx]=fy;
        }
    }

    int find(int i)
    {
        if(i!=father[i])
        {
            father[i]=find(father[i]);
        }
        return father[i];
    }

    bool isSameSet(int x,int y)
    {
        return find(x)==find(y);
    }
};

 这个题就需要一点思考了,由于要求路径上每一条边的权值都小于limit,所以整体思路是,连接所有小于限制的边,生成最小生成树,然后查询要求的两节点是否连通,即在同一集合。

所以考虑先按limit从小到大给查询数组排序,注意为了之后往ans的对应位置输答案,这里要先往每个查询后加入到时候往ans里输的位置。之后遍历每条查询,合并小于limit的边,然后查询是否在同一集合即可。

3.繁忙的都市

#include<bits/stdc++.h>
using namespace std;

vector<int>father;

void build(int n)
{
	father.resize(n+1);
	for(int i=1;i<=n;i++)
	{
		father[i]=i;
	}
}

int find(int i)
{
	if(i!=father[i])
	{
		father[i]=find(father[i]);
	}
	return father[i];
}

bool Union(int x,int y)
{
	int fx=find(x);
	int fy=find(y);
	if(fx!=fy)
	{
		father[fx]=fy;
		return true;
	}
	return false;
}

void solve(int n,int m,vector<vector<int>>&edges)
{
	build(n);
	
	sort(edges.begin(),edges.end(),
	[&](vector<int>&a,vector<int>&b){return a[2]<b[2];});
	
	int cnt=0;
	int Max=0;
	for(int i=0;i<m;i++)
	{
		if(Union(edges[i][0],edges[i][1]))
		{
			cnt++;
			Max=max(Max,edges[i][2]);//最小生成树必是最小瓶颈树 -> 最大边权最小 
		}
		if(cnt==n-1)//最小生成树必是n-1条 
		{
			break;
		}
	}
	
	cout<<n-1<<" "<<Max;
}

void read()
{
	int n,m;
	cin>>n>>m;
	
	vector<vector<int>>edges(m,vector<int>(3));
	for(int i=0;i<m;i++)
	{
		cin>>edges[i][0]>>edges[i][1]>>edges[i][2];
	}
	
	solve(n,m,edges);
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	read();
	return 0;	
} 

 这个题有一个结论,就是最小生成树必是最小瓶颈树。最小瓶颈树就是在连通的情况下,要求最大边权最小,即这道题的第三个要求。而有了这个结论之后再思考,可以发现在最小生成树的情况下,这个要求的边数就是n-1。所以只需要统计边的最大值即可。

总结

怎么说呢,最小生成树非常重要。虽然这几个题看上去不是那么吓人,但通常这个还会放在新情境里和其他算法一起出现,那就比较恶心了。

END

相关文章:

  • PakePlus支持将vue/react等项目打包为跨平台桌面软件了
  • 高并发内存池
  • visual studio 中导入 benchmark
  • TouchSocket TcpService:构建高性能Tcp服务的终极利器
  • 某网关管理软件 9-12ping.php 命令执行漏洞(CVE-2025-1448)
  • YOLOv5
  • 基于 Milvus 和 BiomedBERT 的医学文献智能搜索系统
  • 如何通过iPaaS集成平台快速配置协议接口
  • vscode 源代码管理
  • Windows系统本地部署OpenManus对接Ollama调用本地AI大模型
  • Day 3
  • 快速入手-基于Django的主子表间操作mysql(五)
  • 学习111
  • VL开源模型实现文本生成图片
  • Python第六章06:列表的循环练习
  • 《白帽子讲 Web 安全》之开发语言安全深度解读
  • 17153. 班级活动(蓝桥杯-python)
  • CAN FD、传统CAN以及RS-485通信介绍
  • <C#> 详细介绍.net 三种依赖注入:AddTransient、AddScoped、AddSingleton 的区别
  • 如何在 Vue 项目中实现动态组件加载,有什么应用场景?
  • 长三角铁路今日预计发送418万人次,持续迎来出行客流高峰
  • 购车补贴、“谷子”消费、特色产品,这些活动亮相五五购物节
  • 解放日报:服务国家战略,加快建成科技创新高地
  • 辽宁省委书记、省长连夜赶赴辽阳市白塔区火灾事故现场,指导善后处置工作
  • TAE联手加州大学开发出新型核聚变装置:功率提升百倍,成本减半
  • 春暖花开,为何皮肤却闹起了小情绪?