最小生成树和最短路

这周是高产的一周,今天来讲讲图论:最小生成树和最短路:
******************这是一条分割线
最小生成树:一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。 最小生成树可以用kruskal(克鲁斯卡尔)算法或prim(普里姆)算法求出。
效果:最小生成树能够保证整个拓扑图的所有路径之和最小,但不能保证任意两点之间是最短路径。
****************这是一条分割线
kruskal(克鲁斯卡尔)算法:
此算法可以称为“加边法”,初始最小生成树边数为0,每迭代一次就选择一条满足条件的最小代价边,加入到最小生成树的边集合里。这是一个贪心的过程。
过程:
1.首先把所有的边权从小到大排序;
2. 把图中的n个顶点看成独立的n棵树组成的森林;
3. 选出权值最小两个点形成的边,u,v(u,v应属于不同的树),加入到生成树中;
4. 重复3的过程,直到所有点包括到生成树里或者生成树有n-1条边;
最小生成树和最短路_第1张图片
****************这是一条分割线
Prim算法:
此算法可以称为“加点法”,每次迭代选择代价最小的边对应的点,加入到最小生成树中。算法从某一个顶点s开始,逐渐长大覆盖整个连通网的所有顶点。
过程:
1.令所有点的集合为V,任选一个点的的集合为U,未选到的点集合为V-U=VV;
2.在U和VV两个集合能组成的边中选一条权值最小的边加入到生成树中,并且更新集合;
3.重复2的过程,直到所有点包括到生成树里或者生成树有n-1条边;
最小生成树和最短路_第2张图片
****************这是一条分割线

prime算法时间复杂度o(n^2),适合稠密图,可以用二叉堆优化到0(m*log n),但是不如Kruskal算法简单。

#include
#include
#include
#include
#include
#include
#include 
#include
#include
#define ll long long 
#define INF 0x3F3F3F3F 
#define MOD 1000000007

using namespace std; 
const int MAXN=2e5+7;
int a[3010][3010],,d[3010],n,m,ans;
bool vis[3010];
bool prime(){
	memset(d,0x3F3F3F3F,sizeof(d));
	memset(v,0,sizeof(v));
	d[1] = 0;
	for(int i =1 ;i < n ;i++){
		int x = 0;
		for(int j = 1;j <= n;j++){
			if(!v[j] && (x == 0 || d[j] < d[x])){
				x = j;
			}
		}
		v[x] = 1;
		for(int y = 1;y <= n;y++){
			if(!v[y])d[y] = min(d[y],a[x][y]);
		}
	}
}
int main()
{
	cin>>n>>m;
	memset(a,0x3F3F3F3F,sizeof(a));
	for(int i = 1;i <= n;i++){
		int x , y,z;
		cin>>x>>y>>z;
		a[y][x] = a[x][y] = min(a[x][y],z);
	}
	prime();
	for(int i =2;i<=n;i++){
		ans += d[i];
	}
	cout<<ans<<endl;
	return 0;
}

Kruskal算法
时间复杂度O(m* log m)

#include
#include
#include
#include
#include
#include
#include 
#include
#include
#define ll long long 
#define INF 0x3F3F3F3F 
#define MOD 1000000007

using namespace std; 
const int MAXN=2e5+7;
struct rec
{
	int x,y,z;
}edge[5e5+10];
int fa[1e5+10],n,m,ans;
bool operator < (rec a ,rec b){
	return a.z < b.z;
}
int get(int x){
	if(x == fa[x])return x;
	return fa[x] = get(fa[x]);
}
int main()
{
	cin >> n >> m;
	for(int i = 1;i <= m;i++){
		cin>>edge[i].x>>edge[i].y>>edge[i].z;
	}
	//按照边权排序
	sort(edge + 1 ,edge + 1 + m);
	//并查集初始化
	for(int i = 1; i <= m;i++ ){
		fa[i] = i;
	}
	//求最小生成树
	for (int i = 1; i <= m; i++)
	{
		int x = get(edge[i].x);
		int y = get(edge[i].y);
		if(x == y )continue;
		fa[x] = y;
		ans += edge[i].z;
	}
	cout<<ans<<endl;
	return 0;
}

*****************这是一条分割线

最短路

单源最短路径(single source shortest path ,SSPP问题),给定一张有向图G = (V,E),V是点集,E是边集,| V | = n ,| E | = m ,节点以[1,n]之间的连续整数编号,(x,y,z)表示一条从x出发,到达y,长度为z的有向边。设点1为起点,dist[i]表示从起点1到节点i的最短路径的长度。
Dijkstra算法:
过程:
1:初始化 dist[1]=0,其余节点的dist值为INF(无穷大);
2. 找出一个未被标记的,dist[x] 最小节点 x,然后标记节点x;
3. 扫描节点x的所有出边(x,y,z),若dist[x]+z < dist[y],则使用 dist[x]+z 更新dist[y];
4. 重复上述2-3的步骤,知道所有节点被标记;
*****************这是一条分割线
Dijkstra算法基于贪心思想,它只适用于所有边的长度都是非负数的图。当边长为非负数时,全局的最小值不可能再被其他的节点更新,故在第一步中选出的节点x必然满足:dist[x]已经是起点到x的最短路径。我们不断选择全局最小值进行标记和扩展,最终可得到起点1到每个节点的最短路径的长度。
*****************这是一条分割线

