图(4)——最短路径

最短路径

找到从一个顶点到另一个顶点的成本最小的路径。
定义 :在一幅加权有向图中,从顶点S到顶点t的最短路径是所有从s到t的路径中的权重最小者。

最短路径的性质:

1.路径是有方向的。
2.权重不一定等价于距离。
3.并不是所有顶点都是可达的。
4.负权重会使问题更复杂。
5.最短路径一般哦都是简单的。(忽略0权重边,即没有环)
6.最短路径不一定是唯一的。
7.可能存在平行边和自环。(平行边中的权重最小者才会被选中,最短路径也不可能包含自环)

最短路径树:以S为根结点,树的每条路径都是有向图中的一条最短路径。

加权有向图的数据结构:

1.加权有向边:

代码:
#ifndef DEDGE_H
#define DEDGE_H
#include<sstream>
#include<string>
using namespace::std;

class DEdge
{
private:
	int v;//起点
	int w;//终点
	double weight;//权重
public:
	DEdge(){}
	DEdge(int v, int w, double weight) :v(v), w(w), weight(weight){}

	bool operator<(const DEdge&itr)const
	{
		return weight < itr.weight;
	}
	bool operator>(const DEdge&itr)const
	{
		return weight > itr.weight;
	}
	bool operator==(const DEdge&itr)const
	{
		return weight == itr.weight;
	}
	bool operator!=(const DEdge&itr)const
	{
		return weight != itr.weight;
	}
	bool operator<=(const DEdge&itr)const
	{
		return !(weight > itr.weight);
	}
	bool operator>=(const DEdge&itr)const
	{
		return !(weight < itr.weight);
	}

	double getweight(){ return weight; }
	int from(){ return v; }
	int to(){ return w; }
	string tostring()
	{
		string s1, s2;
		stringstream stream;
		stream << v << "-" << w << ":";
		stream >> s1;
		stream.clear();
		stream << weight << endl;
		stream >> s2;
		return s1 + s2;
	}
};
#endif

2.加权有向图:

代码:
#ifndef EDGEDIGTAPH_H
#define EDGEDIGRAPH_H

#include<vector>
using std::vector;
#include"DEdge.h"
#include"Bag.h"

class EdgeDigraph
{
private:
	int V;//顶点的总数
	int E;//边的总数
	vector<Bag<DEdge>> adj;//;邻接表
public:
	EdgeDigraph(int V) :V(V), E(0), adj(V){}
	EdgeDigraph(int V, int E) :V(V), E(0), adj(V)
	{
		int v, w;
		double weight;
		for (int i = 0; i < E; i++)
		{
			cout << "输入第" << i << "条边的两个顶点和权重:" << endl;
			cin >> v >> w >> weight;
			DEdge e(v, w, weight);
			addEdge(e);
		}
	}
	int getV(){ return V; }
	int getE(){ return E; }
	void addEdge(DEdge e)
	{
		adj[e.from()].add(e);
		E++;
	}
	Bag<DEdge> getadj(int V){ return adj[V]; }
	Bag<DEdge> edges()
	{
		Bag<DEdge> b;
		for (int v = 0; v < V; v++)
			for (auto e : adj[v])
				b.add(e);
		return b;
	}
};
#endif


测试用例:
#include<iostream>
#include"EdgeDigraph.h"
using namespace std;
int main()
{
	int n, m;
	cout << "输入图的顶点数和边数:" << endl;
	cin >> n >> m;
	EdgeDigraph G(n, m);
	Bag<DEdge> b=G.edges();
	for (auto w : b)
		cout << w.tostring() << endl;
	cout << endl;
	system("pause");
	return 0;
}


最短路径算法:

1.最短路径需要的工具

1)edgeTo[ ]:最短路径树中的边;(每个结点都储存他来时的路径)
2)distTo[ ]:到达起点的距离(权重和);
3)约定:s为起点,edgeTo[s]=NULL,disTo[s]=0,且起点到不可达顶点为DBL_MAX;
4)松弛:放松边v->w意味着检查从s到w的最短路径是否是先从s到v,然后再由v到w。
点的松弛:
void relax(EdgeDigraph G, int v)
	{
		for (auto e : G.getadj(v))
		{
			int w = e.to();
			if (distTo[w] > distTo[v] + e.getweight())//e这条边,是不是已知到w的边中最短的
			{
				distTo[w] = distTo[v] + e.getweight();
				edgeTo[w] = e;
			}
		}
	}


