Dijkstra(迪杰斯特拉)算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。Dijkstra算法是很有代表性的最短路径算法,在很多专业课程中都作为基本内容有详细的介绍,如数据结构,图论,运筹学等等。注意该算法要求图中不存在负权边。
看如下实例:
(1)初始
左边是一张连通带权有向图,右边是起始顶点 0 到各个顶点的当前最短距离的列表,起始顶点 0 到自身的距离是 0
(2)将顶点 0 进行标识,并作为当前顶点
对当前顶点 0 的所有相邻顶点依次进行松弛操作,同时更新列表从列表的未标识顶点中找到当前最短距离最小的顶点,即 顶点 2,就可以说,起始顶点 0 到顶点 2 的最短路径即 0 -> 2
因为:图中没有负权边,即便存在从顶点 1 到顶点 2 的边,也不可能通过松弛操作使得从起始顶点 0 到顶点 2 的距离更小。图中没有负权边保证了:对当前顶点的所有相邻顶点依次进行松弛操作后,只要能从列表的未标识顶点中找到当前最短距离最小的顶点,就能确定起始顶点到该顶点的最短路径
(3)将顶点 2 进行标识,并作为当前顶点
(4)对当前顶点 2 的相邻顶点 1 进行松弛操作,同时更新列表
(5)对当前顶点 2 的相邻顶点 4 进行松弛操作,同时更新列表
(6)对当前顶点 2 的相邻顶点 3 进行松弛操作,同时更新列表
从列表的未标识顶点中找到当前最短距离最小的顶点,即 顶点 1,就可以说,起始顶点 0 到顶点 1 的最短路径即 0 -> 2 -> 1
(7)将顶点 1 进行标识,并作为当前顶点
(8)对当前顶点 1 的相邻顶点 4 进行松弛操作,同时更新列表
从列表的未标识顶点中找到当前最短距离最小的顶点,即 顶点 4,就可以说,起始顶点 0 到顶点 4 的最短路径即 0 -> 2 -> 1 -> 4
(9)将顶点 4 进行标识,并作为当前顶点
当前顶点 4 没有相邻顶点,不必进行松弛操作。从列表的未标识顶点中找到当前最短距离最小的顶点,即 顶点 3,就可以说,起始顶点 0 到顶点 3 的最短路径即 0 -> 2 -> 3
(10)将顶点 3 进行标识,并作为当前顶点
对当前顶点 3 的相邻顶点 4 进行松弛操作,发现不能通过松弛操作使得从起始顶点 0 到顶点 4 的路径更短,所以保持原有最短路径不变至此,列表中不存在未标识顶点,Dijkstra 算法结束,找到了一棵以顶点 0 为根的最短路径树
Dijkstra 算法的过程总结:
第一步:从起始顶点开始
第二步:对当前顶点进行标识
第三步:对当前顶点的所有相邻顶点依次进行松弛操作
第四步:更新列表
第五步:从列表的未标识顶点中找到当前最短距离最小的顶点,作为新的当前顶点
第六步:重复第二步至第五步,直到列表中不存在未标识顶点
Dijkstra 算法主要做两件事情:
(1)从列表中找最值
(2)更新列表
<?php
/**
* 这是一个节点类.
*/
class Node
{
//标记该节点是否已得到最短路径
public $isMarked = false;
//标记该节点离起始节点的最短路径长度,等于0表示起点
public $minPathDistance = PHP_INT_MAX;
//标记节点名称
public $name;
public function __construct($name, $distance = null)
{
$this->name = $name;
if (null !== $distance && 0 === $distance) {
$this->minPathDistance = $distance;
$this->isMarked = true;
}
}
}
class Graph
{
//未访问到的节点
private $unVasited = [];
//节点之间的距离,是一个邻接矩阵,保存任意两个节点间的距离
private $edges;
//所有节点
private $nodes;
//起始节点的下标
private $startNodeIndex;
public function __construct(array $nodes, array $edges, int $startNodeIndex)
{
$this->nodes = $nodes;
$this->edges = $edges;
$this->startNodeIndex = $startNodeIndex;
$this->initial();
}
//标记起点,构造未访问节点数组
private function initial()
{
//在邻接矩阵里查找与起始节点相邻的节点(找路径最短的)
$arrEdge = $this->edges[$this->startNodeIndex];
$min = min(array_filter($arrEdge)); //过滤掉路径长度为0的本元素
$nodeLen = count($arrEdge);
//构造未访问到的节点
for ($i = 0; $i < $nodeLen; ++$i) {
if ($i == $this->startNodeIndex) {
continue;
}
if ($arrEdge[$i] < PHP_INT_MAX) {
if ($arrEdge[$i] == $min) {//将找到的节点标注已获取最短路径
$this->nodes[$i]->isMarked = true;
$this->nodes[$i]->minPathDistance = $min;
} else {
$this->nodes[$i]->minPathDistance = $arrEdge[$i];
$this->unVisited[$i] = $this->nodes[$i];
}
} else {
$this->unVisited[$i] = $this->nodes[$i];
}
}
$this->stepPrint();
}
/**
* 处理所有未访问的节点,成功后从未访问数组中删除.
*
* @return
*/
public function search()
{
//将未访问到的节点遍历一遍,处理完毕后将找到的节点从$unVisited中删除
while (!empty($this->unVisited)) {
//查询到与已标记节点相邻的节点,如果有多个相邻的节点,则比较各节点路径长度
$indexVisited = [];
foreach ($this->nodes as $k => $v) {
if ($this->nodes[$k]->isMarked) {
$indexVisited[] = $k;
}
}
$minLen = PHP_INT_MAX;
$minIndex = 0;
foreach ($this->unVisited as $i => $node) {
//更新与已标记节点相邻的节点的距离
$currLen = PHP_INT_MAX;
foreach ($indexVisited as $j) {
if ($this->edges[$i][$j] > 0 && $this->edges[$i][$j] < PHP_INT_MAX) {//相邻
// echo "i:{$i},j={$j}, currLen={$currLen}<br/>";
if ($currLen > $this->edges[$i][$j] + $this->nodes[$j]->minPathDistance) {
$currLen = $this->edges[$i][$j] + $this->nodes[$j]->minPathDistance;
}
}
}
$this->unVisited[$i]->minPathDistance = $currLen;
//找到路径最短的那条线
if ($minLen > $node->minPathDistance && $node->minPathDistance > 0) {
$minLen = $node->minPathDistance;
$minIndex = $i;
}
}
//修改即将删除的节点为已标记
$this->nodes[$minIndex]->isMarked = true;
//更新最短线上的节点的路径长度
$this->nodes[$minIndex]->minPathDistance = $minLen;
//从未访问节点中删除已查询到的节点
unset($this->unVisited[$minIndex]);
$this->stepPrint();
}
}
/**
* 打印每一步的结果.
*
* @return [type] [description]
*/
private function stepPrint()
{
$visited = [];
foreach ($this->nodes as $k => $v) {
if ($this->nodes[$k]->isMarked) {
$visited[] = $v->name;
}
}
$unvisit = [];
foreach ($this->unVisited as $k => $v) {
$unvisit[] = $v->name;
}
echo "S:".json_encode($visited)."<br/>";
echo "U:".json_encode($unvisit)."<br/>";
echo '<br/>';
}
/**
* 打印最终结果.
*
* @return [type] [description]
*/
public function printGraph()
{
$len = count($this->nodes);
for ($i = 0; $i < $len; ++$i) {
echo "{$this->nodes[$i]->name}({$this->nodes[$i]->minPathDistance}),";
}
echo "<br/>";
}
}
//构造节点数组
$nodes = [];
$node = new Node('A');
$nodes[] = $node;
$node = new Node('B');
$nodes[] = $node;
$node = new Node('C');
$nodes[] = $node;
$node = new Node('D', 0); //D节点是起始节点
$nodes[] = $node;
$startNodeIndex = count($nodes) - 1; //起始节点的下标
$node = new Node('E');
$nodes[] = $node;
$node = new Node('F');
$nodes[] = $node;
$node = new Node('G');
$nodes[] = $node;
//节点之间是否有弧边,弧边的长度,0-6分别对应nodes中的下标
$max = PHP_INT_MAX;
//节点A A B C D E F G
$edges[0] = [0, 12, $max, $max, $max, 16, 14]; //A
$edges[1] = [12, 0, 10, $max, $max, 7, $max]; //B
$edges[2] = [$max, 10, 0, 3, 5, 6, $max]; //C
$edges[3] = [$max, $max, 3, 0, 4, $max, $max]; //D
$edges[4] = [$max, $max, 5, 4, 0, 2, 8];
$edges[5] = [16, 7, 6, $max, 2, 0, 8];
$edges[6] = [14, $max, $max, $max, 8, 9, 0];
$graph = new Graph($nodes, $edges, $startNodeIndex);
$graph->search();
$graph->printGraph();
//此时,起点D到各个顶点的最短距离就计算出来了:A(22) B(13) C(3) D(0) E(4) F(6) G(12)。