基于Dijkstra算法的最短路问题求解


迪杰斯特拉算法是由荷兰计算机科学家在1956年发现的算法,此算法使用类似广度优先搜索的方法解决了带权图的单源最短路径问题。它是一个贪心算法。

一、Dijkstra算法原理、步骤

  1. 算法输入:赋权(非负权)有向图 G = ( V , A ) G=(V,A) G=(V,A),指定一个起点 v 0 v_0 v0(即从顶点 v 0 v_0 v0开始计算)
  2. 初始化:当前点 v i v_i vi=起点 v 0 v_0 v0,数组 P P P T T T,数组 P P P中只有起点 v 0 v_0 v0,即 P = { v 0 } P=\{ v_0\} P={v0};数组T中是除起点 v 0 v_0 v0之外的顶点,并且数组T中记录各顶点到起点 v 0 v_0 v0的距离。如果顶点与起点 v 0 v_0 v0不相邻,距离为无穷大。

    P的作用是记录已求出最短路径的顶点(以及相应的最短路径长度),而T则是记录还未求出最短路径的顶点(以及该顶点到起点V_0的距离)

  3. T = { } T=\{\} T={},算法停止,输出集合 P P P;否则转入step4。
  4. 获取当前点v_i的邻接点,更新邻接点的距离,从数组T中找出路径最短的顶点 v k v_k vk,并将其加入到数组P中;同时,从数组T中移除顶点 v k v_k vk
  5. 令当前点 v i v_i vi= v k v_k vk,执行step3。

二、基于Dijkstra算法的最短路问题求解(Python)

参考https://blog.csdn.net/xiaoxi_hahaha/article/details/110257368中的题目:

在这里插入图片描述
完整代码如下:

import numpy as np

inf = np.inf


def dijkstra(graph, source):
    permanent = {source: 0}  # 永久标号
    temporary = {}  # 临时标号

    nodes = graph.keys()  # 顶点集合

    # 初始化
    for node in nodes:
        if node != source:
            temporary[node] = inf

    current_node = source

    while temporary:  # 若临时标号集合不为空,算法继续
        neighbor_weight_list = graph[current_node]
        for n, w in neighbor_weight_list:
            if permanent[current_node] + w < temporary[n]:
                temporary[n] = permanent[current_node] + w

        min_key = None
        min_val = 1e6
        for k, v in temporary.items():
            if v < min_val:
                min_val = v
                min_key = k
        temporary.pop(min_key)

        permanent[min_key] = min_val
        current_node = min_key

    print(permanent)


if __name__ == "__main__":
	# 有向图的邻接表,点:【(临界点,权值),(邻接点,权值)】
    graph = {
        0: [(1, 5), (2, 2)],
        1: [(3, 1), (4, 6)],
        2: [(3, 6), (5, 8)],
        3: [(4, 1), (5, 2)],
        4: [(6, 7)],
        5: [(6, 3)],
        6: []
    }
    dijkstra(graph, 0)

三、基于Dijkstra算法的最短路问题求解(Java)

为方便说明,做以下定义:

  1. 顶点:赋权图中的点,顶点集合 V = { A , B , C , D , E , F } V=\{A,B,C,D,E,F\} V={A,B,C,D,E,F}
  2. 标号:这里标号对应于运筹学中P标号。对某个点 v i , ∀ i ∈ V v_i,\forall i \in V vi,iV的标号为从起点到 v i v_i vi的最短距离。如对 v i = B v_i=B vi=B,则 B . d i s t a n c e = 5 B.distance = 5 B.distance=5
  3. 邻居:定义 v i v_i vi的邻居为:和 v i v_i vi直接相连接且未被标号的点。

