圖的最短路徑

最短路徑是指兩個(gè)頂點(diǎn)之間權(quán)值之和最小的路徑, 但是不能有負(fù)權(quán)環(huán)

有權(quán)有向圖和無向圖最短路徑
無權(quán)有向圖無向圖路徑

有負(fù)權(quán)邊, A 到E 最短路徑, A -> B -> E

有負(fù)權(quán)路徑

有負(fù)權(quán)環(huán), 不存在最短路徑

有環(huán)的負(fù)權(quán)路徑

最短路徑典型應(yīng)用之一, 路徑規(guī)劃問題

3 個(gè)經(jīng)典算法

單源最短路徑

  • Dijkstra
  • Bellman-Ford

多源路徑

  • Floyd

Dijkstra

用于計(jì)算一個(gè)頂點(diǎn)到其他所有頂點(diǎn)的最短路徑, 但是不能有負(fù)權(quán)邊

時(shí)間復(fù)雜度, 可以優(yōu)化至O(ElogV), E 是邊數(shù)量, V 是節(jié)點(diǎn)數(shù)量

假想所有頂點(diǎn)是石頭, 邊為連著兩塊石頭的繩子, 平放在桌子上

將石頭A, 提起, 遠(yuǎn)離桌面, B, D, C 會(huì)依次離開桌面

最后繃直的繩子就是A 到其他小石頭的最短路徑, 后離開桌面的小石頭, 都是被先離開桌面的小石頭拉起的

dijkstra想象成石頭
石頭A提起

執(zhí)行過程

綠色代表已經(jīng)離開桌面, 確定了最短路徑

紅色更新最短路徑信息

dijkstra執(zhí)行過程1
dijkstra執(zhí)行過程2
dijkstra執(zhí)行過程3

松弛操作(Relaxation): 更新兩個(gè)頂點(diǎn)之間最短路徑, 找出更短的路徑

抽象類

public abstract class Graph<V, E> {
    
    protected WeightManager<E> weightManager;
    
    public Graph() {}
    
    public Graph(WeightManager<E> weightManager) {
        this.weightManager = weightManager;
    }
    
    public abstract int edgesSize();
    public abstract int verticesSize();
    
    public abstract void addVertex(V v);
    public abstract void addEdge(V from, V to);
    public abstract void addEdge(V from, V to, E weight);
    
    public abstract void removeVertex(V v);
    public abstract void removeEdge(V from, V to);
    
    public abstract void bfs(V begin, VertexVisitor<V> visitor);
    public abstract void dfs(V begin, VertexVisitor<V> visitor);
    
    public abstract Set<EdgeInfo<V, E>> mst();
    
    public abstract List<V> topologicalSort();
    
//  public abstract Map<V, E> shortestPath(V begin);
    public abstract Map<V, PathInfo<V, E>> shortestPath(V begin);
    public abstract Map<V, Map<V, PathInfo<V, E>>> shortestPath();
    
    public interface WeightManager<E> {
        int compare(E e1, E e2);
        E add(E e1, E e2);
        E zero();
    }
    
    public interface VertexVisitor<V> {
        boolean visit(V v);
    }
    
    public static class PathInfo<V, E> {
        protected E weight;
        protected List<EdgeInfo<V, E>> edgeInfos = new LinkedList<>();
        
        public PathInfo() {}
        public PathInfo(E weight) {
            this.weight = weight;
        }

        public E getWeight() {
            return weight;
        }
        
        public void setWeight(E weight) {
            this.weight = weight;
        }
        
        public List<EdgeInfo<V, E>> getEdgeInfos() {
            return edgeInfos;
        }
        
        public void setEdgeInfos(List<EdgeInfo<V, E>> edgeInfos) {
            this.edgeInfos = edgeInfos;
        }

        @Override
        public String toString() {
            return "PathInfo [weight=" + weight + ", edgeInfos=" + edgeInfos + "]";
        }
        
    }
    
    public static class EdgeInfo<V, E> {
        private V from;
        private V to;
        private E weight;
        
