最短路径(其一)

最短路径的常用方法一般有四种:Dijsktra算法、BF算法、SPFA算法、Floyd算法
其中,Dijsktra算法主要针对不存在负权的图,所以应用也更为广泛,下面首先介绍这个算法的两种形式
一、邻接矩阵形式

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

const int MAXV=1000;
const int INF=0x3fffffff;//不要使用0x7fffffff,这样两个数相加会超出范围

int n,G[MAXV][MAXV];
int d[MAXV];
bool vis[MAXV]={false};

void Dijkstra(int s)//s为任意起点
{
	//初始化距离数组 
	fill(d,d+MAXV,INF);
	d[s]=0;//非常重要的一步,不添加函数无法“启动 ” 
	
	for(int i=0;i<n;i++)//循环n次确保所有的结点都已经加入 
	{
		//寻找距离最短的顶点
		int u=-1,MIN=INF;
		for(int j=0;j<n;j++)
		{
			if(vis[j]==false&&d[j]<MIN)
			{
				u=j;
				MIN=d[j];
			}
		} 
		if(u==-1) return;//说明不连通,第一次找到的肯定是s点
		vis[u]=true;//加入点集,不会再改变对应的d[u] 
		
		
		for(int v=0;v<n;v++)//查看所有的点,看看有没有可以改变的,有就改变 
		{
			if(vis[v]==false&&G[u][v]!=INF&&d[u]+G[u][v]<d[v])
			{
				d[v]=d[u]+G[u][v];
			}
		} 
	}
} 

int main()
{
	cout<<"ok"<<endl;
}

二、邻接表形式

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

const int MAXV=1000;
const int INF=0x3fffffff;//不要使用0x7fffffff,这样两个数相加会超出范围

struct Node
{
	int v,w;//分别代表顶点和边权 
}; 


vector<Node> Adj[MAXV]; //储存邻接表
int n;
int d[MAXV];
bool vis[MAXV]={false};

void Dijkstra(int s)//s为任意起点
{
	//初始化距离数组 
	fill(d,d+MAXV,INF);

	//启动
	d[s]=0;
	
	for(int i=0;i<n;i++)//循环n次确保所有的结点都已经加入已经访问过的集合
	{
		//寻找距离最短的顶点(即为当前处理的顶点) 
		int u=-1,MIN=INF;
		for(int j=0;j<n;j++)
		{
			if(vis[j]==false&&d[j]<MIN)
			{
				u=j;
				MIN=d[j];
			}
		} 
		if(u==-1) return;//说明不连通,第一次找到的肯定是s点
		vis[u]=true;//加入点集,不会再改变对应的d[u] 
		
		//邻接表版本只有这里开始有点不一样,但都是想要遍历所有和u相连的结点 
		for(int j=0;j<Adj[u].size();j++)//查看所有的点,看看有没有可以改变的,有就改变 
		{
			int v=Adj[u][j].v;
			if(vis[v]==false&&d[u]+Adj[u][v].w<d[v])
			{
				d[v]=d[u]+Adj[u][v].w;
			}
		} 
	}
}

有时候,题目会让我们求得具体的最短路径
直接添加一个全局变量pre[],然后每次在优化的时候就记录当前结点的前驱结点
最后使用DFS遍历,注意一定要到起点然后才开始输出(毕竟是倒序的)

void DFS(int s,int v)//起点和终点
{
	if(v==s)
	{
		cout<<s<<endl;
		return;
	}
	DFS(s,pre[v]);//输出v前面的路径 
	cout<<v<<endl;//输出v 
} 

另外一些时候,题目会让我们输出最优的路径一共有几条
那么我们可以添加一个全局的num[]

for(int v=0;v<n;v++) 
{
	if(vis[v]==false&&G[u][v]!=INF)
	{
		if(d[u]+G[u][v]<d[v])
		{
			d[v]=d[u]+G[u][v];
			num[v]=num[u];
		}
		else if(d[u]+G[u][v]==d[v])
		{
			num[v]+=num[u];
		}
	}
}

通常题目不会太过直接,比如有时候会出现不止一条路径最短,那么接下来题目会有两种出法
(1)给边再增加一种权值(路费),需要两个新的数组cost[][]和c[]来记录,下面的例子以距离优先
只需要改优化的部分就可以了(毕竟我们还是需要遍历所有和当前结点连接起来的点)

	if(vis[v]==false&&G[u][v]!=INF)
	{
		if(d[u]+G[u][v]<d[v])
		{
			d[v]=d[u]+G[u][v];
			c[v]=c[u]+cost[u][v];
		}
		else if(d[u]+G[u][v]==d[v]&&c[u]+cost[u][v]<c[v])
		{
			c[v]=c[u]+cost[u][v];
		}
	}