2.Dijkstra算法:(类似Prim)

与算法prim大至相似,只是prim每次都添加的都是离树最近的非树顶点,Dijkstra每次添加的都是里起点最近的非树顶点。他们都不需要marked[ ]数组,因为条件!marked[w]等价于distTo[w]为无穷大。
代码:
#ifndef DIJKSTRASP_H
#define DIJKSTRASP_H

#include<vector>
using std::vector;
#include"EdgeDigraph.h"
#include"DEdge.h"
#include"IndexMinPQ.h"
#include"stack.h"

class DijkstraSP
{
private:
	vector<DEdge> edgeTo;//最短路径树的边
	vector<double> distTo;//到底起点的距离
	IndexMinPQ<double> pq;
	int s;//起点
	void relax(EdgeDigraph G, int v)
	{
		for (auto e : G.getadj(v))
		{
			int w = e.to();
			if (distTo[w] > distTo[v] + e.getweight())//e这条边,是不是已知到w的边中最短的
			{
				distTo[w] = distTo[v] + e.getweight();
				edgeTo[w] = e;
				if (pq.contains(w))pq.change(w, distTo[w]);
				else pq.insert(w, distTo[w]);
			}
		}
	}
public:
	DijkstraSP(EdgeDigraph G, int s) :edgeTo(G.getV()),
		distTo(G.getV(), DBL_MAX), pq(G.getV()), s(s)
	{
		distTo[s] = 0.0;
		pq.insert(s, 0.0);
		while (!pq.Empty())
			relax(G, pq.delMin());
	}
	double getdistTo(int v){ return distTo[v]; }//查询顶点到v的最小距离
	bool hasPathTo(int v){ return distTo[v] < DBL_MAX; }//顶点与v是否可达
	stack<DEdge> pathTo(int v)//输出到v的路径
	{
		stack<DEdge> path;
		if (!hasPathTo(v)||v==s)return path; 
		DEdge e;
		for ( e = edgeTo[v]; e.from() != s; e = edgeTo[e.from()])
			path.push(e);
		path.push(e);
		return path;
	}
};
#endif


测试用例:
#include<iostream>
using namespace std;
#include"DijkstraSP.h"

int main()
{
	int n, m;
	cout << "输入图的顶点数和边数:" << endl;
	cin >> n >> m;
	EdgeDigraph G(n, m);
	DijkstraSP Sp(G, 0);
	for (int t = 0; t < G.getV(); t++)
	{
		stack<DEdge> b = Sp.pathTo(t);
		cout << 0 << " to " << t << "(" << Sp.getdistTo(t) << "): " << endl;
		for (auto w : b)
			cout << w.tostring() << endl;
		cout << endl;
	}
	system("pause");
	return 0;
}


3.无环加权有向图的最短路径算法

他是对于拓扑排序的扩展,有以下几个特点:
1)能够在线性时间内解决单点最短路径问题;
2)能够处理负权重问题;
3)能够解决相关问题,(最长路径)
具体步骤就是:先拓扑排序顶点,然后再按照顺序放松顶点。
在拓扑排序中需要使用到DepthFirstOther和DirectedCycle算法,因为边的数据结构有改动所以需要两个代码也需要变动:
#ifndef DC_H
#define DC_H

#include<vector>
using std::vector;

#include"stack.h"
class DirectedCycle
{
private:
	vector<bool> marked;
	vector<DEdge> edgeTo;//来时的路径
	stack<DEdge> cycle;//有向环中的边
	vector<bool> onstack;//递归调用所有栈上的顶点
public:
	DirectedCycle(EdgeDigraph G) :
		marked(G.getV()), edgeTo(G.getV()), onstack(G.getV())
	{
		for (int v = 0; v < G.getV(); v++)
			if (!marked[v])dfs(G, v);
	}
	void dfs(EdgeDigraph G,int v)
	{
		onstack[v] = true;
		marked[v] = true;
		for (auto e : G.getadj(v))
		{
			int w = e.to();
			if (hasCycle())return;
			else if (!marked[w])
			{
				edgeTo[w] = e;//记录来时的路径
				dfs(G, w);
			}
			else if (onstack[w])//在次出现该结点,说明有该结点的环
			{//逆序追踪到上一次出现该结点的地点
				while (e.from() != w)
				{
					cycle.push(e);
					e = edgeTo[e.from()];
				}
				cycle.push(e);
			}
		}
		onstack[v] = false;
	}
	bool hasCycle(){ return !cycle.isEmpty(); }
	stack<DEdge> getcycle(){return cycle;}
};
#endif
#ifndef DFO_H
#define DFO_H

