【数据结构】图(五)—— 最短路径算法(二)—— Dijkstra Algorithm(迪杰斯特拉算法)

  • Dijkstra algorithm 特点
  • 特别重要
  • 单源最短路径
  • 算法的思路
  • 算法步骤
  • 算法图示
  • 代码编写
  • python 代码
  • java 代码



重点细节

计算非加权图中的最短路径,(找出段数最少的路径)可使用 广度优先算法。
计算加权图中的最短路径,(找出最快的路径)可使用 迪杰斯特拉算法 (Dijkstra algorithm)

Dijkstra algorithm 特点

特别重要
  1. 贪心算法思想的一种体现;
  2. 解决赋权有向加权图的单源最短路径问题;
  3. 仅适用于权值为 非负的有向加权图;
  4. 只适用于 有向无环图(directed acyclic graph);

无向图意味着两个节点彼此指向对方,其实就是环;
在无向图中每一条边都是环。

单源最短路径

给定一个带权有向图G=(V,E),其中每条边的权是一个实数。另外,还给定 V 中的一个顶点,称为源。要计算从源到其他所有各顶点的最短路径长度。这里的长度就是指路上各边权之和。这个问题通常称为单源最短路径问题

通俗的讲:就是计算有向图中某一个特定的点(称之为源点),到有向图中其它各点的路径长度。

指定一个点(源点)到其余各个顶点的最短路径,也叫做“单源最短路径

Dijkstra(迪杰斯特拉)算法是典型的最短路径路由算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。Dijkstra算法能得出最短路径的最优解,但由于它遍历计算的节点很多,所以效率低。

迪科斯彻算法使用了广度优先搜索解决赋权有向图或者无向图的单源最短路径问题,算法最终得到一个最短路径树。该算法常用于路由算法或者作为其他图算法的一个子模块。

算法的思路

Dijkstra算法采用的是一种贪心的策略,声明一个数组dis来保存源点到各个顶点的最短距离和一个保存已经找到了最短路径的顶点的集合:T,初始时,原点 s 的路径权重被赋为 0 (dis[s] = 0)。若对于顶点 s 存在能直接到达的边(s,m),则把dis[m]设为w(s, m),同时把所有其他(s不能直接到达的)顶点的路径长度设为无穷大。初始时,集合T只有顶点s。

然后,从dis数组选择最小值,则该值就是源点s到该值对应的顶点的最短路径,并且把该点加入到T中,OK,此时完成一个顶点,

然后,我们需要看看新加入的顶点是否可以到达其他顶点并且看看通过该顶点到达其他点的路径长度是否比源点直接到达短,如果是,那么就替换这些顶点在dis中的值。
然后,又从dis中找出最小值,重复上述动作,直到T中包含了图的所有顶点。

算法步骤

设有向图的顶点为:python 一万个点中到直线最近距离的点_数据结构,设置两个集合 python 一万个点中到直线最近距离的点_数据结构_02,
S:存放已求出最短路径的顶点的集合;
T:python 一万个点中到直线最近距离的点_散列表_03, 尚未确定最短路径的顶点集合;

python 一万个点中到直线最近距离的点_散列表_04 中顶点按最短路径递增的次序加入到python 一万个点中到直线最近距离的点_算法_05中,注意每次向python 一万个点中到直线最近距离的点_算法_05集合中加入一个顶点后,必须对python 一万个点中到直线最近距离的点_散列表_04

  1. 初始化:先找出从源点 python 一万个点中到直线最近距离的点_散列表_08 到各终点 python 一万个点中到直线最近距离的点_结点_09 的直达路径 python 一万个点中到直线最近距离的点_结点_10, 即通过一条弧到达的路径;
  2. 从这些路径中找到一条长度最短的路径 python 一万个点中到直线最近距离的点_算法_11 并将此顶点加入python 一万个点中到直线最近距离的点_结点_12
  3. 更新:若在图中存在弧python 一万个点中到直线最近距离的点_算法_13,且python 一万个点中到直线最近距离的点_最短路径_14,则以路径python 一万个点中到直线最近距离的点_最短路径_15代替python 一万个点中到直线最近距离的点_结点_10
  4. 循环操作 2 和 3, 直至python 一万个点中到直线最近距离的点_结点_17

算法图示

视屏讲解十分形象,便于理解

