贪心算法之prim算法/Kruskal算法(最小生成树)

n个节点最少n-1条边能构成连通图(n个节点,其中任意两个节点都连通的图),最多n(n-1)/2边。树是没有环路的连通图
n个节点,n-1条边组成的权值最小的树为最小生成树。

prim算法
最小生成树算法,时间复杂度T(n)=O(V^2) V为顶点数目,受边的影响不大,适合于边比较多的图(稠密图),在稀疏图时,Kruscal复杂度更低,我们可以使用堆优化的prim算法达到与Kruscal一样的复杂度。

prim算法设计思路(O(N^2))
1.任意选定一点s(通常选择第一个点),设集合S={s}
2.从不在集合S的点中选出一个点j使得其与S内的某点的距离最短,则(i,j)就是生成树上的一条边,同时将j点加入S
3.转到2继续进行,直至所有点都已加入S集合
在这里插入图片描述

#include<stdio.h>

#define MAXN 100
#define INF 100000;
int G[MAXN][MAXN];


void prim(int n)
{
	int closeset[n]={0};    //记录不在点集s中的节点在s中的最近节点
	int lowcost[n],used[n]; //记录不在s中的节点到s的最短距离,即到最近节点的权值
	int result=0;           //标记节点是否被访问过

    //首先将节点0放入点集s
	for(int i=0;i<n;i++)
	{                       //closeset[2]=1表示节点2(点集s外)的在s中的最近节点为节点1(在s中) 
		closeset[i]=0;      //将所有s外的节点在s中的最近节点设为节点0(包括节点0)
		lowcost[i]=G[0][i]; //所有s外的节点到s的最短距离设置为节点0到它们的距离
		used[i]=0;          //所有节点均设置为未被访问过
	}
    used[0]=1;//将第1个点加入s中
	for(int i=1;i<n;i++)
	{
		int j=0;
		for(int k=0;k<n;k++)
		{
			if(!used[k] && (lowcost[k]<lowcost[j]))
			{
				j=k;
			}
		}
		used[j]=1;
		for(int k=0;k<n;k++)
		{
			if((!used[k])&&(G[j][k]<lowcost[k]))
			{
				lowcost[k]=G[j][k];
				closeset[k]=j;
			}
		}
	}
	for(int i=1;i<n;i++)
	{
		printf("node:%d->%d distance:%d\n",closeset[i],i,lowcost[i]);
		result+=lowcost[i];
	}
	printf("sum:%d\n",result);
}

int main()
{
	int n,m;                  //n为节点个数 m为边数
	int x,y,dis;              //节点x到节点y的距离dis
	scanf("%d %d",&n,&m);
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<n;j++)
		{
			G[i][j]=INF;
		}
	}
	for(int j=0;j<m;j++)
	{
		scanf("%d %d %d",&x,&y,&dis);
		G[x][y]=dis;
		G[y][x]=dis;
	}
	prim(n);

}

执行结果:
在这里插入图片描述

堆优化的prim算法(T(n)=O(nlogn)):
在这里插入图片描述

#include <iostream>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>
#include <vector>
#define N 100
#define INF 1<<10
using namespace std;

int G[N][N]={0};//距离矩阵,G[i][j]表示顶点i和顶点j的距离
int visited[N]={0};//visited[i]=1表示某个顶点已经放入顶点集s中
int lowcost[N];//lowcost[i]表示顶点i与顶点集s(s中的顶点)的最短距离
int closeset[N];//closeset[i]=j表示顶点i在顶点集s中的最近顶点为顶点j
int flag=0;//最终判断整个图是否连通
int result=0;
int n,m;//n表示顶点个数,m表示边数

struct node
{
	int pos;//顶点的位置
	int len;//以顶点为终点的边长
};

vector<node> adjacent[N];//adjacent[i]表示顶点i的邻接节点

//排序方式为小顶堆(升序)
struct cmp
{
	bool operator()(node a,node b)
	{
		return a.len>b.len;
	}
};