        public EdgeInfo(V from, V to, E weight) {
            this.from = from;
            this.to = to;
            this.weight = weight;
        }

        
        public V getFrom() {
            return from;
        }

        
        public void setFrom(V from) {
            this.from = from;
        }

        
        public V getTo() {
            return to;
        }

        
        public void setTo(V to) {
            this.to = to;
        }

        
        public E getWeight() {
            return weight;
        }
        
        public void setWeight(E weight) {
            this.weight = weight;
        }

        @Override
        public String toString() {
            return "EdgeInfo [from=" + from + ", to=" + to + ", weight=" + weight + "]";
        }
        
    }
    
}
    private Map<V, PathInfo<V, E>> dijkstra(V begin) {
        Vertex<V, E> beginVertex = vertices.get(begin);
        if (beginVertex == null) return null;
        
        Map<V, PathInfo<V, E>> selectedPaths = new HashMap<>();
        Map<Vertex<V, E>, PathInfo<V, E>> paths = new HashMap<>();
        
        // 初始化paths
        for (Edge<V, E> edge : beginVertex.outEdges) {
            PathInfo<V, E> path = new PathInfo<>();
            path.weight = edge.weight;
            path.edgeInfos.add(edge.info());
            paths.put(edge.to, path);
        }
        
        while (!paths.isEmpty()) {
            Entry<Vertex<V, E>, PathInfo<V, E>> minEntry = getMinPath(paths);
            Vertex<V, E> minVertex = minEntry.getKey();
            PathInfo<V, E> minPath = minEntry.getValue();
            selectedPaths.put(minVertex.value, minEntry.getValue());
            paths.remove(minVertex);
            for (Edge<V, E> edge : minVertex.outEdges) {
                // 如果edge.to 已經(jīng)離開桌面, 就沒必要進(jìn)行松弛操作
                if (selectedPaths.containsKey(edge.to.value)) continue;
                relaxDijkstra(edge, minPath, paths);
//               新的最短路徑, beginVertex 到edge.from 的最短路徑 + edge.weight
//              E newWeight = weightManager.add(minEntry.getValue().weight, edge.weight);
//              // 以前的最短路徑, beginVertex 到edge.to 的最短路徑
//              PathInfo<V, E> oldPath = paths.get(edge.to);
//              if (oldPath != null && weightManager.compare(newWeight, oldPath.weight) >= 0) continue;
//              if (oldPath == null) {
//                  oldPath = new PathInfo<>();
//                  paths.put(edge.to, oldPath);
//              } else {
//                  oldPath.edgeInfos.clear();
//              }
//
//              oldPath.weight = newWeight;
//              oldPath.edgeInfos.addAll(minEntry.getValue().edgeInfos);
//              oldPath.edgeInfos.add(edge.info());
            }
        }
        selectedPaths.remove(begin);
        return selectedPaths;
    }
    
    /**
     * 松弛
     * @param edge 需要進(jìn)行松弛的邊
     * @param fromPath edge的from的最短路徑信息
     * @param paths
     */
    private void relaxDijkstra(Edge<V, E> edge, PathInfo<V, E> fromPath, Map<Vertex<V, E>, PathInfo<V, E>> paths) {
        // 新的最短路徑, beginVertex 到edge.from 的最短路徑 + edge.weight
        E newWeight = weightManager.add(fromPath.weight, edge.weight);
        // 以前的最短路徑, beginVertex 到edge.to 的最短路徑
        PathInfo<V, E> oldPath = paths.get(edge.to);
        if (oldPath != null && weightManager.compare(newWeight, oldPath.weight) >= 0) return;
        if (oldPath == null) {
            oldPath = new PathInfo<>();
            paths.put(edge.to, oldPath);
        } else {
            oldPath.edgeInfos.clear();
        }

        oldPath.weight = newWeight;
        oldPath.edgeInfos.addAll(fromPath.edgeInfos);
        oldPath.edgeInfos.add(edge.info());
        
    }
    
    /**
     * 返回paths 中的最小路徑
     * @param path
     * @return
     */
    private Entry<Vertex<V, E>, PathInfo<V, E>> getMinPath(Map<Vertex<V, E>, PathInfo<V, E>> paths) {
        Iterator<Entry<Vertex<V, E>, PathInfo<V, E>>> it = paths.entrySet().iterator();
        Entry<Vertex<V, E>, PathInfo<V, E>> minEntry = it.next();
        while (it.hasNext()) {
            Entry<Vertex<V, E>, PathInfo<V, E>> entry = it.next();
            if (weightManager.compare(entry.getValue().weight, minEntry.getValue().weight) < 0) {
                minEntry = entry;
            }
        }
        return minEntry;
    }