#include<vector>
using std::vector;


#include"stack.h"
#include"queue.h"

class DepthFirstOther
{
private:
	vector<bool> marked;
	queue<int> pre;//先序
	queue<int> post;//后序
	stack<int> reversepost;//逆后序
public:
	DepthFirstOther(EdgeDigraph G) :marked(G.getV())
	{
		for (int v = 0; v < G.getV(); v++)
			if(!marked[v])dfs(G, v);
	}
	void dfs(EdgeDigraph G, int v)
	{
		pre.enqueue(v);
		marked[v] = true;
		for (auto e : G.getadj(v))
		{
			int w = e.to();
			if (!marked[w])dfs(G, w);
		}
		post.enqueue(v);
		reversepost.push(v);
	}
	queue<int> getpre(){ return pre; }
	queue<int> getpost(){ return post; }
	stack<int> getreversepsot(){ return reversepost; }
};
#endif



代码:
#ifndef ACYCLICSP_H
#define ACYCLICSP_H

#include<vector>
using std::vector;

#include"Topological.h"

class AcyclicSP
{
private:
	vector<DEdge> edgeTo;//来时的路径
	vector<double> distTo;//到起点的距离
	int s;//起点
	void relax(EdgeDigraph G, int v)
	{
		for (auto e : G.getadj(v))
		{
			int w = e.to();
			if (distTo[w] > distTo[v] + e.getweight())
			{
				distTo[w] = distTo[v] + e.getweight();
				edgeTo[w] = e;
			}
		}
	}
public:
	AcyclicSP(EdgeDigraph G, int s) :edgeTo(G.getV()), distTo(G.getV(),DBL_MAX), s(s)
	{
		distTo[s] = 0.0;//初始化
		Topological top(G);
		for (int v : top.getorder())
			relax(G, v);
	}
	double getdistTo(int v){ return distTo[v]; }
	bool hasPathTo(int v){ return distTo[v] < DBL_MAX; }
	stack<DEdge> pathTo(int v)//输出到v的路径
	{
		stack<DEdge> path;
		if (!hasPathTo(v) || v == s)return path;
		DEdge e;
		for (e = edgeTo[v]; e.from() != s; e = edgeTo[e.from()])
			path.push(e);
		path.push(e);
		return path;
	}
};
#endif


测试用例:
#include<iostream>
using namespace std;
#include"EdgeDigraph.h"
#include"Acyclicsp.h"
//#include"DijkstraSP.h"
int main()
{
	int n, m;
	cout << "输入图的顶点数和边数:" << endl;
	cin >> n >> m;
	EdgeDigraph G(n, m);
	AcyclicSP Sp(G, 0);
	for (int t = 0; t < G.getV(); t++)
	{
		stack<DEdge> b = Sp.pathTo(t);
		cout << 0 << " to " << t << "(" << Sp.getdistTo(t) << "): " << endl;
		for (auto w : b)
			cout << w.tostring() << endl;
		cout << endl;
	}
	system("pause");
	return 0;
}

而求最长路径就十分简单了,只需要把代码中DBL_MAX改为DBL_MIN,以及relax()中不等式的方向改变即可。

4.基于队列的Bellman-Ford算法(不存在负权重环)

一般加权有向图中的最短路径问题,也就是可能含有负权重的图。
负权重环:是一个总权重为负的有向环。
当且仅当加权有向图中至少存在一条从s到v的有向路径且所有从s到v的有向路径上的任意顶点都是不存在于任何负权重环中时,s到v的最短路径才是存在的。
也就是说我们需要在Dijkstra算法的基础上确定是否负权重环。

