最小生成树和单源最短路径

最小生成树和单源最短路径


n个顶点的最小生成树只有n-1条边。

最小生成树能够保证整个拓扑图的所有路径之和最小,但不能保证任意两点之间是最短路径。

最短路径是从一点出发,到达目的地的路径最小。

最小生成树构成后所有的点都被连通,而最短路只要到达目的地走的是最短的路径即可,与所有的点连不连通没有关系。

1 最小生成树

最小生成树:构造连通网的生成树,且代价最小。

Prim算法:

  1. 找到任意一个点,以这个点为起点开始生成树
  2. 不断加入与树相邻且权值最小的边和相应的顶点
  3. 重复第2步直到全部顶点录入完毕,成为最小生成树

第2步的关键是在未收录的顶点中找到与树相邻并离树最近的顶点.
加入后,在剩下的顶点中继续寻找权值最小的边

在这里插入图片描述

1.1 书本代码:

起点从0开始

时间复杂度为O(n2)

const int MAX = 10e5+1;
const int NIL = -1;

const int WHITE = 0;//表示未访问
const int GRAY = 1;//表示访问中
const int BLACK = 2;//表示已访问

int m[101][101];
int n;
int d[MAX];//记录顶点内边中,权值最小的边的权值
int p[MAX];//记录MST中顶点v的父节点
int color[MAX];//记录访问状况
int prim()
{
	int u,minv;
	//初始化
	for(int i=0;i<n;i++)
	{
		d[i] = MAX;
		p[i] = -1;
		color[i] = WHITE;
	}
    
	d[0] = 0;
	while(1)
	{
        //取最小权值的边
		minv = MAX;
		u = -1;
		for(int i=0;i<n;i++)
		{
			if(d[i]<minv&&color[i]!=BLACK)
			{
				u = i;
				minv = d[i];
			}
		}
		if(u == -1)
			break;
		color[u] = BLACK;
		//维护 
		for(int v=0;v<n;v++)
		{
			if(color[v]!=BLACK&&m[u][v]!=MAX)//未访问且通
			{
				if(m[u][v]<d[v])
				{
					d[v] = m[u][v];
					p[v] = u;
					color[v] = GRAY;
				}
			}
		}
	}
    
    //返回最小生成树各边权值总和
	int sum = 0;
	for(int i=0;i<n;i++)
		if(p[i]!=-1)
			sum+=m[i][p[i]];
	return sum;
}

1.2 精简代码

时间复杂度为O(n2)

void prim()
{
    memset(dis,127/3,sizeof(dis));//初始化
    dis[0]=0;
    for(int i=0;i<n;++i)
    {
        int k=-1;
        for(int j=0;j<n;j++)//找出权值最小的点
            if(!v[j]&&(k==-1||dis[j]<dis[k]))
                k=j;
        v[k]=1;//加入集合
        for(int j=0;j<n;j++)//松弛
            if(!v[j]&&a[k][j]<dis[j])
                dis[j]=a[k][j];
    }
    for(int i=0;i<n;i++)
		sum+=dis[i];
    cout<<sum<<endl;
}

1.3 优化代码

利用优先队列管理权值,配合邻接表,降低时间复杂度

时间复杂度:O(elogn)

代码:

const int MAX = 10e5+1;
int v[MAX];//记录访问状况

vector<pair<int,int> >adj[MAX];
priority_queue<pair<int,int> >q;
//pair的first为权值,second为通往的点

void prim()
{
	memset(dis,127/3,sizeof(dis));//初始化
    dis[0]=0;
    q.push(make_pair(0,0));
    while(!q.empty())
    {
    	pair<int,int>f = q.top();q.pop();
    	int u = f.second;
        v[u]=1;//加入集合
        if(dis[u]<f.first*(-1))
			continue;
			
        for(int j=0;j<adj[u].size();j++)//松弛
        {
        	int v0 = adj[u][j].first;
        	if(v[v0] == 1)
				continue;
        	if(adj[u][j].second<dis[v0])
        	{
        		dis[v0] = adj[u][j].second;
                //优先队列默认最大值,所以要乘以-1
				q.push(make_pair(dis[v0]*(-1),v0));
			}
		}    
    }
    int sum = 0;
	for(int i=0;i<n;i++)
        sum+=dis[i];
	cout<<sum<<endl;
}

2 单源最短路径

