图论——最短路

Dijkstra(迪杰斯特拉) 算法

迪杰斯特拉算法中有几个数组:s[ i ], dist[ i ](假设起始点是u,终点为v)

  • s[i] :在s数组的意思是已经找到从u开始的最短路
  • dist[i] : 表示在目前为止计算到从u到v 的最短的距离

迪杰斯特拉算法三步骤:

  • 在dist[i]数组中找到S[i]为0的点,并且dist[i]最小的点u
  • 将u加如集合S[i],即把S[u]标为1
  • 修改与u链接的点的最短路径dist[i]和上一节点path[i]

迪杰斯特拉算法就是通过dist【v】最小的开始确定,从v到其他点继续确定,应该是一个贪心算法

struct edge{			//邻接表存图
	int v;			
	int w;
	int next;
}e[maxn]; 

priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>>q;
// 优先队列从小到大排序
void Dijkstra(int u)//u为源点
{
	s[u] = 0;	
	dis[u] = 0;				// u到自己本身的距离是0
	q.push(make_pair(dis[u], u)); //入队
	while(!q.empty())
	{
		int u = q.top()->second;  //将当前dis最小的结点取出
		q.pop();
		if(s[u]) continue;
		s[u] = 1;					//加入到s集合中
		for(int i = head[u]; ~i; i = e[i].next)
		{
			dis[edge[i].v] = min(dis[u]+edge[v].w, dis[edge[i].v]);
			q.push(make_pair<dis[edge[i].v], edge[i].v>);	
		}	
	}	
} 

说白了Dijkstra算法就是通过访问dis最小的点来更新他周围的结点

Floyd算法

floyd算法用来解决多源点,但是不能够带有负权值的边

void floyd(){
	int i, j, k;
	for( k = 0; k < n ; i++)//经过k点
		for(j = 0; j < n; j++)//从j点开始
			for(i = 0; i < n; i++)//终点为i
			{
				edge[i][j] = min(edge[i][k]+ege[k][j], edge[i][j]);
			} 
}

Bellman-Ford算法(贝尔曼-福特算法)

贝尔曼算法只限制于没有负环的(负环指:这个环的所有边相加是负值)

int bellman_ford(int u){
	memset(dist, 63, sizeof(dist));
	dist[u] = 0;
	for(int i = 1; i < n; i++ )//这里控制的是通过几条边到达源点,这里最多n-1条,不能n次循环,因为如果要经过n-1条边到达,那就是有环了
	{
		bool flag = 0;
		for(int j = 1; j <= n ;j++)//先找到一条边到达源点的点的距离,第二次循环在第一次找到的结果上继续找经过两条边到达源点的点。
			for(int z = head[j]; ~z; z = edge[z].next)
			{
				if(dist[j]+edge[z].w < dist[edge[z].v])
				{
					dist[edge[z].v] = dist[j]+edge[z].w;
					flag = 1;
				}
				//由于这部操作所以带有负值的环只会执行一次
			}
		if(flag)
			break;
	}
	bool flag = 0;//因为用了链式前面向星,所以不是很方便直接遍历所有边,通过点来遍历,寻找负环,有负环直接break
	for(int i = 1; i <= n; i++)
	{
		for(int z = head[i]; ~z; z = edge[z].next)
		{
			if(dist[edge[z].v] > (dist[edge[z].u]+edge[z].w))
			{
				flag = 1;
				break;
			}
		}
	}
	if(flag)
		return 0;
	else 
		return 1;
}

外循环其实就只有n-1次,执行外循环一次代表找经过一条边到达源点的距离,执行两次代表经过两次到达源点的距离,执行n-1次代表经过n-1次到达源点的距离。

词条模板

#include<iostream>  
#include<cstdio>  
using namespace std;
 
#define MAX 0x3f3f3f3f  
#define N 1010  
int nodenum, edgenum, original; //点,边,起点  
 
typedef struct Edge //边  
{
	int u, v;
	int cost;
}Edge;
 
Edge edge[N];
int dis[N], pre[N], aft[N];
 
bool Bellman_Ford()
{
	for (int i = 1; i <= nodenum; ++i) //初始化  
		dis[i] = (i == original ? 0 : MAX);
	for (int i = 1; i <= nodenum - 1; ++i)
		for (int j = 1; j <= edgenum; ++j)
			if (dis[edge[j].v] > dis[edge[j].u] + edge[j].cost) //松弛(顺序一定不能反~)  
			{
				dis[edge[j].v] = dis[edge[j].u] + edge[j].cost;//源点到v距离=源点到u距离 + u到v距离
				pre[edge[j].v] = edge[j].u;//pre[i]->i
				aft[edge[j].u] = edge[j].v;//i->aft[i]
			}	
	bool flag = 1; //判断是否含有负权回路  
	for (int i = 1; i <= edgenum; ++i)
		if (dis[edge[i].v] > dis[edge[i].u] + edge[i].cost)
		{
			flag = 0;
			break;
		}
	return flag;
}
 
void print_path(int root) //打印最短路的路径(反向)  
{
	while (root != pre[root]) //前驱  
	{
		printf("%d-->", root);
		root = pre[root];
	}
	if (root == pre[root])
		printf("%d\n", root);
}
 
int main()
{
	scanf_s("%d%d%d", &nodenum, &edgenum, &original);
	pre[original] = original;
	for (int i = 1; i <= edgenum; ++i)
	{
		scanf_s("%d%d%d", &edge[i].u, &edge[i].v, &edge[i].cost);
	}
	if (Bellman_Ford())
		for (int i = 1; i <= nodenum; ++i) //每个点最短路  
		{
			printf("%d   ", dis[i]);
			printf("Path:");
			print_path(i);
		}
	else
		printf("have negative circle\n");
	return 0;
}

SPFA

SPFA算法是bellman-ford算法的队列优化实现的算法,我觉的SPFA的过程是有点像BFS

思维过程:

  • 初始,将源点入队
  • 每次取出一个点,将与他的相连的顶点松弛,若松弛成功,将该点入队
  • 重复操作知道队列为空
queue<int> q;
void spfa(int u)
{
	memset(dist, 63, sizeof(dist));
	dist[u] = 0;
	s[u] = 1;			//判断是不是在队列中
	deep[u] = 1;		//防止出现负环,无休止的减下去	
	q.push(u);
	while(!q.empty)
	{
		int h = q.front();
		if(deep[h] > n)
			break;
			q.pop();
			s[u] = 0;//释放u点,可以重新入队
		for(int z = head[h]; ~z; z = edge[z].next)
			{
				if(dist[edge[z].v] > dist[h]+edge[z].w)
				{
					dist[edge[z].v] = dist[h]+edge[z].w;
					if(!s[edge[z].v])
					{
						q.push(edge[z].v);
						deep[edge[z].v]++;
					}	
				}	
			}		
	}	
 } 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值