【路径规划】Dijkstra算法——超详细原理图解

Dijkstra算法详解
  • 1. Dijkstra算法原理
    •  1.1. 有向图的Dijkstra算法
    •  1.2. 无向图和栅格网络的拓展
      •   1.2.1. 无向图
      •   1.2.2. 栅格网络
  • 2. Dijkstra程序实现
    •  Java

1. Dijkstra算法原理

 1.1. 有向图的Dijkstra算法

  网上关于Dijkstra算法的文章纷繁复杂,有的在算法流程上有一些问题或谬误,有的并没有明确解释算法的详细流程,有的只介绍了简单的流程步骤,没有后续迭代的步骤。所以我决定将该算法的彻底完整流程以图表配文的形式详细描述一遍,希望能给有需要的人带来帮助。
  Dijkstra算法在最短路径问题上有着十分稳定、准确的最短路径搜索结果,是十分经典的路径规划算法,其基础理论也作为许多最短路径算法的基础,那么我们就来图解一下Dijkstra算法的具体流程:
  以下图5节点带权边的有向图为例:
Dijkstra1

openABCDE
closed

  A为路径起点,E为路径终点,每个节点含2个信息,第1个是在从起点到该节点的当前最短路径上,该节点的父节点,初始化为自身;第2个是从起点到该节点的当前最短路径长度。
  同时算法要维护两个集合,open开集集合和closed闭集集合,open开集集合存储还未确定到达起点的最短路径的节点,初始化包含所有点集,closed闭集集合存储已经确定了到达起点的最短路径的节点,初始化为空集合。
  第一步:
Dijsktra2

openBCDE
closedA

  1)将起点A从open集合中去除并加入closed集合。
  2)计算并更新在open集合中起点A可达的节点B、C、D到起点A的距离,并更新这些节点的父节点为A,并将剩余节点E的父节点置为空或自身皆可,将其距离值置为正无穷大或一个足够大的数。
  第二步:
Dijsktra3

openBDE
closedAC

  1)选取open集合中距离起点A的最短路径长度最小的节点C,将其从open集合中去除并加入closed集合中。
  第三步:
Dijsktra4

openBDE
closedAC

  1)从上一步选出的节点C出发,计算所有其可达的并在open集合中的节点B,更新B的最短路径长度和父节点,当前B的最短路径距离为9,父节点为A,而若从C出发到达B,则最短路径长度将减小为7,符合更新条件,所以将B的最短路径长度更新为7,其父节点更新为C。
  第四步:
Dijkstra5

openBE
closedACD

  1)选取open集合中距离起点A的最短路径长度最小的节点D,将其从open集合中去除并加入closed集合中。
  第五步:
Dijkstra6

openBE
closedACD

  1)从上一步选出的节点D出发,计算所有其可达的并在open集合中的节点E,由于当前E还未更新最短路径长度和父节点,所以直接更新E的最短路径长度和父节点为10和D。
  第六步:
Dijkstra7

openE
closedABCD

  1)选取open集合中距离起点A的最短路径长度最小的节点B,将其从open集合中去除并加入closed集合中。
  第七步:
Dijkstra8

openE
closedABCD

  1)从上一步选出的节点B出发,计算所有其可达的并在open集合中的节点E,当前E节点的最短路径长度为10,其父节点为D,而A经过B到达E的话,其路径长度为12,大于E原有的最短路径长度,不符合更新条件,所以对E的更新失败,E仍然保留原有的最短路径长度和父节点。
  第八步:
Dijkstra9

open
closedABCDE

  1)选取open集合中距离起点A的最短路径长度最小的节点E,将其从open集合中去除并加入closed集合中。
  第九步:
Dijkstra10