3.1算法流程

  1. 算法输入:顶点集合,邻接矩阵
  2. 初始化每个顶点 { A , B , C , D , E , F } \{A,B,C,D,E,F\} {A,B,C,D,E,F},将所有顶点入队。 v 1 . d i s t a n c e = 0 , v i . d i s t a n c e = ∞ , ∀ v i ∈ V ∖ { v 1 } , v i . m a r k e d = f a l s e v_1.distance =0,v_i.distance=∞, \forall v_i\in V \setminus \{v_1\},v_i.marked=false v1.distance=0,vi.distance=,viV{v1}vi.marked=false。将所有顶点入优先队列再弹出起点;而上面Python求解中,起点入P集合,其他顶点入T集合是一样的。
  3. 弹出队头顶点 j j j,标记为已访问。
  4. 获取队头的邻接点。
  5. 更新邻接点的标号
  6. 若队列不为空,返回 step2。

3.2实例分析

step1:初始化:所有元素入优先级队列,如图3

step2:

  1. 弹出队头元素 A = 0 A=0 A=0
  2. 得到 A A A的邻居, n e i g h b o r s A = { B , C } neighbors_A=\{B,C\} neighborsA={B,C}
  3. 更新邻居标号,如图4
  4. 队列 [ B = 6 , C = 3 , D = ∞ , ∞ , F = ∞ ] [B=6,C=3,D=\infty,\infty,F=\infty] [B=6,C=3,D=,,F=]不为空。

step3:

  1. 弹出队列头元素 C = 3 C=3 C=3

  2. 得到 C C C的邻居, n e i g h b o r s C = { B , D , E } neighbors_C=\{B,D,E\} neighborsC={B,D,E}

  3. 更新邻居标号,如图5

  4. 队列 [ B = 5 , D = 6 , E = 7 , F = ∞ ] [B=5,D=6,E=7,F=\infty] [B=5,D=6,E=7,F=]不为空。

step4:

  1. 弹出队列头元素 B B B
  2. 得到 B B B的邻居, n e i g h b o r s B = { D } neighbors_B=\{D\} neighborsB={D}
  3. 更新邻居标号, 5 + 5 < 6 5+5<6 5+5<6,D点标号无需更新,如图6
  4. 队列 [ D = 6 , E = 7 , F = ∞ ] [D=6,E=7,F=\infty] [D=6,E=7,F=]不为空。

step5:

  1. 弹出队列头元素 D D D

  2. 得到 D D D的邻居, n e i g h b o r s D = { E , F } neighbors_D=\{E,F\} neighborsD={E,F}

  3. 更新邻居标号,如图7

  4. 队列 [ E = 7 , F = 9 ] [E=7,F=9] [E=7,F=9]不为空。

step5:

  1. 弹出队列头元素 E E E
  2. 得到 D D D的邻居, n e i g h b o r s E = { F } neighbors_E=\{F\} neighborsE={F}
  3. 更新邻居标号,如图8
  4. 队列 [ F = 9 ] [F=9] [F=9]不为空。

step5:

  1. 弹出队列头元素 F F F
  2. 得到 D D D的邻居, n e i g h b o r s F = { N U L L } neighbors_F=\{NULL\} neighborsF={NULL}
  3. 更新邻居标号
  4. 队列 [ ] [] []为空,算法结束

注:Dijkstra算法应用优先队列的原因:每次弹出的队列的元素是有优先级的,即标号最小的优先弹出,这里也看出Dijkstra就是一种贪婪算法。

3.3完整代码

  • Vertex类:对赋权有向图中的顶点进行封装,每个顶点有3个属性:顶点名称、永久标号、是否被访问。
  • Graph类:赋权有向图,有顶点两个属性。
  • Dijkstra类:算法运行类。
  • Main类:程序运行类,程序开始,初始化顶点,和边的信息。有顶点和边构建图。将图传入算法中运行即可。

import java.util.*;

public class Dijkstra {
    private final Graph graph;
    private final List<Vertex> vertices;
    private final int[][] edges;
    private Queue<Vertex> temporary;
    private Set<Vertex> permanent;

    Dijkstra(Graph graph) {
        this.graph = graph;
        this.vertices = graph.getVertices();
        this.edges = graph.getEdges();

    }

    private void init(int start) {
        vertices.get(start).setDistance(0);
        temporary = new PriorityQueue<>(Comparator.comparingInt(Vertex::getDistance));
        temporary.addAll(vertices);
        permanent = new TreeSet<>(Comparator.comparingInt(Vertex::getDistance)) {{
            add(vertices.get(start));
        }};

        System.out.printf("initialize: %s\n", temporary);
    }