python 一万个点中到直线最近距离的点_结点_18

  1. 第一步,python 一万个点中到直线最近距离的点_结点_19python 一万个点中到直线最近距离的点_数据结构_20到其它各点的距离,如下表可得:到python 一万个点中到直线最近距离的点_结点_21的距离最短,将python 一万个点中到直线最近距离的点_结点_21存入python 一万个点中到直线最近距离的点_数据结构_23 集合;
  2. python 一万个点中到直线最近距离的点_散列表_24

  3. 在更新 python 一万个点中到直线最近距离的点_数据结构_23 集合的基础上,将 python 一万个点中到直线最近距离的点_数据结构_23 集合上的点作为可连通的点,更新python 一万个点中到直线最近距离的点_结点_27 到其它python 一万个点中到直线最近距离的点_结点_28 集合的点;选取剩余点中距离最小的点存入python 一万个点中到直线最近距离的点_数据结构_23

python 一万个点中到直线最近距离的点_散列表_30


python 一万个点中到直线最近距离的点_最短路径_31 时,加入了 python 一万个点中到直线最近距离的点_最短路径_32 元素,如上图,python 一万个点中到直线最近距离的点_数据结构_33 可以通过 python 一万个点中到直线最近距离的点_最短路径_32 到其它元素的距离进行更新。结果见下表。

python 一万个点中到直线最近距离的点_结点_35

  1. 循环重复,直至 python 一万个点中到直线最近距离的点_结点_17

python 一万个点中到直线最近距离的点_散列表_37

代码编写

以 python 为例。

一般来说需要三个散列表和一个数组。

python 一万个点中到直线最近距离的点_结点_38

graph:
表示有向图中各顶点与其权值;

costs:
表示起点到其它各顶点的最短距离;

parent:
表示最短路径上某顶点的上一个结点(父结点)

以 下图为例:

python 一万个点中到直线最近距离的点_数据结构_39

  1. 表示顶点与顶点以及之间的权重关系
graph = {} 
graph["start"] = {}
graph["start"]["a"] = 6
graph["start"]["b"] = 2

因为设置了 graph[“start”] 为一个散列表,要获取其连通点:

graph["start"].keys()

获取 两顶点之间的权重:

print(graph["start"]["b"])

注意: 设置 graph[“start”] 为一个散列表是十分巧妙的操作,可以有效便捷的实现各项操作;

  1. 创建源点到 python 一万个点中到直线最近距离的点_结点_17
costs = {}
costs["a"] = 6
costs["b"] = 6
costs["fin"] = float("inf")
  1. 创建存储父节点的散列表
parents = {}
parents["a"] = "start"
parents["b"] = "start"
parents["fin"] = None

·4. 存放记录过的节点(存放python 一万个点中到直线最近距离的点_算法_05集合中的元素)

processed = []

注:之所以不需要存放python 一万个点中到直线最近距离的点_散列表_04集合元素的数组,是因为在 costs 散列表中已经体现。

python 代码
# 表示顶点与顶点以及之间的权重关系
graph = {}
graph["start"] = {}
graph["start"]["a"] = 6
graph["start"]["b"] = 2

graph["a"] = {}
graph["a"]["fin"] = 1

graph["b"] = {}
graph["b"]["fin"] = 5
graph["b"]["a"] = 10
graph["fin"]={}

# 创建源点到 $T$ 集合中各点的距离表
costs = {}
costs["a"] = 6
costs["b"] = 2
costs["fin"] = float("inf")

# 存储父节点的散列表
parents = {}
parents["a"] = "start"
parents["b"] = "start"
parents["fin"] = None

# 存放已求出最短路径的结点
processed = [] 

def find_lowest_cost_node(costs):
    '''
    在未处理的结点中,寻找距离最短的一个结点
    :param costs:
    :return: 距离最短的一个结点
    '''
    lowest_cost = float("inf")
    lowest_cost_node = None
    for node in costs:      # 遍历所有结点
        cost = costs[node]
        if cost < lowest_cost and  (node not in processed):
            lowest_cost =  cost # 将其视为距离最低的结点
            lowest_cost_node = node
    return lowest_cost_node

node = find_lowest_cost_node(costs)

while node is not None:
    print("costs",costs)
    cost = costs[node]
    neighbors = graph[node] # 某结点的相邻结点
    print(neighbors.keys())
    for n in neighbors.keys():# 遍历当前节点的所有邻居
        new_cost = cost + neighbors[n]
        if costs[n] > new_cost: # 更新T集合中结点的距离
            costs[n] = new_cost
            parents[n] = node
    processed.append(node)
    node = find_lowest_cost_node(costs)
    print("node:", node)
    #print(graph[node])
print(costs)
java 代码
package Dijkstra;

public class Node {
    private char id;
    private boolean isInTree;
    
    public Node(char id) {
        this.setId(id);
        setInTree(false);
    }

    public char getId() {
        return id;
    }

    public void setId(char id) {
        this.id = id;
    }

    public boolean isInTree() {
        return isInTree;
    }