open
closedABCDE

  1)由于目标点E已经被加入closed闭集合中,所以算法迭代中止,已经找到了从A到E的最短路径。
  2)从路径终点E开始,根据父节点逆推路径可以得到,从A到E的最短路径是A-D-E,其最短路径长度为10。
  至此,带权边的有向图中单源最短路径已经找到,Dijkstra算法的整个运行流程也便是如此。相信这样的图解已经十分详细表现了Dijkstra算法的运行流程啦。

 1.2. 无向图和栅格网络的拓展

  1.2.1. 无向图

  无向图的Dijkstra算法只需要在上述有向图的算法中进行拓展,算法差别只是在每一次从选定节点向外搜索可达节点并更新的环节上,无向图中可以选择所有open集合中与选定节点相连的节点,可拿上述过程的第二步和第三步做直观比较:
  “第二步:”
Dij1
  “第三步:”
Dij2
  面向无向图的拓展改进是好理解并好实现的,而在机器路径规划时常用到的栅格网络,则是无向图的一种特例,是一种规则化等权边的无向图。

  1.2.2. 栅格网络

  栅格网络常用在对机械运动的构型空间的建模中,用于进行路径规划,而栅格网络可以理解成规则排布的一个个节点,每个连边的权重为1:
在这里插入图片描述
  利用这样的等效,将自由空间节点和障碍物节点根据空间情况进行划分和构建,就可以使用Dijkstra算法在栅格化的构型空间中寻找出机械运作或移动的最短路径。

2. Dijkstra程序实现

  程序范例以上文提到的例子进行测试:
demo1
根据该图,可以创建输入数据文本:
demo2

 Java

  代码:

//Dijkstra算法
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;