    void search(int start) {
        init(start);
        System.out.println("dijkstra searching...");
        while (!temporary.isEmpty()) {
            Vertex vertex = temporary.poll(); // 出队列

            vertex.setMarked(true);

            List<Vertex> neighbors = graph.getNeighbors(vertex);

            updateDistance(vertex, neighbors);
        }
        System.out.println(permanent);
    }

    private void updateDistance(Vertex vertex, List<Vertex> neighbors) {
        for (Vertex neighbor : neighbors) {
            updateDistance(vertex, neighbor);
        }
    }

    private void updateDistance(Vertex vertex, Vertex neighbor) {
        int distance = edges[vertices.indexOf(vertex)][vertices.indexOf(neighbor)] + vertex.getDistance();
        if (distance < neighbor.getDistance()) {
            temporary.remove(neighbor);
            neighbor.setDistance(distance);
            permanent.add(neighbor);
            temporary.add(neighbor);
        }
    }

    void writeAns() {
        System.out.println("\n initial data: ");
        for (int i = 0; i < edges.length; i++) {
            for (int j = 0; j < edges[i].length; j++) {
                if (edges[i][j] == Integer.MAX_VALUE) {
                    System.out.print("M" + "	");
                } else {
                    System.out.print(edges[i][j] + "	");
                }
            }
            System.out.println();
        }
    }
}

class Graph {

    private List<Vertex> vertices;
    private int[][] edges;

    public Graph(String[] vertexName, int[][] adjacentList) {
        this.vertices = new ArrayList<>();
        this.edges = adjacentList;

        Vertex[] vertices = new Vertex[vertexName.length];
        for (int i = 0; i < vertexName.length; i++) {
            vertices[i] = new Vertex(vertexName[i]);
        }
        Collections.addAll(this.vertices, vertices);
    }

    public Graph() {

    }

    private Vertex getVertex(int index) {
        return vertices.get(index);
    }

    List<Vertex> getNeighbors(Vertex v) {
        List<Vertex> neighbors = new ArrayList<>();
        int index = vertices.indexOf(v);
        for (int i = 0; i < vertices.size(); i++) {
            if (i == index) continue;
            if (edges[index][i] < Integer.MAX_VALUE) {
                Vertex neighbor = getVertex(i);
                if (!neighbor.isMarked())
                    neighbors.add(neighbor);
            }
        }
        return neighbors;
    }


    public List<Vertex> getVertices() {
        return vertices;
    }

    public int[][] getEdges() {
        return edges;
    }
}

class Vertex {
    private String name;
    private int distance;
    private boolean marked;

    Vertex(String name) {
        this.name = name;
        this.distance = Integer.MAX_VALUE;
        setMarked(false);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getDistance() {
        return distance;
    }

    public void setDistance(int path) {
        this.distance = path;
    }

    public boolean isMarked() {
        return marked;
    }

    public void setMarked(boolean marked) {
        this.marked = marked;
    }

    @Override
    public String toString() {
        return name + "=" + distance;
    }
}

class Main {
    private final static int M = Integer.MAX_VALUE;

    public static void main(String[] args) {
        // 节点名称
        String[] vertexName = new String[]{"A", "B", "C", "D", "E", "F"};
        // 邻接矩阵
        int[][] adjacentList = {
                {0, 6, 3, M, M, M},
                {6, 0, 2, 5, M, M},
                {3, 2, 0, 3, 4, M},
                {M, 5, 3, 0, 5, 3},
                {M, M, 4, 5, 0, 5},
                {M, M, M, 3, 5, 0}
        };
        Graph graph = new Graph(vertexName, adjacentList);
        Dijkstra dijkstra = new Dijkstra(graph);
        dijkstra.search(0);
        dijkstra.writeAns();
    }
}

运行结果如下:

[A=0, C=3, B=5, D=6, E=7, F=9]

参考

  • https://blog.csdn.net/xiaoxi_hahaha/article/details/110257368
  • https://zhuanlan.zhihu.com/p/346558578
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值