单源点最短路径

 单源最短路径:从顶点v可达顶点u表示为v->u,在所有的可达路径中存在一条权值最小的路径,则这条路径叫做v到u的最短路径。那么单源最短路径指的是:求出给定源点s到其他所有顶点的最短路径。
 最短路径的性质:对于一给定的带权图G=(V,G),所定义的加权函数为w:E->R。设p=<v1,v2,v3,……,vk>是从v1到vk的最短路径。对于任意i,j,其中1<=i<=j<=k,设pij=<vi,vi+1,……,vj>为p中从顶点vi到顶点vj的子路径。那么,pij是从vi到vj的最短路径。(最短路径的子路径是最短路径)
    最短路径中不存在负权回路,因为如果存在负权回路,我们总是可以顺着已经找的“最短”路径,再穿过负权回路而获得一条权值更小的路径。这样无限次的穿过,我们得到这条路径的权值为负无穷。同样,最短路径中也不存在正权回路,对于权值为零的回路可有可无。
    路径松弛性质:如果p=<v0,v1,……,vk>是从s=v0到vk的最短路径,而且p的边按照(v0,v1),(v1,v2),……,(vk-1,vk)的顺序进行松弛,那么d[vk]=sp(s,vk)(表示s到vk的最短路径上的权值),这个性质的保持并不受其他松弛操作的影响,即使它们与p的边上的松弛操作混合在一起也是一样的。
下面介绍三种方法求最短路径:
   (1)Bellman-Ford算法,该算法用来解决一般的单源最短路径问题(边的权值可以为负,但是不存在负权回路)。此方法可以检测是否有存在从源点可达的负权回路。
   (2)在有向无环图中,我们可以根据图中顶点的拓扑顺序,在线性时间内计算出单源最短路径路径的值。
   (3)Dijkstra算法,运行时间比Bellman-Ford算法时间低,但此算法有一个不足之处是:必须要求边上的权值非负。
在介绍上述三个算法之前,我们先介绍两个基本操作:初始化操作和松弛操作。
初始化操作的伪代码:
Initialize-Single-Source(s)
	for each vertex v∈V[G]
		do  d[v] <- ∞
			p[v] <- NIL
	d[s] <- 0;
其中d[v]表示从源点到v的最短路径上权值的上界,p[v]表示最短路径上v的前驱顶点。
松弛操作的伪代码:
Relax(u,v,w)
<span style="white-space:pre">	</span>if d[v] > d[u] + w(u,v)
	   then d[v] <- d[u] + w(u,v)
		p[v] <- u;	
在松弛一条边(u,v)时,要测试是否可以通过u,对目前为止到v的最短路径进行更新。如果可以更新的话,则更新d[v]和p[v]。一次松弛操作可以减少最短路径估计值d[v],并且更新v的前驱p[v]。
方法一:Bellman-Ford算法
对于给定的带权有向图G=(V,G),其源点为s,加权函数为w:E->R,对该图运行Bellman-Ford算法后返回一个布尔值,表明图中是否存在一个从源点可达的负权回路。若存在这样的回路,算法说明该问题无解;若不存在这样的回路,算法将产生最短路径(p[v])及其权值(d[v])。
此算法对所有的边执行|v|-1次松弛操作。经过|v|-1次迭代后,对于s可达的顶点v,都有d[v]=sp(s,v),该算法的时间复杂度为o(VE)。
证明:设v为从s可达的任意顶点,p=<v0,v1,……,vk>是任一条从s到v的最短路径,其中v0=s和vk=v。路径p至多有|v|-1条边,所以k<=|v|-1。在每一次松弛所有边的操作过程中,第i次迭代被松弛的边为(vi-1,vi),由上述的路径松弛性质,可得d[v]=d[vk]=sp(s,v)。
/*Bellman_Frod算法求最短路径*/
#include<iostream>
#include<vector>
#include<fstream>
using namespace std;

//边类
struct Node
{
	int value;//存储边的终点
	int weight;//存储边的权值
	Node(){}
	Node(int value,int weight)
	{
		this->value = value;
		this->weight = weight;
	}
};

