主要介紹最短路徑問題和鬆弛操做、Dijkstra算法的思想、負權邊和Bellman-Ford算法等算法
一個節點到另外一個節點最短的路徑,路徑規劃問題。數組
對於無權圖進行廣度優先遍歷就是求出了一個最短路徑(求出的是一顆最短路徑樹)微信
從起始點到其餘節點路徑最短的樹數據結構
無權圖的最短路徑。函數
鬆弛操做是指對於每一個頂點v∈V,都設置一個屬性d[v],用來描述從源點s到v的最短路徑上權值的上界,稱爲最短路徑估計(shortest-path estimate)。測試
最小索引堆this
鬆弛操做spa
// Dijkstra算法求最短路徑
template<typename Graph, typename Weight>
class Dijkstra{
private:
Graph &G; // 圖的引用
int s; // 起始點
Weight *distTo; // distTo[i]存儲從起始點s到i的最短路徑長度
bool *marked; // 標記數組, 在算法運行過程當中標記節點i是否被訪問
vector<Edge<Weight>*> from; // from[i]記錄最短路徑中, 到達i點的邊是哪一條
// 能夠用來恢復整個最短路徑
public:
// 構造函數, 使用Dijkstra算法求最短路徑
Dijkstra(Graph &graph, int s):G(graph){
// 算法初始化
assert( s >= 0 && s < G.V() );
this->s = s;
distTo = new Weight[G.V()];
marked = new bool[G.V()];
for( int i = 0 ; i < G.V() ; i ++ ){
distTo[i] = Weight();
marked[i] = false;
from.push_back(NULL);
}
// 使用索引堆記錄當前找到的到達每一個頂點的最短距離
IndexMinHeap<Weight> ipq(G.V());
// 對於其實點s進行初始化
distTo[s] = Weight();
from[s] = new Edge<Weight>(s, s, 0);
ipq.insert(s, distTo[s] );
marked[s] = true;
while( !ipq.isEmpty() ){
int v = ipq.extractMinIndex();
// distTo[v]就是s到v的最短距離
marked[v] = true;
// 對v的全部相鄰節點進行更新
typename Graph::adjIterator adj(G, v);
for( Edge<Weight>* e = adj.begin() ; !adj.end() ; e = adj.next() ){
int w = e->other(v);
// 若是從s點到w點的最短路徑尚未找到
if( !marked[w] ){
// 若是w點之前沒有訪問過,
// 或者訪問過, 可是經過當前的v點到w點距離更短, 則進行更新
if( from[w] == NULL || distTo[v] + e->wt() < distTo[w] ){
distTo[w] = distTo[v] + e->wt();
from[w] = e;
if( ipq.contain(w) )
ipq.change(w, distTo[w] );//最小索引堆支持change操做
else
ipq.insert(w, distTo[w] );
}
}
}
}
}
// 析構函數
~Dijkstra(){
delete[] distTo;
delete[] marked;
delete from[0];
}
// 返回從s點到w點的最短路徑長度
Weight shortestPathTo( int w ){
assert( w >= 0 && w < G.V() );
assert( hasPathTo(w) );
return distTo[w];
}
// 判斷從s點到w點是否聯通
bool hasPathTo( int w ){
assert( w >= 0 && w < G.V() );
return marked[w];
}
// 尋找從s到w的最短路徑, 將整個路徑通過的邊存放在vec中
void shortestPath( int w, vector<Edge<Weight>> &vec ){
assert( w >= 0 && w < G.V() );
assert( hasPathTo(w) );
// 經過from數組逆向查找到從s到w的路徑, 存放到棧中
stack<Edge<Weight>*> s;
Edge<Weight> *e = from[w];
while( e->v() != this->s ){
s.push(e);
e = from[e->v()];
}
s.push(e);
// 從棧中依次取出元素, 得到順序的從s到w的路徑
while( !s.empty() ){
e = s.top();
vec.push_back( *e );
s.pop();
}
}
// 打印出從s點到w點的路徑
void showPath(int w){
assert( w >= 0 && w < G.V() );
assert( hasPathTo(w) );
vector<Edge<Weight>> vec;
shortestPath(w, vec);
for( int i = 0 ; i < vec.size() ; i ++ ){
cout<<vec[i].v()<<" -> ";
if( i == vec.size()-1 )
cout<<vec[i].w()<<endl;
}
}
};
複製代碼
main.cpp:3d
// 測試咱們的Dijkstra最短路徑算法
int main() {
string filename = "testG1.txt";
int V = 5;
SparseGraph<int> g = SparseGraph<int>(V, true);
// Dijkstra最短路徑算法一樣適用於有向圖
//SparseGraph<int> g = SparseGraph<int>(V, false);
ReadGraph<SparseGraph<int>, int> readGraph(g, filename);
cout<<"Test Dijkstra:"<<endl<<endl;
Dijkstra<SparseGraph<int>, int> dij(g,0);
for( int i = 0 ; i < V ; i ++ ){
if(dij.hasPathTo(i)){
cout<<"Shortest Path to "<<i<<" : "<<dij.shortestPathTo(i)<<endl;
dij.showPath(i);
}
else
cout<<"No Path to "<<i<<endl;
cout<<"----------"<<endl;
}
return 0;
}
複製代碼
dijkstra算法不能處理負權邊code
擁有負權環的不存在最短路徑
兩邊造成負權環
鬆弛操做的核心是咱們找到了一條邊的路徑,咱們看一下有沒有兩條邊的路徑比他權值小。
// 使用BellmanFord算法求最短路徑
template <typename Graph, typename Weight>
class BellmanFord{
private:
Graph &G; // 圖的引用
int s; // 起始點
Weight* distTo; // distTo[i]存儲從起始點s到i的最短路徑長度
vector<Edge<Weight>*> from; // from[i]記錄最短路徑中, 到達i點的邊是哪一條
// 能夠用來恢復整個最短路徑
bool hasNegativeCycle; // 標記圖中是否有負權環
// 判斷圖中是否有負權環
bool detectNegativeCycle(){
for( int i = 0 ; i < G.V() ; i ++ ){
typename Graph::adjIterator adj(G,i);
for( Edge<Weight>* e = adj.begin() ; !adj.end() ; e = adj.next() )
if( from[e->v()] && distTo[e->v()] + e->wt() < distTo[e->w()] )
return true;
}
return false;
}
public:
// 構造函數, 使用BellmanFord算法求最短路徑
BellmanFord(Graph &graph, int s):G(graph){
this->s = s;
distTo = new Weight[G.V()];
// 初始化全部的節點s都不可達, 由from數組來表示
for( int i = 0 ; i < G.V() ; i ++ )
from.push_back(NULL);
// 設置distTo[s] = 0, 而且讓from[s]不爲NULL, 表示初始s節點可達且距離爲0
distTo[s] = Weight();
from[s] = new Edge<Weight>(s, s, 0); // 這裏咱們from[s]的內容是new出來的, 注意要在析構函數裏delete掉
// Bellman-Ford的過程
// 進行V-1次循環, 每一次循環求出從起點到其他全部點, 最多使用pass步可到達的最短距離
for( int pass = 1 ; pass < G.V() ; pass ++ ){
// 每次循環中對全部的邊進行一遍鬆弛操做
// 遍歷全部邊的方式是先遍歷全部的頂點, 而後遍歷和全部頂點相鄰的全部邊
for( int i = 0 ; i < G.V() ; i ++ ){
// 使用咱們實現的鄰邊迭代器遍歷和全部頂點相鄰的全部邊
typename Graph::adjIterator adj(G,i);
for( Edge<Weight>* e = adj.begin() ; !adj.end() ; e = adj.next() )
// 對於每個邊首先判斷e->v()可達
// 以後看若是e->w()之前沒有到達過, 顯然咱們能夠更新distTo[e->w()]
// 或者e->w()之前雖然到達過, 可是經過這個e咱們能夠得到一個更短的距離, 便可以進行一次鬆弛操做, 咱們也能夠更新distTo[e->w()]
if( from[e->v()] && (!from[e->w()] || distTo[e->v()] + e->wt() < distTo[e->w()]) ){
distTo[e->w()] = distTo[e->v()] + e->wt();
from[e->w()] = e;
}
}
}
hasNegativeCycle = detectNegativeCycle();
}
// 析構函數
~BellmanFord(){
delete[] distTo;
delete from[s];
}
// 返回圖中是否有負權環
bool negativeCycle(){
return hasNegativeCycle;
}
// 返回從s點到w點的最短路徑長度
Weight shortestPathTo( int w ){
assert( w >= 0 && w < G.V() );
assert( !hasNegativeCycle );
assert( hasPathTo(w) );
return distTo[w];
}
// 判斷從s點到w點是否聯通
bool hasPathTo( int w ){
assert( w >= 0 && w < G.V() );
return from[w] != NULL;
}
// 尋找從s到w的最短路徑, 將整個路徑通過的邊存放在vec中
void shortestPath( int w, vector<Edge<Weight>> &vec ){
assert( w >= 0 && w < G.V() );
assert( !hasNegativeCycle );
assert( hasPathTo(w) );
// 經過from數組逆向查找到從s到w的路徑, 存放到棧中
stack<Edge<Weight>*> s;
Edge<Weight> *e = from[w];
while( e->v() != this->s ){
s.push(e);
e = from[e->v()];
}
s.push(e);
// 從棧中依次取出元素, 得到順序的從s到w的路徑
while( !s.empty() ){
e = s.top();
vec.push_back( *e );
s.pop();
}
}
// 打印出從s點到w點的路徑
void showPath(int w){
assert( w >= 0 && w < G.V() );
assert( !hasNegativeCycle );
assert( hasPathTo(w) );
vector<Edge<Weight>> vec;
shortestPath(w, vec);
for( int i = 0 ; i < vec.size() ; i ++ ){
cout<<vec[i].v()<<" -> ";
if( i == vec.size()-1 )
cout<<vec[i].w()<<endl;
}
}
};
複製代碼
main.cpp:
// 測試Bellman-Ford算法
int main() {
string filename = "testG2.txt";
//string filename = "testG_negative_circle.txt";
int V = 5;
SparseGraph<int> g = SparseGraph<int>(V, true);
ReadGraph<SparseGraph<int>, int> readGraph(g, filename);
cout<<"Test Bellman-Ford:"<<endl<<endl;
BellmanFord<SparseGraph<int>, int> bellmanFord(g,0);
if( bellmanFord.negativeCycle() )
cout<<"The graph contain negative cycle!"<<endl;
else
for( int i = 1 ; i < V ; i ++ ) {
if (bellmanFord.hasPathTo(i)) {
cout << "Shortest Path to " << i << " : " << bellmanFord.shortestPathTo(i) << endl;
bellmanFord.showPath(i);
}
else
cout << "No Path to " << i << endl;
cout << "----------" << endl;
}
return 0;
}
複製代碼
用於有向圖,由於無向圖中一條負權邊就等價於兩個方向都有,會造成環。
具體實現,distTo[i] 初始化爲「正無窮」
if( from[e->v()] && (!from[e->w()] || distTo[e->v()] + e->wt() < distTo[e->w()]) ){
distTo[e->w()] = distTo[e->v()] + e->wt();
from[e->w()] = e;
}
複製代碼
利用隊列數據結構
queue-based bellman-ford算法
-------------------------華麗的分割線--------------------
看完的朋友能夠點個喜歡/關注,您的支持是對我最大的鼓勵。
想了解更多,歡迎關注個人微信公衆號:番茄技術小棧