獲取多條最短路徑的Dijkstra演算法
阿新 • • 發佈:2019-01-08
Dijkstra演算法是單源最短路徑經典演算法,一般用於所有邊的權為非負數的情況下,有向圖和無向圖均可。
效率方面:儲存圖模型的資料結構有很多種,使用鄰接矩陣的話其空間複雜度都為O(E^2)。而如果是稀疏圖,使用鄰接連結串列更划算,空間複雜度為O(V+E)。在每次搜尋離起點最近的點方面,這裡用的還是vector(最近在複習vector….),所以每一次的時間複雜度還是O(N),其實可以使用優先佇列實現,將時間複雜度降到O(logN)。
路徑數目方面:一般演算法使用一個一維陣列記錄每個點的前驅點,以此記錄指定點到每個點的最短路徑。但是由於每個點只保留一個前驅點,因此最後得到的最短路徑只有一條,尋找過程中其他距離相等的最短路徑會被拋棄,而做到儲存多條最短路徑,可以讓每一個點都維護一個自己的前驅點陣列 。對於點i,在遇到距離相同的路徑的時候,把i在這條路徑的前驅點記錄下來,而不是拋棄或者覆蓋;而遇到更短的路徑的時候,把i的前驅點陣列清空,存入新的前驅點。這樣最後每個點的前驅陣列大小都不同。
/**
*
* @param start 起點
* @param dest 終點
* @param adj 鄰接連結串列。adj[i][k]表示節點i的第k個鄰接點的索引
* @param distance 到起點的距離。distance[i]表示起點到點i的距離
* @return prevPoints 前驅點陣列。 prevPoints[k]表示到達點k的最短路徑中的所有前驅點
*/
vector<vector<int> > dijkstra(vector<vector<int> > adj,int start, int dest=-2, vector<int> distance) {
unsigned long num = adj.size();
vector<bool> visited(num, false);
visited[start] = true;
vector<vector<int> > prevPoints;
//前驅點陣列初始化
for (int i = 0; i < num; ++i) {
if (distance[i] < 999) {
prevPoints.push_back(vector<int>(1,start));
}
else{
prevPoints.push_back(vector<int>(1,-1));
}
}
if (prevPoints[dest][0] == start) {
return prevPoints;
}
for (int i = 1; i < num; ++i) {
// 找離起點最近的點
// 這裡的複雜度還是O(N),可以通過使用優先佇列優化
int closest = 999;
int toVisit = -1;
for (int j = 0; j < num; ++j) {
if (!visited[j] && distance[j] < closest) {
closest = distance[j];
toVisit = j;
}
}
//如果是要找指定終點dest,可以提前剪枝,
//但這樣的話未訪問的點的路徑就不能保證是最短的。
if (toVisit != -1 && !(dest != -2 && toVisit == dest) ) {
//更新最短路徑
visited[toVisit] = true;
for (int k = 0; k < adj[toVisit].size(); k++) {
if (adj[toVisit][k] != -1 && !visited[adj[toVisit][k]]) {
if (distance[adj[toVisit][k]] > distance[toVisit] + 1) {
//update the distance
distance[adj[toVisit][k]] = distance[toVisit] + 1;
//clear the paths,and store the new path
prevPoints[adj[toVisit][k]].clear();
prevPoints[adj[toVisit][k]].push_back(toVisit);
} else if (distance[adj[toVisit][k]] == distance[toVisit] + 1) {
//add the new path
prevPoints[adj[toVisit][k]].push_back(toVisit);
}
}
}
} else {
break;
}
}
return prevPoints;
}
得到的前驅點陣列可以用DFS從終點往回獲取。
if (prevPoints[dest][0] != -1){
index = dest;
results = getPaths(start,index,prevPoints);
}
/**
*
* @param start 起點
* @param index 終點
* @param prevPoints 前驅點陣列。prevPoints[k]表示到達點k的最短路徑中的所有前驅點
* @return paths 路徑。paths[k]表示從start到index的第k條最短路徑
*/
vector<vector<int> > getPaths(int start,int index, vector<vector<int> >& prevPoints){
vector<vector<int> >childPaths;
vector<vector<int> >midPaths;
if (index != start){
for (int i = 0; i < prevPoints[index].size(); ++i) {
childPaths = getPaths(start,prevPoints[index][i],prevPoints);
for (int j = 0; j < childPaths.size(); ++j) {
childPaths[j].push_back(index);
}
if(midPaths.empty()){
midPaths = childPaths;
} else{
midPaths.insert(midPaths.end(),childPaths.begin(),childPaths.end());
}
}
}
else{
// 第一個點
midPaths.push_back(vector<int>(1,start));
}
return midPaths;
}