vector<vector<Node>> mGraph;//图结构
int nodeNum;//图中顶点数
int edgeNum;//图中边数
vector<int> d;//从源点到顶点v的最短路径估计
vector<int> p;//当前顶点的前驱值

//存储图结构
void readGraph()
{
	fstream fin("E:\\SPData\\data01.txt");//打开文件
	fin>>nodeNum>>edgeNum;//读取顶点数和边数
	mGraph.resize(nodeNum);
	d.resize(nodeNum);
	p.resize(nodeNum);
	int s , t, w;
	Node tmp;
	while(fin>>s>>t>>w)
	{
		tmp.value = t;
		tmp.weight = w;
		mGraph[s].push_back(tmp);
	}
	fin.close();
}

//初始化d[v]和p[v]的值
void init_single_source(int s)
{
	for(int i = 0; i < nodeNum; ++i)
	{
		d[i] = 100;
		p[i] = -1;
	}
	d[s] = 0;
}

//松弛操作
void relax(int u, int v, int w)
{
	if(d[v] > d[u] + w)
	{
		d[v] = d[u] + w;
		p[v] = u;
	}
}

/*Bellman-Ford算法:如果此方法返回true,则最短路径值存储在d[v],前驱值存储在p[v],我们可以根据p[v]得到最短路径所经过的顶点,如果此方法返回false,则表明该图中存在负权回路*/
bool bellman_ford(int s)
{
	init_single_source(s);//初始化
	for(int i = 1; i < nodeNum; ++i)//执行|v|-1次迭代
	{
		for(int j = 0; j < nodeNum; ++j)//对所有的边进行松弛操作
		{
			int count = mGraph[j].size();
			for(int k = 0; k < count; ++k)
			{
				relax(j,mGraph[j][k].value,mGraph[j][k].weight);
			}
		}
	}
	//判断是否存在负权回路
	for(int j = 0; j < nodeNum; ++j)
	{
		int count = mGraph[j].size();
		for(int k = 0; k < count; ++k)
		{
			int u = j;
			int v = mGraph[j][k].value;
			int w = mGraph[j][k].weight;
			if(d[v] > d[u] + w)
			{
				return false;
			}
		}
	}
	return true;
}
int main(void)
{
	readGraph();
	bellman_ford(0);
	system("pause");
	return 0;
}
我们可以对此方法进行改进:我们不必要执行|v|-1次对所有边的松弛操作。如果我们知道最短路径上边数最小值的最大值m,我们只要执行m+1次迭代操作就可以,即使m未知。主要代码如下:
Bellman-Ford-(M+1)
change = true;
while(change)
	do change = false;
	for each edge(u,v)
		do relax(u,v,w);
Relax(u,v,w)
if(d[v] > d[u] + w(u,v))
<span style="white-space:pre">	</span>then d[v] = d[u] + w(u,v);
		p[v] = u;
		change = true;
方法二:有向无环图中单源最短路径
按照顶点的拓扑序列对DAG图(有向无环图)G=(V,G)的边进行松弛操作后,就可以在o(V+E)时间内计算出单源最短路径。在一个DAG图中最短路径总是存在的,因为即使图中有权为负的边,也不可能存在负权回路。算法首先对DAG进行拓扑排序,以便获得顶点的线性序列。如果从u到v存在一条路径,则在拓扑序列u先于v。然后我们对所有的顶点执行一趟操作。当对每个顶点处理时,松弛从该顶点出发的所有的边。可以根据路径松弛性质证明此算法的正确性。
#include<iostream>
#include<vector>
#include<fstream>
#include<queue>
using namespace std;

//边类
struct Node
{
	int value;//存储边的终点
	int weight;//存储边上的权值
	Node(){}
	Node(int value,int weight)
	{
		this->value = value;
		this->weight = weight;
	}
};

vector<vector<Node>> mGraph;//图结构
int nodeNum;//图中顶点数
int edgeNum;//图中边数
vector<int> d;//从源点到顶点v的最短路径估计
vector<int> p;//当前顶点的前驱值
vector<int> indegree;//存储当前顶点的入度数
vector<int> top_arr;//拓扑排序的序列