void prim()
{
	int s;
	priority_queue<node,vector<node>,cmp> buf;//优先队列priority_queue<type,container,functional>
	lowcost[0]=0;//将0号顶点放入s中,则0号顶点到点集s(中的0号顶点)的距离为0
	closeset[0]=0;//定义0号顶点的最近顶点为0号顶点
	node temp={0,0};//0号顶点,距离为0
    buf.push(temp);//将0号顶点放入s中
    while(!buf.empty())
    {
    	s=buf.top().pos;
    	buf.pop();
    	visited[s]=1;//标记访问过0号顶点
        int NodeNum=adjacent[s].size();//位置为s处顶点的临近顶点数目
    	for(int i=0;i<NodeNum;i++)
    	{
    		int p=adjacent[s][i].pos;//依次访问与位置s处顶点的所有临近顶点的位置
    		if(visited[p]) continue;
    		int templen=adjacent[s][i].len;//访问s出顶点与这些临近顶点的边
    		if(templen<lowcost[p])//找到最小边长
    		{
    			lowcost[p]=templen;//更新位置p与点集s的最短距离
    			closeset[p]=s;
    			temp.len=lowcost[p];
    			temp.pos=p;
    			buf.push(temp);
    		}
    	}

    }
    for(int i=1;i<n;i++)
    {
    	if(closeset[i]==-1)
    	{
    		flag=1;
    		break;
    	}
    	result+=lowcost[i];
    }
}

int main()
{
	cin>>n>>m;
	int x,y,dis;//顶点x和顶点y之间的距离为dis
    node a;
    for(int i=0;i<m;i++)
    {
    	cin>>x>>y>>dis;
    	G[x-1][y-1]=dis;//输入时从1开始编码的顶点i和顶点j从0开始编码则为i-1和j-1
    	a.pos=y-1;
    	a.len=dis;
        adjacent[x-1].push_back(a);//保存顶点x-1的邻接节点
        G[y-1][x-1]=dis;
        a.pos=x-1;
        a.len=dis;
        adjacent[y-1].push_back(a);
    }
    for(int i=0;i<n;i++)
    {
    	closeset[i]=-1;
    	lowcost[i]=INF;
    }
    prim();
    if(flag) cout<<"orz"<<endl;
    else cout<<result<<endl;

}

执行结果如下:
在这里插入图片描述
kruskal算法(简单来说就是n个孤立的节点,每次都选择最短边,如果选择的边不会使原来的图成环就加入(即连接两个点))
(1)设一个有n个顶点的连通网络为G(V,E),最初先构造一个只有n个顶点,没有边的非连通图T{V,Ø},图中每个顶点自成一个连通分量;
(2)当在E中选择一条具有最小权值的边时,若该边的两个顶点落在不同的连通分量上,则将此边加入到T中;否则,即这条边的两个顶点落在同一个连通分量上,则将此边舍去(此后永不选用这条边),重新选择一条权值最小的边;
(3)如此重复下去,直到所有顶点在同一个连通分量上为止。

#include<iostream>
#include<algorithm>
#define N 1000
using namespace std;
int n,m;//n为顶点个数 m为边数
int counter=0;//已经连接了的边数
int result=0;
int ConnectedNode[N];//求某个节点的相连节点

//通过每次选择最短边 
struct edge
{
	int sNode,eNode,dis;//边有起点、终点、边长属性
};

bool cmp(edge &e1,edge &e2)//按照边长的升序排序规则
{
	return e1.dis<e2.dis;
}

int FindConnect(int x)//寻找与某个节点相连的节点
{
	if(ConnectedNode[x]!=x)
	{
		return FindConnect(ConnectedNode[x]);
	}
	else
	{
		return x;
	}
}

void AddSet(int x,int y)//如果两个节点不相连,将这两个节点相连(加入同一个集合中)
{
	ConnectedNode[FindConnect(y)]=FindConnect(x);
}

int main()
{
	cin>>n>>m;
	edge e[m+1];
	for(int i=1;i<=m;i++)
	{
		cin>>e[i].sNode>>e[i].eNode>>e[i].dis;
	}
	for(int i=1;i<=n;i++)//初始化每个节点的相连节点为自己
	{
		ConnectedNode[i]=i;
	}
	sort(e+1,e+1+m,cmp);//按照边长升序排列
	for(int i=1;i<=m;i++)
	{
		if(counter==n-1) break;//最小生成树n个节点n-1条边,当计数器=n-1时退出循环
		if(FindConnect(e[i].sNode)!=FindConnect(e[i].eNode))//判断两个节点是否在同一个集合中
		{
			AddSet(e[i].sNode,e[i].eNode);
			result+=e[i].dis;
			counter++;
		}
	}
	cout<<result<<endl;  
}
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值