1. 程式人生 > >獲取多條最短路徑的Dijkstra演算法

獲取多條最短路徑的Dijkstra演算法

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=-2vector<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;
    }