//读取图文件
void readGraph()
{
	fstream fin("E:\\SPData\\data02.txt");//打开文件
	fin>>nodeNum>>edgeNum;//读取顶点数和边数
	mGraph.resize(nodeNum);
	d.resize(nodeNum);
	p.resize(nodeNum);
	top_arr.resize(nodeNum);
	indegree.resize(nodeNum);
	int s , t, w;
	Node tmp;
	while(fin>>s>>t>>w)
	{
		tmp.value = t;
		tmp.weight = w;
		mGraph[s].push_back(tmp);
	}
	fin.close();
}

//初始化indegree数组
void init_indegree()
{
	for(int i = 0; i < nodeNum; ++i)
	{
		int count = mGraph[i].size();
		for(int j = 0; j < count; ++j)
		{
			indegree[mGraph[i][j].value]++;
		}
	}
}

//拓扑排序
void top_sort()
{
	queue<int> q;
	int index = 0;
	for(int i = 0; i < nodeNum; ++i)
	{
		if(indegree[i] == 0)
		{
			q.push(i);
		}
	}
	while(!q.empty())
	{
		int num = q.front();
		top_arr[index++] = num;
		int count = mGraph[num].size();
		for(int i = 0; i < count; ++i)
		{
			if((--indegree[mGraph[num][i].value]) == 0)
			{
				q.push(mGraph[num][i].value);
			}
		}
		q.pop();
	}
}

//初始化d[v]和p[v]的值
void initialize_single_source(int s)
{
	for(int i = 0; i < nodeNum; ++i)
	{
		d[i] = 10;
		p[i] = -1;
	}
	d[s] = 0;
}

//松弛操作
void relax(int u, int v, int weight)
{
	if(d[v] > d[u] + weight)
	{
		d[v] = d[u] + weight;
		p[v] = u;
	}
}

void DAG_Shortest_Paths(int s)
{
	init_indegree();//初始化每个顶点的入度数
	top_sort();//调用拓扑排序方法
	initialize_single_source(s);//初始化d[v]和p[v]
	for(int i = 0; i < nodeNum; ++i)//每个顶点遍历一次
	{
		int val = top_arr[i];
		int count = mGraph[val].size();
		for(int j = 0; j < count; ++j)//相应顶点的出边进行松弛操作
		{
			int u = val;
			int v = mGraph[val][j].value;
			int weight = mGraph[val][j].weight;
			relax(u,v,weight);
		}
	}
}

int main(void)
{
	readGraph();
	DAG_Shortest_Paths(0);
	system("pause");
	return 0;
}
方法三:Dijstra算法
Dijstra算法解决了有向图G=(V,G)上带权的单源最短路径问题,但是要求所有边的权值为非负。Dijstra算法设置了一顶点集合S,从源点s到集合中的顶点的最短路径的权值均已经确定。算法反复选择具有最短路径估计的顶点u∈V-S,并将u加入到S中,然后对u的所有出边进行松弛操作。在选择最短路径估计时,我们使用的优先队列,排序的关键字为d[v]值。Dijstra算法的时间复杂度依赖于最小优先队列的实现。如果使用一般的数组存储优先队列,因为每次找最小值都要扫描一遍优先队列,总共需要执行|v|次查找最小值得操作,并且需要执行|E|次松弛操作,所以时间复杂度为o(V*V+E)。如果使用二叉堆来实现最小优先队列。每次找到最小值的时间复杂度为o(lgV),总共需要|v|次,而且每松弛一条边时时间复杂度也为o(lgV),最多需要松弛|E|次,所以时间复杂度为o((V+E)lgV)。适合于稀疏图。使用一般的数组存储优先队列,时间复杂度为o(V*V+E)的代码。
#include<iostream>
#include<vector>
#include<fstream>
#include<queue>
using namespace std;

//边类
struct Node
{
	int value;//存储边的终点
	int weight;//存储边上的权值
	Node(){}
	Node(int value,int weight)
	{
		this->value = value;
		this->weight = weight;
	}
};