给点增加权值(采购),需要两个新的数组weight[]和w[]

if(vis[v]==false&&G[u][v]!=INF)
{
	if(d[u]+G[u][v]<d[v])
	{
		d[v]=d[u]+G[u][v];
		w[v]=w[u]+weight[u][v];
	}
	else if(d[u]+G[u][v]==d[v]&&w[u]+weight[u][v]<w[v])
	{
		w[v]=w[u]+weight[u][v];
	}
}

如果上面的第二权值的计算方式更加复杂的话,上面的方法就需要我们在优化的时候更加的严谨,但是仍然很容易出错,所以就有了下面的Dijkstra+DFS算法

//具体的思路就是先记录最短路径,然后根据第二判断标准选择出最好的一条路径


//第一步使用Dijkstra算法,需要记录下所有的最短路径,存在数组里面,必须要用二维vector记录:vector<int> pre[MAXV]
if(d[u]+G[u][v]<d[v])
{
	d[v]=d[u]+G[u][v];
	pre[v].clear();
	pre[v].push_back(u);
} 
else if(d[u]+G[u][v]==d[v])
{
	pre[v].push_back(u); 
}
//前面的寻找最小的d[u]步骤是一样的 

//第二步使用DFS算法,根据第一步得到的数组求得最短路径
int optvalue;//最优值(第二标准)
vector<int> pre[MAXV];//第一步求得
vector<int> path,tempPath;

void DFS(int v)//v是当前访问结点
{
	if(v==st)//st为路径的起点
	{
		tempPath.push_back(v);
		int value=0; 
		//然后计算tempPath上value的值
		//边权之和
		for(int i=tempPath.size()-1;i>0;i--)//注意这里只要访问n-1条边 
		{
			int id=tempPath[i],idNext=tempPath[i-1];
			value+=V[id][idNext]//计算第二权值和 
		}
		
		//点权之和
		for(int i=tempPath.size()-1;i>=0;i++)
		{
			int id=tempPath[i];
			value+=W[id]//计算第二点权和 
		} 
		
		if(value/*优于*/optvalue)
		{
			optvalue=value;
			path=tempPath;
		} 
		tempPath.pop_back();
		return;
	} 
	tempPath.push_back(v);
	for(int i=0;i<pre[v].size();i++)
	{
		DFS(pre[v][i]);//递归前驱结点 
	}
	tempPath.pop_back();//最后要保证tempPath为空 
} 

题目应用
1.直接给出输入和输出的样例
输入样例:
第一行:顶点数n,边数m,起点,终点
后面m行:顶点1,顶点2,距离,价格(求最短距离,最少价格)
4 5 0 3
0 1 1 20
1 3 2 30
0 3 4 10
0 2 2 20
2 3 1 20
输出样例:具体的路径+最短路径距离+最少花费
0 2 3 3 40

(1)Dijkstra算法

#include <iostream>
#include <algorithm>
using namespace std;

const int MAXV = 510;
const int INF = 0x3fffffff;
int d[MAXV], pre[MAXV], c[MAXV];
int G[MAXV][MAXV], cost[MAXV][MAXV], n, m;
int bg, ed;
bool vis[MAXV] = { false };

void Dijkstra(int s)
{
	//数组们的初始化阶段 
	fill(d, d + MAXV, INF);
	fill(d, d + MAXV, INF);
	for (int i = 0; i < n; i++)//pre数组的初始化可以将所有顶点的前驱设为自己 
	{
		pre[i] = i;
	}

	//启动 
	d[s] = 0;
	c[s] = 0;

	//主要算法
	for (int i = 0; i < n; i++)//需要循环n次,每次的步骤都是 第一步,先找出d[]最小不为INF且没有访问过的顶点
	{
		//寻找(没有访问过,d[]不是INF的) 
		int u = -1, MIN = INF;
		for (int j = 0; j < n; j++)
		{
			if (vis[j] == false && d[j] < MIN)
			{
				u = j;
				MIN = d[j];
			}
		}
		vis[u] = true;
		if (u == -1) return;//说明没有找到 

		//优化
		for (int j = 0; j < n; j++)
		{
			if (G[u][j] != INF && vis[j] == false)
			{
				if (d[u] + G[u][j] < d[j])
				{
					d[j] = d[u] + G[u][j];
					c[j] = c[u] + cost[u][j];
					pre[j] = u;
				}
				else if (d[u] + G[u][j] == d[j] && c[u] + cost[u][j] < c[j])
				{
					c[j] = c[u] + cost[u][j];
					pre[j] = u;
				}
			}
		}


	}


}