    public void setInTree(boolean isInTree) {
        this.isInTree = isInTree;
    }
}
package Dijkstra;

public class DistPare {
    private int parentNode;
    private int distance;
    public DistPare(int parentNode, int distance) {
        this.setParentNode(parentNode);
        this.distance = distance;
    }

    public int getParentNode() {
        return parentNode;
    }

    public void setParentNode(int parentNode) {
        this.parentNode = parentNode;
    }
    
    public int getDistance() {
        return distance;
    }
    
    public void setDistance(int distance) {
        this.distance = distance;
    }

}
package Dijkstra;

public class Graph {
    private final int MAXNUM = 20;
    private final int MAXINT = 999999;
    private int nodeNum;       //number of the nodes in Graph
    private int treeNum;       //number of the nodes in tree
    private int adjMatrix[][]; //distance between two nodes
    private Node nodeList[];   //all nodes 
    private DistPare sPath[];  //shortest path between parent node and i  
    private int currentNode;
    private int currentDist;

    public Graph(){
        adjMatrix = new int[MAXNUM][MAXNUM];
        nodeList = new Node[MAXNUM];
        sPath = new DistPare[MAXNUM];
        nodeNum = 0;
        treeNum = 0;
        for(int i=0; i<MAXNUM; i++)
             for(int j=0; j<MAXNUM; j++)
                  adjMatrix[i][j] = MAXINT;
    }
    
    public void addVertex(char id) {
        nodeList[nodeNum++] = new Node(id);
    }
    
    //directed graph
    public void addEdge(int start, int end, int weight) {
        adjMatrix[start][end] = weight;
    }
    
    public void dijkstra() {
        int sourceNode = 0;
        nodeList[sourceNode].setInTree(true);
        treeNum++;
        for(int i = 0; i < nodeNum; i++) {
            int weight = adjMatrix[sourceNode][i];
            sPath[i] = new DistPare(sourceNode, weight);
        }        
        while(treeNum < nodeNum) {
            int minNode = getMin();
            int minDist = sPath[minNode].getDistance();            
            if(minDist == MAXINT) {
                System.out.println("The node can't be reached!");
           }
           else {
                currentNode = minNode;
                currentDist = minDist;
           }
           nodeList[currentNode].setInTree(true);
           treeNum++;
           adjustPath();
        }
        displayPath();
    }
    
    private void adjustPath() {
        // TODO Auto-generated method stub
        int num = 1;
        while(num < nodeNum) {
             if(nodeList[num].isInTree()) {
                  num ++;
                  continue;
             }
             int currentToFringe = adjMatrix[currentNode][num];
             int startToFringe = currentDist + currentToFringe;
             int sPathDist = sPath[num].getDistance();
             if(startToFringe<sPathDist) {
                  sPath[num].setParentNode(currentNode);
                  sPath[num].setDistance(startToFringe);;
             }
             num ++;
        }
    }

    private void displayPath() {
        // TODO Auto-generated method stub
        for(int i=0; i<nodeNum; i++) {
            System.out.print(nodeList[i].getId() + "=");
            if(sPath[i].getDistance() == MAXINT)
                 System.out.print("infinity");
            else
                 System.out.print(sPath[i].getDistance());
            char parent = nodeList[sPath[i].getParentNode()].getId();
            System.out.print("(" + parent + ") ");
       }
       System.out.println(" ");
    }

    /**
     * get the minimum DistPare
     * @return
     */
    private int getMin() {
        int minDist = MAXINT;
        int indexMin = 0;
        for(int j=0; j<nodeNum; j++) {
             if(!nodeList[j].isInTree() && sPath[j].getDistance()<minDist) {
                  minDist = sPath[j].getDistance();
                  indexMin = j;
             }
        }
        return indexMin;
    }
    
}
package Dijkstra;

public class DijkstraTest {
    public static void main(String[] args) {
        Graph theGraph = new Graph();
        theGraph.addVertex('A');//0
        theGraph.addVertex('B');//1
        theGraph.addVertex('C');//2
        theGraph.addVertex('D');//3
        theGraph.addVertex('E');//4

        theGraph.addEdge(0, 1, 50);//AB 50
        theGraph.addEdge(0, 3, 80);//AD 80
        theGraph.addEdge(1, 2, 60);//BC 60
        theGraph.addEdge(1, 3, 90);//BD 90
        theGraph.addEdge(2, 4, 40);//CE 40
        theGraph.addEdge(3, 2, 20);//DC 20
        theGraph.addEdge(3, 4, 70);//DE 70
        theGraph.addEdge(4, 1, 50);//EB 50
       
        System.out.println("Dijkstra: ");
        theGraph.dijkstra();
    }
}