Bellman-Ford

支持負(fù)權(quán)邊, 可以檢測(cè)出是否有負(fù)權(quán)環(huán)

原理: 對(duì)所有的邊進(jìn)行V - 1 次松弛操作, 得到所有可能的最短路徑

時(shí)間復(fù)雜度O(EV), E 為邊數(shù)量, V 是節(jié)點(diǎn)數(shù)量

例如, 對(duì)下圖, 所有邊盡一次松弛, 找出A 到所有頂點(diǎn)最短路徑, 順序從左到右

A到所有頂點(diǎn)的松弛操作

最壞情況, 恰好每次從右到左對(duì)邊進(jìn)行松弛操作, 所有邊進(jìn)行V - 1 次松弛操作, 才能計(jì)算出A 到其他所有頂點(diǎn)最短路徑

最壞情況松弛

松弛操作

松弛操作

每次松弛順序DC, DF, BC, ED, EF, BE, AE, AB

執(zhí)行過程1
執(zhí)行過程2
正常過程3
    private Map<V, PathInfo<V, E>> bellmanFord(V begin) {
        Vertex<V, E> beginVertex = vertices.get(begin);
        if (beginVertex == null) return null;
        
        Map<V, PathInfo<V, E>> selectedPaths = new HashMap<>();
        selectedPaths.put(begin, new PathInfo<>(weightManager.zero()));
        
        int count = vertices.size() - 1;
        for (int i = 0; i < count; i++) {
            for (Edge<V, E> edge : edges) {
                PathInfo<V, E> pathFrom = selectedPaths.get(edge.from.value);
//              if (pathFrom == null) continue;
                relax(edge, pathFrom, selectedPaths);
            }
        }
        
        for (Edge<V, E> edge : edges) {
            PathInfo<V, E> pathFrom = selectedPaths.get(edge.from.value);
//          if (pathFrom == null) continue;
            if (relax(edge, pathFrom, selectedPaths)) {
                System.out.println("有負(fù)權(quán)環(huán)");
                return null;
            }
        }
        
        selectedPaths.remove(begin);
        return selectedPaths;
    }
    
    /**
     * 松弛
     * @param edge 需要進(jìn)行松弛的邊
     * @param fromPath edge的from的最短路徑信息
     * @param paths
     */
    private boolean relax(Edge<V, E> edge, PathInfo<V, E> fromPath, Map<V, PathInfo<V, E>> paths) {
        // 新的最短路徑, beginVertex 到edge.from 的最短路徑 + edge.weight
        E newWeight = weightManager.add(fromPath.weight, edge.weight);
        // 以前的最短路徑, beginVertex 到edge.to 的最短路徑
        PathInfo<V, E> oldPath = paths.get(edge.to.value);
        if (oldPath != null && weightManager.compare(newWeight, oldPath.weight) >= 0) return false;
        if (oldPath == null) {
            oldPath = new PathInfo<>();
            paths.put(edge.to.value, oldPath);
        } else {
            oldPath.edgeInfos.clear();
        }

        oldPath.weight = newWeight;
        oldPath.edgeInfos.addAll(fromPath.edgeInfos);
        oldPath.edgeInfos.add(edge.info());
        return true;
    }

Floyd

多源路徑算法, 能夠求出任意2 個(gè)頂點(diǎn)之間的最短路徑, 支持負(fù)權(quán)邊

時(shí)間復(fù)雜度O(V^3), 效率比執(zhí)行V 次Dijkstra 算法要好

原理:

從任意頂點(diǎn)i 到任意頂點(diǎn) j 的最短路徑包括

  1. 直接i 到j(luò)
  2. 從i 經(jīng)過若干頂點(diǎn)到j(luò)

假設(shè)dist(i, j)為頂點(diǎn)i 到j(luò) 的最短路徑距離