//源点到顶点v的距离
struct Distance
{
	int dis;//到当前顶点的距离
	bool flag;//标记是不是最短距离值
	Distance(){}
	Distance(int dis,bool flag)
	{
		this->dis = dis;
		this->flag = flag;
	}
};

vector<vector<Node>> mGraph;//图结构
int nodeNum;//图中顶点数
int edgeNum;//图中边数
vector<Distance> d;//源点到顶点v的距离
vector<int> p;//存储当前顶点的前驱值

//读取图文件
void readGraph()
{
	fstream fin("E:\\SPData\\data03.txt");//打开文件
	fin>>nodeNum>>edgeNum;//读取顶点数和边数
	mGraph.resize(nodeNum);
	d.resize(nodeNum + 1);
	p.resize(nodeNum);
	int s , t, w;
	Node tmp;
	while(fin>>s>>t>>w)
	{
		tmp.value = t;
		tmp.weight = w;
		mGraph[s].push_back(tmp);
	}
	fin.close();
}

//初始化d[v]和p[v]
void initialize_signal_source(int s)
{
	for(int i = 0; i < nodeNum; ++i)
	{
		d[i].dis = 255;
		d[i].flag = false;
		p[i] = -1;
	}
	d[s].dis = 0;
	d[nodeNum].dis = 255;
	d[nodeNum].flag = false;
}

//松弛操作
void relax(int u, int v, int weight)
{
	if(d[v].dis > d[u].dis + weight)
	{
		d[v].dis = d[u].dis + weight;
		p[v] = u;
	}
}

//Dijstra算法
void dijkstra(int s)
{
	initialize_signal_source(s);//初始化
	for(int i = 0; i < nodeNum; ++i)
	{
		int index = nodeNum;
		for(int j = 0; j < nodeNum; ++j)//遍历每个顶点找到最小值
		{
			if(!d[j].flag && d[j].dis < d[index].dis)
			{
				index = j;
			}
		}
		d[index].flag = true;
		int count = mGraph[index].size();
		for(int k = 0; k < count; ++k)//松弛以该顶点为出度的边
		{
			relax(index,mGraph[index][k].value,mGraph[index][k].weight);
		}
	}
}

int main(void)
{
	readGraph();
	dijkstra(0);
	system("pause");
	return 0;
}
使用二叉堆来实现最小优先队列的代码:
#include<iostream>
#include<vector>
#include<fstream>
using namespace std;

/*优先队列里存储元素类型*/
struct Node
{
	int ver;//源点可达的顶点
	int dis;//源点到可达顶点的距离
	Node(){}
	Node(int ver, int dis)
	{
		this->ver = ver;
		this->dis = dis;
	}
};

//优先队列类
struct HeapMin
{
	vector<Node> ve;//优先队列中存储元素的容器
	vector<int> index;//顶点在优先队列中的下标index[2] = 3;表示顶点为2的顶点存储在优先队列的3号位置。 
	int size;//优先队列的总容量
	int cur;//当前优先队列中元素的个数
	
	//初始化优先队列
	HeapMin(int size)
	{
		this->size = size;
		ve.resize(size);
		index.resize(size);
		cur = 0;
	}
	
	//优先队列中的某个位置(pos)的元素值被改变了,重新调整优先队列
	void siftUp(int pos)
	{
		while(pos > 0 && ve[pos].dis < ve[(pos - 1)/2].dis)
		{
			swap(index[ve[pos].ver],index[ve[(pos - 1)/2].ver]);
			swap(ve[pos],ve[(pos - 1)/2]);
			pos = (pos - 1)/2;
		}
	}
	
	//判断优先队列是否为空
	bool empty()
	{
		if(cur == 0)
			return true;
		else
			return false;
	}
	
	//返回优先队列中的最小元素但是不弹出
	Node top()
	{
		return ve[0];
	}
	
	//弹出优先队列对头元素
	void pop()
	{
		cur--;
		swap(index[ve[cur].ver],index[ve[0].ver]);
		swap(ve[0],ve[cur]);
		siftDown(0);
	}
	