public class Main {
    public static void main(String[] args) {
        String fileName = "demo.txt";
        Character startPoint = 'A';
        Character endPoint = 'E';
        File file = new File(fileName);
        BufferedReader reader = null;
        String tempString = null;
        HashMap<Character, ArrayList<TwoTuple<Character, Integer>>> connects = new HashMap<>();
        try {
            reader = new BufferedReader(new FileReader(file));
            while (null != (tempString = reader.readLine())) {
                String[] tempStrSplit = tempString.split(" ");
                TwoTuple<Character, Integer> tempTwoTuple = new TwoTuple<>(tempStrSplit[1].charAt(0), Integer.parseInt(tempStrSplit[2]));
                if (connects.containsKey(tempStrSplit[0].charAt(0))) { //连接集合已经含有该点出边
                    ArrayList<TwoTuple<Character, Integer>> tempArr = connects.get(tempStrSplit[0].charAt(0));
                    tempArr.add(tempTwoTuple);
                    connects.replace(tempStrSplit[0].charAt(0), tempArr);
                } else { //连接集合未含该点出边
                    ArrayList<TwoTuple<Character, Integer>> tempArr = new ArrayList<>();
                    tempArr.add(tempTwoTuple);
                    connects.put(tempStrSplit[0].charAt(0), tempArr);
                }
            }
            reader.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        TwoTuple<Character[], Integer> res = DijkstraMethod(connects, startPoint, endPoint); //获得最短路径结果
        System.out.println("Path :"); //打印最短路径结果
        for (int i = 0; i < res.fst.length - 1; i++) {
            System.out.print(res.fst[i] + " -> ");
        }
        System.out.println(res.fst[res.fst.length - 1]);
        System.out.println("Length :");
        System.out.println(res.snd);
    }
    public static TwoTuple<Character[], Integer> DijkstraMethod(HashMap<Character, ArrayList<TwoTuple<Character, Integer>>> source, Character startPoint, Character endPoint) {
        ArrayList<Character> open = new ArrayList<>();
        ArrayList<Character> closed = new ArrayList<>();
        HashSet<Character> points = new HashSet<>( );
        source.forEach((k, v) -> { //获得所有点集
            points.add(k);
            v.forEach(x -> {
                points.add(x.fst);
            });
        });
        Character[] allPoints = new Character[points.size()];
        Character[] fatherPoints = new Character[points.size()];
        int[] dists = new int[points.size()];
        int[] distRes = new int[points.size()];
        int k = 0;
        for (Iterator<Character> iterator = points.iterator(); iterator.hasNext(); ) { //将点集转化为字符数组形式
            Character thisChar = iterator.next();
            open.add(thisChar); //添加开集
            allPoints[k] = thisChar; //初始化节点数组
            fatherPoints[k] = thisChar; //初始化父节点数组
            dists[k] = Integer.MAX_VALUE; //初始化距离数组
            k++;
        }
        k = findIndex(allPoints, startPoint);
        dists[k] = 0;
        int tempIndex = 0;
        while (open.contains(endPoint)) { //开集有终点则一直继续寻找轨迹
            k = findMinNumIndex(dists); //找到当前距离最小节点
            open.remove(allPoints[k]); //从开集中移除
            closed.add(allPoints[k]); //加入闭集
            distRes[k] = dists[k]; //将最终距离存储
            dists[k] = -1; //距离置为-1,不参与最小值判断
            if (allPoints[k].equals(endPoint)) { //若终点被移除,则停止迭代
                break;
            }
            ArrayList<TwoTuple<Character, Integer>> thisPointConnect = source.get(allPoints[k]);
            for (int i = 0; i < thisPointConnect.size(); i++) {
                tempIndex = findIndex(allPoints, thisPointConnect.get(i).fst);
                if (distRes[k] + thisPointConnect.get(i).snd < dists[tempIndex]) { //符合更新条件
                    fatherPoints[tempIndex] = allPoints[k];
                    dists[tempIndex] = distRes[k] + thisPointConnect.get(i).snd;
                }
            }
        }
        //输出路径和距离
        k = findIndex(allPoints, endPoint);
        ArrayList<Character> output = new ArrayList<>();
        output.add(endPoint);
        while (!allPoints[k].equals(startPoint)) {
            output.add(fatherPoints[k]);
            k = findIndex(allPoints, fatherPoints[k]);
        }
        Character[] outChars = new Character[output.size()];
        for (int i = 0; i < output.size(); i++) {
            outChars[i] = output.get(output.size() - 1 - i);
        }
        k = findIndex(allPoints, endPoint);
        TwoTuple<Character[], Integer> res = new TwoTuple<>(outChars, distRes[k]);
        return res;
    }
    public static int findIndex(Character[] chars, Character charOne) {
        for (int i = 0; i < chars.length; i++) {
            if (chars[i].equals(charOne)) {
                return i;
            }
        }
        return -1;
    }
    public static int findMinNumIndex(int[] ints) {
        int minNum = Integer.MAX_VALUE;
        int minNumIndex = 0;
        for (int i = 0; i < ints.length; i++) {
            if (ints[i] < minNum && ints[i] >= 0) {
                minNum = ints[i];
                minNumIndex = i;
            }
        }
        return minNumIndex;
    }
}
class TwoTuple<K, V> {
    public final K fst;
    public final V snd;
    TwoTuple(K k, V v){
        fst = k;
        snd = v;
    }
}