单源最短路径即以某顶点为起点,求该顶点到各顶点的最短路径上个边权值的总和。

dijkstra算法:

此算法与prim算法思想非常相似,同时不能处理负权值的图。

  1. 初始化两个集合(S,U),S为只有初试顶点A的集合,U为其他顶点
  2. 如果U不为空,取出最小顶点D
    • 将D加入S
    • 更新通过顶点D到达U所有点的距离(如果小于)
    • 重复

在这里插入图片描述

2.1 书本代码

起点从0开始

时间复杂度:O(n2)

const int MAX = 10e5+1;
const int NIL = -1;
const int WHITE = 0;
const int GRAY = 1;
const int BLACK = 2;


int m[101][101];
int n;
int d[MAX];//记录起点到v的最短路径权值
int p[MAX];//记录最短路径树中顶点v的父节点
int color[MAX];//记录访问状况
int dijkstra()
{
	int u,minv;
	//初始化
	for(int i=0;i<n;i++)
	{
		d[i] = MAX;
		p[i] = -1;
		color[i] = WHITE;
	}
	d[0] = 0;
	while(1)
	{
        //找最小权值
		minv = MAX;
		u = -1;
		for(int i=0;i<n;i++)
		{
			if(minv>d[i]&&color[i]!=BLACK)
			{
				u = i;
				minv = d[i];
			}
		}
		if(u == -1)
			break;
		color[u] = BLACK;
		
		//维护 
		for(int v=0;v<n;v++)
		{
			if(color[v]!=BLACK&&m[u][v]!=MAX)
			{
				if(m[u][v]+d[u]<d[v])
				{
					d[v] = m[u][v]+d[u];
					p[v] = u;
					color[v] = GRAY;
				}
			}
		}
	}
		
	for(int i=0;i<n;i++)
		cout<<i<<" "<<(d[i]==MAX?-1:d[i])<<endl;
}

2.2 精简代码

时间复杂度:O(n2)

void dijkstra()
{
    memset(dis,127/3,sizeof(dis));//初始化
    dis[0]=0;
    for(int i=0;i<n;++i)
    {
        int k=-1;
        for(int j=0;j<n;j++)//找出距离最近的点
            if(!v[j]&&(k==-1||dis[j]<dis[k]))
                k=j;
        v[k]=1;//加入集合
        for(int j=0;j<n;j++)//松弛
            if(!v[j]&&dis[k]+a[k][j]<dis[j])
                dis[j]=dis[k]+a[k][j];
    }
    for(int i=0;i<n;i++)
		cout<<i<<" "<<dis[i]<<endl;
}

2.3 优化代码

利用优先队列管理权值,配合邻接表,降低时间复杂度

时间复杂度:O((e+n)logn)

代码:

const int MAX = 10e5+1;
int v[MAX];//记录访问状况

vector<pair<int,int> >adj[MAX];
priority_queue<pair<int,int> >q;
//pair的first为权值,second为通往的点

void dijkstra()
{
	memset(dis,127/3,sizeof(dis));//初始化
    dis[0]=0;
    q.push(make_pair(0,0));
    while(!q.empty())
    {
    	pair<int,int>f = q.top();q.pop();
    	int u = f.second;
        v[u]=1;//加入集合
        if(dis[u]<f.first*(-1))
			continue;
			
        for(int j=0;j<adj[u].size();j++)//松弛
        {
        	int v0 = adj[u][j].first;
        	if(v[v0] == 1)
				continue;
        	if(dis[u] + adj[u][j].second<dis[v0])
        	{
        		dis[v0] = dis[u] + adj[u][j].second;
                //优先队列默认最大值,所以要乘以-1
				q.push(make_pair(dis[v0]*(-1),v0));		
			}
		}      
    }
	for(int i=0;i<n;i++)
		cout<<i<<" "<<(dis[i]==MAX?-1:dis[i])<<endl;
}

3 总结

最小生成树常用算法

  1. P算法
  2. K算法

最短路径常用算法

  1. D算法:特定两点之间的最短路径
  2. F算法:可以算出所有顶点两两之间的最短路径
  3. 深度优先和广度优先:找出两点之间的所有路径,比较得出结果

遇到求所有路径之和最小的问题用最小生成树&并查集解决;

遇到求两点间最短路径问题的用**最短路,**即从一个城市到另一个城市最短的路径问题。

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值