void DFS(int nowvisit)
{
	if (nowvisit == bg)
	{
		cout << bg;
		return;
	}
	DFS(pre[nowvisit]);
	cout << " " << nowvisit;
}

int main()
{
	freopen("in.txt","r",stdin);
	cin>>n>>m>>bg>>ed;//输入顶点数,边数,开始结点,结束结点
	int u, v;
	fill(G[0], G[0] + MAXV * MAXV, INF);
	fill(cost[0],cost[0]+MAXV*MAXV,INF);
	for (int i=0; i<m; i++)//输入每一条边构建图
	{
		cin>>u>>v;
		cin>>G[u][v]>>cost[u][v];
		G[v][u] = G[u][v];//这里注意是有向图还是无向图,千万注意不要把v和u填反了 
		cost[v][u] = cost[u][v];
	}
	Dijkstra(bg);
	DFS(ed);
	cout << " " << d[ed] << " " << c[ed];

}

Dijsktra+DFS算法

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

const int MAXV=510;
const int INF=0x3fffffff;

int n,m,bg,ed;
int d[MAXV],minCost=INF;//显然,如果使用Dijsktra+DFS算法的话,就不需要c[]数组了 
int G[MAXV][MAXV],cost[MAXV][MAXV];
bool vis[MAXV]={false};
vector<int> pre[MAXV];//不需要初始化,每次更新前就自动清零 
vector<int> tempPath,path;//可以赋值,不能用普通的数组 

void Dijsktra(int s)
{
	fill(d,d+MAXV,INF);
	
	d[s]=0;
	
	for(int i=0;i<n;i++)
	{
		int u=-1,MIN=INF;
		for(int j=0;j<n;j++)
		{
			if(vis[j]==false&&d[j]<MIN)
			{
				u=j;
				MIN=d[j];
			}
		}
		if(u==-1) return;
		vis[u]=true;
		
		
		for(int v=0;v<n;v++)
		{
			if(vis[v]==false&&G[u][v]!=INF)
			{
				if(d[u]+G[u][v]<d[v])
				{
					d[v]=d[u]+G[u][v];
					pre[v].clear();
					pre[v].push_back(u);
				}
				else if(d[u]+G[u][v]==d[v])
				{
					pre[v].push_back(u);
				}
			}
		}
	}
}

void DFS(int v)
{
	if(v==bg)
	{
		tempPath.push_back(v);
		int tempCost=0;//储存临时的总权值 
		for(int i=tempPath.size()-1;i>0;i--)
		{
			int id=tempPath[i],idNext=tempPath[i-1];
			tempCost+=cost[id][idNext];
		}
		if(tempCost<minCost)
		{
			minCost=tempCost;
			path=tempPath;
		}
		tempPath.pop_back();//对应进数组,就要出数组,可以保证tempPath清零,以便重复使用 
		return;
	}
	tempPath.push_back(v);
	for(int i=0;i<pre[v].size();i++)//对于每一个前驱结点 
	{
		DFS(pre[v][i]);
	}
	tempPath.pop_back();
}

int main()
{
	freopen("in.txt","r",stdin);
	fill(G[0],G[0]+MAXV*MAXV,INF);
	fill(cost[0],cost[0]+MAXV*MAXV,INF);
	cin>>n>>m>>bg>>ed;
	int u,v;
	for(int i=0;i<m;i++)
	{
		cin>>u>>v;
		cin>>G[u][v]>>cost[u][v];
		G[v][u]=G[u][v];//这里注意一下是无向图还是有向图 
		cost[v][u]=cost[u][v];
	}
	
	Dijsktra(bg);
	DFS(ed);
	int first=1;
	for(int i=path.size()-1;i>=0;i--)
	{
		if(first)
		{
			cout<<path[i];
			first=0;
		}
		else
		{
			cout<<" "<<path[i];
		}
	}
	cout<<" "<<d[ed]<<" "<<minCost;
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值