  结果:
Res

【路径规划】Dijkstra算法——超详细原理图解_dijkstra算法过程图解-优快云博客

讲解 Dijkstra 算法的基本思想,另外还有算法实现. 当然了,这个算法当路径点上万的时候效率上会降低。 我有另外的改进实现, 上万个点也是在200毫秒内完成。但是不知道怎么添加, 我只能在这里贴关键代码了 : static std::list<Node*> vecNodes; static std::list<Edge*> vecEdges; bool CDijkstras::DijkstrasFindPath(Node* psrcNode, Node* pdstNode, std::list<Node*>& vec, double& fromSrcDist) { if (psrcNode == 0 || pdstNode == 0) return false; if (psrcNode == pdstNode) { vec.push_back(pdstNode); return false; } std::list<Node*>::const_iterator it; for (it=vecNodes.begin(); it!=vecNodes.end(); it++) { (*it)->bAdded = false; (*it)->previous = 0; (*it)->distanceFromStart = MAXDOUBLE; (*it)->smallest = 0; } bool bFindDst = DijkstrasRouteInitialize(psrcNode, pdstNode); fromSrcDist = pdstNode->distanceFromStart; Node* previous = pdstNode; while (previous) { vec.push_back(previous); previous = previous->previous; } m_pDstNode = pdstNode; return bFindDst; } bool CDijkstras::DijkstrasRouteInitialize(Node* psrcNode, Node* pdstNode) { bool bFindDst = false; psrcNode->distanceFromStart = 0; Node* smallest = psrcNode; smallest->bAdded = true; std::list<Node*>::const_iterator it, ait; std::list<Node*> AdjAdjNodes ; for (it=psrcNode->connectNodes.begin(); it!=psrcNode->connectNodes.end(); it++) { if ((*it)->bAdded) continue; (*it)->smallest = psrcNode; (*it)->bAdded = true; AdjAdjNodes.push_back(*it); } while (1) { std::list<Node*> tempAdjAdjNodes; for (it=AdjAdjNodes.begin(); it!=AdjAdjNodes.end(); it++) { Node* curNode = *it; for (ait=curNode->connectNodes.begin(); ait!=curNode->connectNodes.end(); ait++) { Node* pns = *ait; double distance = Distance(pns, curNode) + pns->distanceFromStart; if (distance < curNode->distanceFromStart) { curNode->distanceFromStart = distance; curNode->previous = pns; } if (pns->bAdded == false) { tempAdjAdjNodes.push_back(pns); pns->bAdded = true; } } if (curNode == pdstNode) { bFindDst = true; } } if (bFindDst) break; if (tempAdjAdjNodes.size() == 0) break; AdjAdjNodes.clear(); AdjAdjNodes = tempAdjAdjNodes; } return bFindDst; } // Return distance between two connected nodes float CDijkstras::Distance(Node* node1, Node* node2) { std::list<Edge*>::const_iterator it; for (it=node1->connectEdges.begin(); it!=node1->connectEdges.end(); it++) { if ( (*it)->node1 == node2 || (*it)->node2 == node2 ) return (*it)->distance; } #ifdef _DEBUG __asm {int 3}; #endif return (float)ULONG_MAX; } /****************************************************************************/ /****************************************************************************/ /****************************************************************************/ //得到区域的Key// __int64 CDijkstras::GetRegionKey( float x, float z ) { long xRegion = (long)(x / m_regionWidth); long zRegion = (long)(z / m_regionHeight); __int64 key = xRegion; key <<= 32; key |= ( zRegion & 0x00000000FFFFFFFF ); return key; } //得到区域的Key// __int64 CDijkstras::GetRegionKey( long tx, long tz ) { long xRegion = tx ; long zRegion = tz ; __int64 key = xRegion; key <<= 32; key |= ( zRegion & 0x00000000FFFFFFFF ); return key; } //取得一个区域内的所有的路径点, 返回添加的路径点的个数// unsigned long CDijkstras::GetRegionWaypoint (__int64 rkey, std::vector<Node*>& vec) { unsigned long i = 0; SAME_RANGE_NODE rangeNode = mmapWaypoint.equal_range(rkey); for (CRWPIT it=rangeNode.first; it!=rangeNode.second; it++) { i++; Node* pn = it->second; vec.push_back(pn); } return i; } inline bool cmdDistanceNode (Node* pNode1, Node* pNode2) { return pNode1->cmpFromStart < pNode2->cmpFromStart; }; //添加一个路径点// Node* CDijkstras::AddNode (unsigned long id, float x, float y, float z) { Node* pNode = new Node(id, x, y, z); __int64 rkey = GetRegionKey(x, z); mmapWaypoint.insert(make_pair(rkey, pNode)); mapID2Node[id] = pNode; return pNode; } //添加一条边// Edge* CDijkstras::AddEdge (Node* node1, Node* node2, float fCost) { Edge* e = new Edge (node1, node2, fCost); return e; } //通过路径点ID得到路径点的指针// Node* CDijkstras::GetNodeByID (unsigned long nid) { std::map<unsigned long, Node*>::const_iterator it; it = mapID2Node.find(nid); if (it!=mapID2Node.end()) return it->second; return NULL; }
### Dijkstra算法的流程图与图解 Dijkstra算法是一种用于计算加权图中最短路径的经典算法。以下是该算法的核心逻辑及其对应的流程图描述。 #### 1. 初始化阶段 在开始之前,需要初始化距离数组 `dis[]` 和访问标记数组 `vis[]`。 - 将起点的距离设置为0 (`dis[start] = 0`)。 - 所有其他节点的距离设为无穷大 (`dis[i] = ∞`, 对于所有 `i ≠ start`)。 - 访问标记数组全部置为未访问状态 (`vis[i] = false`)。 此部分对应伪代码如下: ```cpp memset(dis, INF, sizeof(dis)); dis[start] = 0; memset(vis, false, sizeof(vis)); ``` #### 2. 主循环阶段 通过迭代的方式逐步扩展已知最短路径集合,直到处理完所有的节点或目标节点被访问为止。具体步骤包括: - **选取当前最小距离节点**:从未访问过的节点中找出具有最小距离值的一个节点作为新的“中心点”。这一步可以通过遍历整个 `dis[]` 数组完成[^2]。 - **更新相邻节点的距离**:对于新选中的节点的所有邻居节点,尝试更新它们的距离值。如果经过当前节点到达某个邻居节点的距离更短,则更新该邻居节点的距离值[^3]。 这部分可以用C++实现表示为: ```cpp for (int t = 1; t < n; t++) { int minn = INF, temp; for (int i = 1; i <= n; i++) { if (!vis[i] && dis[i] < minn) { minn = dis[i]; temp = i; } } vis[temp] = true; for (int i = 1; i <= n; i++) { if (map[temp][i] + dis[temp] < dis[i]) { dis[i] = map[temp][i] + dis[temp]; } } } ``` #### 3. 结束条件 当所有节点都被访问过或者找到了目标节点时,算法结束。最终得到的结果存储在 `dis[]` 中,其中每个元素代表从起点到相应节点的最短路径长度。 --- ### 流程图说明 下面是一个简化版的Dijkstra算法流程图描述: 1. 开始 -> 输入图的数据结构(邻接矩阵/列表形式),设定初始参数; 2. 判断是否存在未访问且可进一步探索的节点?如果是则继续执行下一步;否则跳至第7步; 3. 寻找尚未访问的节点集中拥有最小临时距离的那个节点X; 4. 设置节点X已被访问; 5. 遍历节点X的所有直接相连的节点Y,并检查是否能经由X使得通往Y的路径变得更优。若是如此,则调整相应的记录; 6. 返回第二步重复操作直至满足终止条件; 7. 输出结果——即各顶点相对于源点S的最佳路径成本。 ![Dijkstra Algorithm Flowchart](https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Dijkstra_Animation.gif/800px-Dijkstra_Animation.gif) 注意:由于这是理论上的抽象表达方式,在实际编程应用过程中还需要考虑边界情况以及性能优化等问题。 --- ### Python 实现示例 为了便于理解,这里提供了一个基于Python的语言版本实现: ```python import heapq def dijkstra(graph, start): distances = {node: float('inf') for node in graph} # Initialize all nodes to infinity. distances[start] = 0 # Set the distance of starting point as zero. priority_queue = [(0, start)] # Use a heap queue instead of simple list. while priority_queue: current_distance, current_node = heapq.heappop(priority_queue) if current_distance > distances[current_node]: continue for neighbor, weight in graph[current_node].items(): distance = current_distance + weight if distance < distances[neighbor]: distances[neighbor] = distance heapq.heappush(priority_queue, (distance, neighbor)) return distances ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值