對(duì)于每一個(gè)頂點(diǎn)k, 檢查dist(i, k) + dist(k, j) < dist(i, j) 是否成立

如果成立, 證明從i 到k 再到j(luò) 的路徑比i 直接到j(luò) 的路徑, 設(shè)置dist(i, j) = dist(i, k) + dist(k, j)

遍歷所有節(jié)點(diǎn)k, dist(i, j) 中記錄的便是i 到j(luò) 的最短路徑的距離

    public Map<V, Map<V, PathInfo<V, E>>> shortestPath() {
        Map<V, Map<V, PathInfo<V, E>>> paths = new HashMap<>();
        // 初始化
        for (Edge<V, E> edge : edges) {
            Map<V, PathInfo<V, E>> map = paths.get(edge.from.value);
            if (map == null) {
                map = new HashMap<>();
                paths.put(edge.from.value, map);
            }
            PathInfo<V, E> pathInfo = new PathInfo<>(edge.weight);
            pathInfo.edgeInfos.add(edge.info());
            map.put(edge.to.value, pathInfo);
        }
        
        vertices.forEach((V v2, Vertex<V, E> vertex2) -> {
            vertices.forEach((V v1, Vertex<V, E> vertex1) -> {
                vertices.forEach((V v3, Vertex<V, E> vertex3) -> {
                    if (v1.equals(v2) || v2.equals(v3) ||v1.equals(v3)) return;
                    
                    // v1 -> v2
                    PathInfo<V, E> path12 = getPathInfo(v1, v2, paths);
                    if (path12 == null) return;
                    
                    // v2 -> v3
                    PathInfo<V, E> path23 = getPathInfo(v2, v3, paths);
                    if (path23 == null) return;
                    
                    // v1 -> v3
                    PathInfo<V, E> path13 = getPathInfo(v1, v3, paths);
                    E newWeight = weightManager.add(path12.weight, path23.weight);
                    if (path13 != null && weightManager.compare(newWeight, path13.weight) >= 0) return;
                    
                    if (path13 == null) {
                        path13 = new PathInfo<>();
                        paths.get(v1).put(v3, path13);
                    } else {
                        path13.edgeInfos.clear();
                    }
                    
                    path13.weight = newWeight;
                    path13.edgeInfos.addAll(path12.edgeInfos);
                    path13.edgeInfos.addAll(path23.edgeInfos);
                    
                });
            });
        });
        
        return paths;
    }
    
    private PathInfo<V, E> getPathInfo(V from, V to, Map<V, Map<V, PathInfo<V, E>>> paths) {
        Map<V, PathInfo<V, E>> map = paths.get(from);
        return map == null ? null : map.get(to);
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 摘要 最短路徑問題是一個(gè)在圖論研究中很經(jīng)典的問題,已經(jīng)被應(yīng)用到GIS、GPS等信息管理系統(tǒng)中,為人們生活帶來了很大...
    偏偏孤倨引山洪閱讀 334評(píng)論 0 1
  • 摘要 最短路徑問題是一個(gè)在圖論研究中很經(jīng)典的問題,已經(jīng)被應(yīng)用到GIS、GPS等信息管理系統(tǒng)中,為人們生活帶來了很大...
    你本無意穿堂風(fēng)_a69c閱讀 657評(píng)論 2 3
  • 圖論最短路徑問題 最最原始的問題——兩點(diǎn)間的最短路這類背景一般是類似:已知各城市之間距離,請(qǐng)給出從城市A到城市B的...
    yuq329閱讀 667評(píng)論 0 0
  • 求圖的最短路徑(詳談Floyd和Dijkstra) (注:在這一部分起點(diǎn)、源點(diǎn)意思相近;點(diǎn)的距離、邊的長度、權(quán)值意...
    _黑色吊椅閱讀 2,923評(píng)論 0 9
  • 前言 本專題旨在快速了解常見的數(shù)據(jù)結(jié)構(gòu)和算法。 在需要使用到相應(yīng)算法時(shí),能夠幫助你回憶出常用的實(shí)現(xiàn)方案并且知曉其優(yōu)...
    蠻三刀醬閱讀 3,536評(píng)論 0 0

友情鏈接更多精彩內(nèi)容