PHP实现Dijkstra算法

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)。

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值