#include
#include
#include
#include
#include
#include
#include 
#include
#include
#define ll long long 
#define INF 0x3F3F3F3F 
#define MOD 1000000007

using namespace std; 
const int MAXN=2e5+7;
int a[3010][3010],d[3010],n,m;
bool v[3010];
void dijkstra(){
	//初始化dist,v数组
	memset(d,0x3f,sizeof(d));
	memset(v,0,sizeof(v));
	d[1] = 0;
	for(int i =1;i<n;i++){
		//找出未标记节点中dist最小的
		int x = 0;
		for(int j = 1;j<=n;j++){
			if(!v[j] && (x == 0 || d[j] < d[x])){
				x == j;
			}
		}
		v[x] = 1;
		//用全局最小节点x更新其他节点
		for(int y = 1; y<= n;y++){
			d[y] = min (d[y],d[x]+a[x][y]);
		}
	}
}
int main()
{
	cin >> n >>m;
	memset(a,0x3f,sizeof(a));
	//构建邻接链表
	for(int i =1;i<=n;i++){
		a[i][i] = 0;
	}
	for(int i = 1;i<=m;i++){
		int x,y,z;cin>>x>>y>>z;
		a[x][y] = min(a[x][y],z);
	}
	dijkstra();
	for(int i =1;i<=n;i++){
		cout<<d[i]<<endl;
	}
	return 0;
}
//以上可用二叉堆对dist数组进行维护,可在o(m*log n)时间内实现

****************这是一条分割线

Bellman-Ford 和 SPFA 算法

****************这是一条分割线

给定一张有向图,若对图中的某一条(x,y,z),有dist[y]<=dist[x]+z成立,则该边慢走三角形不等式。若所有的边都满足三角形不等式,则dist数组就 是所求的最短路。
Bellman-Ford 算法过程:
1.扫描所有边(x,y,z),若dist[y] > dist[x]+z,则用 dist[x]+z更新dist[y]
2.重复上述步骤,知道没有更新操作发生。
Bellman-Ford 算法时间复杂度o(mn)
SPFA 算法就是“队列优化的 Bellman-Ford 算法”
过程
1.建立一个队列,最初队列中只含有起点1;
2.取出队列头部节点x,扫描它的所有出边(x,y,z),若dist[y] > dist[x]+z,则用 dist[x]+z更新dist[y].同时,若y不在队列中,则把y入队。
3.重复上述步骤,知道队列为空。
时间复杂度o(k
m),k为一个随机常数,但特殊构造的图上,很有可能退化成o(n*m)。
****************这是一条分割线

#include
#include
#include
#include
#include
#include
#include 
#include
#include
#define ll long long 
#define INF 0x3F3F3F3F 
#define MOD 1000000007

using namespace std; 
const int MAXN=2e5+7;
const int N = 100010, M = 1000010;
int head[N],ver[N],edge[M],Next[M],d[N];
int n,m,tot;
queue<int>q;
bool v[N];
void add(int x,int y,int z){
	ver[++tot] = y;edge[tot] = z;
	Next[tot] = head[x],head[x] = tot;
}
void spfa(){
	memset(d,0x3f,sizeof(d));
	memset(v,0,sizeof(v));
	d[1] = 0,v[1] = 1;
	q.push(1);
	while(q,size()){
		int x = q.front();q.pop();
		v[x] = 0;
		for(int i = head[x];i;i=Next[i]){
			int y = ver[i],z = edge[i];
			if(d[y] > d[x]+z){
				d[y] = d[x]+z;
				if(!v[y])q.push(y),v[y] =1;
			}
		}
	}
}
int main()
{
	cin >> n >>m;
	for(int i = 1;i<=m;i++){
		int x,y,z;cin>>x>>y>>z;
		add(x,y,z);
	}
	spfa();
	for(int i =1;i<=n;i++){
		cout<<d[i]<<endl;
	}
	return 0;
}

****************这是一条分割线
任意两点间的最短路:Floyd算法:dp大法好,
动态转移方程:d[k][i][j] = min(d[k-1][i][j],d[k-1][i][k]+d[k-1][k][j])

#include
#include
#include
#include
#include
#include
#include 
#include
#include
#define ll long long 
#define INF 0x3F3F3F3F 
#define MOD 1000000007

using namespace std; 
const int MAXN=2e5+7;
int d[310][310],n,m;

int main()
{
	cin>>n>>m;
	memset(d,0x3F3F3F3F,sizeof(d));
	for(int i =1;i<=n;i++){
		d[i][i] = 0;
	}
	for(int i = 1;i<=m;i++){
		int x,y,z;cin>>x>>y>>z;
		d[x][y] = min(d[x][y],z);
	}
	//floyd算法
	for(k=1;k<=n;k++)  
    for(i=1;i<=n;i++)  
    for(j=1;j<=n;j++)  
    	d[i][j] = min(d[i][j],d[i][k]+d[k][j])
    for(int i =1 ;i<=n;i++){
    	for(int j =1;j<=m;j++){
    		cout<<d[i][j];
    	}
    	puts("");
	}
	return 0;
    
}

你可能感兴趣的:(最小生成树和最短路)