用PHP实现Dijkstra算法,计算从起始点到其他任一节点的最短路径长度

一个图中有很多点,如何求出两点之间的最短距离呢?
这实际上涉及到一个算法问题,如下图所示:
无向图求最短路径
一张图上有从A到G七个节点,节点间的路径长度不同,如果想求出任一个节点到D节点的最短距离,该怎么办呢?
参照 Dijkstra算法原理,我用PHP实现了该算法,感谢“平凡的L同学”,阅读他的文章让我明白了Dijkstra算法的原理。

<?php
/**
 * 这是一个节点类
 */
class Node {
    //标记该节点是否已得到最短路径
    var $isMarked = false;

    //标记该节点离起始节点的最短路径长度,等于0表示起点
    var $minPathDistance = PHP_INT_MAX;

    //标记节点名称
    var $name;

    public function __construct($name, $distance=null) {
        $this->name = $name;
        if (!is_null($distance) && $distance === 0) {
            $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 ];
$edges[1] = [12,   0,   10,   $max, $max,   7,  $max];
$edges[2] = [$max, 10,  0,      3,    5,   6,   $max];
$edges[3] = [$max, $max, 3,     0,    4,  $max, $max];
$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();

最终的打印结果是:

S:[“C”,“D”]
U:[“A”,“B”,“E”,“F”,“G”]

S:[“C”,“D”,“E”]
U:[“A”,“B”,“F”,“G”]

S:[“C”,“D”,“E”,“F”]
U:[“A”,“B”,“G”]

S:[“C”,“D”,“E”,“F”,“G”]
U:[“A”,“B”]

S:[“B”,“C”,“D”,“E”,“F”,“G”]
U:[“A”]

S:[“A”,“B”,“C”,“D”,“E”,“F”,“G”]
U:[]

A(22),B(13),C(3),D(0),E(4),F(6),G(12),

参考文章:
Dijkstra算法原理: https://blog.youkuaiyun.com/yalishadaa/article/details/55827681

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值