算法导论-24.2-有向无回路图中的单源最短路径

本文介绍了一种基于有向无环图(DAG)的最短路径算法实现,通过拓扑排序和松弛技术来计算从指定源点到图中所有其他顶点的最短路径。代码示例展示了如何构建图结构、插入边、进行拓扑排序及应用松弛算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、概念

二、代码

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

#define N 6
#define M 10

//边结点结构
struct Edge
{
	int start;//有向图的起点
	int end;//有向图的终点
	Edge *next;//指向同一个起点的下一条边
	int weight;//边的类型
	Edge(int s, int e,int w):start(s),end(e),weight(w),next(NULL){}
};
//顶点结点结构
struct Vertex
{
	int id;
	Edge *head;//指向以该顶点为起点的下一条边
	int degree;
	int d;
	int p;
	Vertex(int i):head(NULL),degree(0),id(i),p(-1){}
};
//图结构
struct Graph
{
	Vertex *V[N+1];//N个顶点
	Graph()
	{
		int i;
		for(i = 1; i <= N; i++)
			V[i] = new Vertex(i);
	}
	~Graph()
	{
		int i;
		for(i = 1; i <= N; i++)
			delete V[i];
	}
};

queue<int> Q;
int time = 0, Top[N+1] = {0};

void Print(Graph *G)
{
	int i;
	cout<<"d:";
	for(i = 1; i <= N; i++)
		cout<<G->V[i]->d<<' ';
	cout<<endl;
	cout<<"p:";
	for(i = 1; i <= N; i++)
		cout<<G->V[i]->p<<' ';
	cout<<endl;
}
//插入边
void InsertEdge(Graph *G, Edge *E)
{
	//如果没有相同起点的边
	if(G->V[E->start]->head == NULL)
		G->V[E->start]->head =E;
	//如果有,加入到链表中,递增顺序排列,便于查重
	else
	{
		//链表的插入,不解释
		Edge *e1 = G->V[E->start]->head, *e2 = e1;
		while(e1 && e1->end < E->end)
		{
			e2 = e1;
			e1 = e1->next;
		}
		if(e1 && e1->end == E->end)
			return;
		if(e1 == e2)
		{
			E->next = e1;
			G->V[E->start]->head =E;
		}
		else
		{
			e2->next = E;
			E->next = e1;
		}
		//插入边的同时,计下每个顶点的入度
		G->V[E->end]->degree++;
	}
}
//拓扑排序
void Topological(Graph *G)
{
	//队列初始化
	while(!Q.empty())
		Q.pop();
	int i;
	//将所有入度为0的点入队列
	for(i = 1; i <= N; i++)
	{
		if(G->V[i]->degree == 0)
			Q.push(i);
	}
	//队列不为空
	while(!Q.empty())
	{
		//队列首元素
		int t = Q.front();
		Q.pop();
		//输出
		cout<<t<<' ';
		Top[++Top[0]] = t;
		//处理以头结点为起点的所有的边
		Edge *e = G->V[t]->head;
		while(e)
		{
			//将边的终点的入度-1
			G->V[e->end]->degree--;
			//若入度减为0,则入队列
			if(G->V[e->end]->degree == 0)
				Q.push(e->end);
			e = e->next;
		}
	}
	cout<<endl;
}
//初始化
void Initialize_Single_Source(Graph *G, int s)
{
	int i;
	for(i = 1; i <= N; i++)
	{
		G->V[i]->d = 0x7fffffff;
		G->V[i]->p = -1;
	}
	G->V[s]->d = 0;
}
//松弛技术
void Relax(Vertex* u, Vertex* v, int w)
{
	if(u->d != 0x7fffffff && v->d > u->d + w)
	{
		v->d = u->d + w;
		v->p = u->id;
	}
}
void Dag_Shortest_Paths(Graph *G, int s)
{
	//拓扑排序并输出,时间O(V+E)
	Topological(G);
	//初始化O(V)
	Initialize_Single_Source(G, s);
	//对每个顶点进行一次迭代
	for(int i = 1; i <= N; i++)
	{
		cout<<"对第"<<i<<"个点进行松弛后"<<endl;
		Vertex *u = G->V[Top[i]];
		Edge *e = u->head;
		while(e)
		{
			Relax(u,G->V[e->end], e->weight);
			e = e->next;
		}
		Print(G);
	}
}
/*
1 2 5
2 3 2
3 4 7
4 5 -1
5 6 -2
1 3 3
2 4 6
3 5 4
3 6 2
4 6 1
*/
int main()
{
	//构造一个空的图
	Graph *G = new Graph;
	Edge *E;
	//输入边
	int i,weight, start, end;
	for(i = 1; i <= M; i++)
	{
		cin>>start>>end>>weight;
		E = new Edge(start, end,weight);
		InsertEdge(G, E);
	}
	//有向
	int s;
	cin>>s;
	Dag_Shortest_Paths(G, s);
	return 0;
}


三、练习

24.2-1

r:1, s:2, t:3, x:4, y:5, z:6

拓扑排序结果:
1 2 3 4 5 6
对第1个点进行松弛后
d:0 5 3 2147483647 2147483647 2147483647
p:-1 1 1 -1 -1 -1
对第2个点进行松弛后
d:0 5 3 11 2147483647 2147483647
p:-1 1 1 2 -1 -1
对第3个点进行松弛后
d:0 5 3 10 7 5
p:-1 1 1 3 3 3
对第4个点进行松弛后
d:0 5 3 10 7 5
p:-1 1 1 3 3 3
对第5个点进行松弛后
d:0 5 3 10 7 5
p:-1 1 1 3 3 3
对第6个点进行松弛后
d:0 5 3 10 7 5
p:-1 1 1 3 3 3

 

24.2-3

将所有点的d初始化为0,RELAX(u,v,w)时的if条件为:d[v] < d[u] + w(u, v)

 

24.2-4

 

好的,这是两个问题。让我分别回答。 --- 对于习题24.2-4,我们需要证明:对于有向$G=(V,E)$,从源点$s$到任何其他点$v\in V$的最短路径上最多可以包含$|V|-1$条边。 我们可以使用反证法来证明这个结论。假设存在一条从$s$到$v$的最短路径包含了$|V|$条边。由于这是一条简单路径,所以它经过了$|V|$个顶点中的所有顶点。因此,这个路径形成了一个简单环。我们可以从这个简单环中删除任意一条边,得到一条从$s$到$v$的路径,路径长度比原来的最短路径长度更小,这与原来的最短路径的假设相矛盾。因此,假设不成立,结论得证。 --- 对于习题24.3-6,我们需要证明:如果负权重有向$G$中不存在从源点$s$可达的负权重环,则Bellman-Ford算法能够正确地计算出从$s$到所有其他顶点的最短路径。 我们可以使用反证法来证明这个结论。假设存在一个从$s$到$v$的最短路径上存在一个负权重环。由于负权重环的存在,我们可以通过不断绕这个环走来无限制地减小路径长度,因此不存在从$s$到$v$的最短路径。但是,Bellman-Ford算法会在第$|V|$次松弛操作之前终止,并且在第$i$次松弛操作之后,算法会计算出从$s$到所有距离$s$不超过$i$的顶点的最短路径。因此,我们可以得出结论:如果负权重有向$G$中不存在从源点$s$可达的负权重环,则Bellman-Ford算法能够正确地计算出从$s$到所有其他顶点的最短路径
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值