	//从当前位置开始向下调整优先队列
	void siftDown(int start)
	{
		int i = start;
		while((2 * i + 1) < cur && (2 * i + 2) < cur)
		{
			if(ve[2 * i + 1].dis < ve[2 * i + 2].dis)
			{
				if(ve[i].dis > ve[2 * i + 1].dis)
				{
					index[ve[2 *i + 1].ver] = i;
					index[ve[i].ver] = 2 * i + 1;
					swap(ve[i],ve[2 * i + 1]);
					i = 2 * i + 1;
				}
				else
				{
					break;
				}
			}
			else
			{
				if(ve[i].dis > ve[2 *i + 2].dis)
				{
					index[ve[2 * i + 2].ver] = i;
					index[ve[i].ver] = 2 * i + 2;
					swap(ve[i],ve[2 * i + 2]);					
					i = 2 * i + 2;
				}
				else
				{
					break;
				}
			}
		}
		if((2 * i + 1) < cur && ve[i].dis > ve[2 * i + 1].dis)
		{
			swap(ve[i],ve[2 * i + 1]);
			index[ve[2 * i + i].ver] = i;
			index[ve[i].ver] = 2 * i + 1;
		}
	}
	
	//创建一个优先队列
	void createHeap()  
	{  
		for(int i = cur / 2 - 1; i >= 0; --i)  
		{  
			siftDown(i);  
		}  
	}
	
	//向优先队列中插入元素,但是不调整优先队列
	void insert(int v, int w)
	{
		Node tmp(v,w);
		ve[cur] = tmp;
		index[cur++] = v;
	}
};

//边的类型
struct Edge
{
	int value;
	int weight;
	Edge(){}
	Edge(int value,int weight)
	{
		this->value = value;
		this->weight = weight;
	}
};

vector<vector<Edge>> mGraph;//图结构
int nodeNum;//图中顶点数
int edgeNum;//图中边数
vector<int> d;//源点到当前顶点的距离
vector<int> p;//最短路径中的前一个顶点
HeapMin qu(10);//创建一个优先队列

//读取图文件
void readGraph()
{
	fstream fin("E:\\SPData\\data03.txt");//打开文件
	fin>>nodeNum>>edgeNum;//读取顶点数和边数
	mGraph.resize(nodeNum);
	d.resize(nodeNum);
	p.resize(nodeNum);
	int s , t, w;
	Edge tmp;
	while(fin>>s>>t>>w)
	{
		tmp.value = t;
		tmp.weight = w;
		mGraph[s].push_back(tmp);
	}
	fin.close();
}

//初始化源点到其他顶点的距离,并且初始化通过那个顶点到达这个顶点
void initialize_signal_source(int s)
{
	for(int i = 0; i < nodeNum; ++i)
	{
		d[i] = 255;
		p[i] = -1;
	}
	d[s] = 0;
}

//松弛边的操作
void relax(int u, int v, int w)
{
	if(d[v] > d[u] + w)
	{
		d[v] = d[u] + w;
		p[v] = u;
		int pos = qu.index[v];//得到要修改优先队列中距离值得下标
		qu.ve[pos].dis = d[u] + w;//修改对应优先队列中距离值
		qu.siftUp(pos);//从新调整优先队列
	}
}

//迪杰斯特拉算法
void dijkstra(int s)
{
	initialize_signal_source(s);//初始化
	for(int i = 0; i < nodeNum; ++i)
	{
		qu.insert(i,d[i]);
	}
	qu.createHeap();//创建优先队列
	while(!qu.empty())//判断优先队列是否为空
	{
		int val = qu.top().ver;//返回最小值
		qu.pop();//队头元素出队列
		int count = mGraph[val].size();
		for(int i = 0; i < count; ++i)
		{
			int u = val;
			int v = mGraph[val][i].value;
			int weight = mGraph[val][i].weight;
			relax(u,v,weight);		
		}
	}
}

int main(void)
{
	readGraph();
	dijkstra(2);
	system("pause");
	return 0;
}

你可能感兴趣的:(单源点最短路径)