单源点最短路径

来源:互联网 发布:seo公司 编辑:程序博客网 时间:2024/06/05 10:07
    单源最短路径:从顶点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] <- NILd[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;elsereturn 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;}
0 0
原创粉丝点击