需要的数据结构:
1)一条用来保存即将被放松的顶点的队列queue;
2)一个由顶点索引的bool数组onQ[ ],用来指示顶点是否已经在队列中。

代码:
#ifndef BfSP_H
#define BFSP_H

#include<vector>
using std::vector;
#include"DEdge.h"
#include"queue.h"
#include"stack.h"
#include"DirectedCycle.h"

class BellmanFordSP
{
private:
	vector<double> distTo;//到起点的距离
	vector<DEdge> edgeTo;//来时的路径
	vector<bool> onQ;//顶点是否存在于队列中
	queue<int> queue;//正在被放松的顶点
	int cost;//relax()的调用次数
	stack<DEdge> cycle;//负权重环
	int s;//起点
public:
	BellmanFordSP(EdgeDigraph G, int s) :distTo(G.getV(),DBL_MAX), 
		edgeTo(G.getV()), onQ(G.getV()), s(s),cost(1)
	{
		distTo[s] = 0.0;
		queue.enqueue(s);
		onQ[s] = true;
		while (!queue.Empty() && !hasNegativeCycle())//放松队列不为空且没有负权重环
		{
			int v = queue.dequeue();
			onQ[v] = false;
			relax(G, v);
		}
	}
	double getdistTo(int v){ return distTo[v]; }
	bool hasPathTo(int v){ return distTo[v] < DBL_MAX; }
	stack<DEdge> pathTo(int v)//输出到v的路径
	{
		stack<DEdge> path;
		if (!hasPathTo(v) || v == s)return path;
		DEdge e;
		for (e = edgeTo[v]; e.from() != s; e = edgeTo[e.from()])
			path.push(e);
		path.push(e);
		return path;
	}
private:
	void relax(EdgeDigraph G, int v)//放松
	{
		for (auto e : G.getadj(v))
		{
			int w = e.to();
			if (distTo[w] > distTo[v] + e.getweight())
			{
				distTo[w] = distTo[v] + e.getweight();
				edgeTo[w] = e;
				cout << edgeTo[w].tostring() << endl;
				if (!onQ[w])
				{
					queue.enqueue(w);
					onQ[w] = true;
				}
				if (cost++ %G.getV() == 0)findNegativeCycle();//当cost走过一轮后,查看路径中是否有负权重环
			}
		}
	}
	void findNegativeCycle()//检测路径中是否有负权重环
	{
		cout << "1" << endl;
		int V = edgeTo.size();
		EdgeDigraph spt(V);
		for (int v = 0; v < V; v++)
			if (v != s)spt.addEdge(edgeTo[v]);
		DirectedCycle cy(spt);//检测存储路径中是否有环
		double ws=0;
		cout << "3" << endl;
		for (auto e : cy.getcycle())
			ws += e.getweight();//计算环的权重
		if (ws < 0)cycle = cy.getcycle();
		cout << "4" << endl;
	}
public:
	bool hasNegativeCycle(){ return !cycle.isEmpty(); }
	stack<DEdge> negativeCycle(){ return cycle; }
};
#endif

测试用例:
#include<iostream>
using namespace std;
#include"EdgeDigraph.h"
#include"BellmanFordSP.h"
int main()
{
	int n, m;
	cout << "输入图的顶点数和边数:" << endl;
	cin >> n >> m;
	EdgeDigraph G(n, m);
	BellmanFordSP Sp(G, 0);
	if (Sp.hasNegativeCycle())cout << "存在负权重环" << endl;
	cout << "不存在负权重环" << endl;
	for (int t = 0; t < G.getV(); t++)
	{
		stack<DEdge> b = Sp.pathTo(t);
		cout << 0 << " to " << t << "(" << Sp.getdistTo(t) << "): " << endl;
		for (auto w : b)
			cout << w.tostring() << endl;
		cout << endl;
	}
	system("pause");
	return 0;
}

比较三种算法

最短路径算法的性能特点:
算法局限比较次数(一般)比较次数(最坏)所需空间优势
Dijkstra(即时)边的权重必须为正ElogVElogVV最坏情况下性能也较好
拓扑排序只适用于无环加权图E+VE+VV是无环图的最优算法
Bellman-Fond不能存在负权重环E+VVEV适用领域广泛



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值