You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

能否用path-finder库解决带地理坐标的旅行商问题?如何实现?

关于path-finder库解决TSP问题的说明

嘿,我来帮你梳理下这个问题哈~首先直接给你结论:这个path-finder库本身并没有专门内置TSP的求解功能,它的核心强项是处理图结构里的两点间最短路径(比如用A*、Dijkstra这类算法做网格或导航图的路径规划)。不过如果你的TSP问题规模不大(比如10-15个地点以内),完全可以结合这个库的基础能力,自己搭一套求解逻辑;要是地点数量多,那更推荐用专门的TSP求解工具。

下面给你具体的实现思路,针对小规模地理坐标的TSP场景:

1. 先搞定地点间的距离计算

TSP需要每对地点之间的距离数据,你的输入是经纬度坐标,用Haversine公式算球面距离会比欧氏距离更准确(毕竟地球是圆的嘛)。这一步不需要用到path-finder,自己写个函数就行:

function haversineDistance($coord1, $coord2) {
    $R = 6371; // 地球半径,单位公里
    $lat1 = deg2rad($coord1[0]);
    $lon1 = deg2rad($coord1[1]);
    $lat2 = deg2rad($coord2[0]);
    $lon2 = deg2rad($coord2[1]);
    
    $dLat = $lat2 - $lat1;
    $dLon = $lon2 - $lon1;
    
    $a = sin($dLat/2) * sin($dLat/2) + cos($lat1) * cos($lat2) * sin($dLon/2) * sin($dLon/2);
    $c = 2 * atan2(sqrt($a), sqrt(1-$a));
    
    return $R * $c;
}

2. 用path-finder构建图结构(可选)

如果你想借助path-finder来管理所有地点节点和它们之间的路径权重,可以这么做:把每个地点(包括起点)都当成图里的节点,节点之间的边权重就是刚才算出来的距离:

use PathFinder\Graph\Graph;
use PathFinder\Graph\Node;
use PathFinder\Graph\Edge;

$graph = new Graph();

// 先把所有节点加进去,包括起点
$nodes = [];
$startNode = new Node('start', $start);
$graph->addNode($startNode);
$nodes['start'] = $startNode;

// 逐个添加其他地点节点
foreach ($places as $index => $coord) {
    $nodeId = "place_$index";
    $node = new Node($nodeId, $coord);
    $graph->addNode($node);
    $nodes[$nodeId] = $node;
}

// 给所有节点之间加上双向边(毕竟TSP里从A到B和B到A的距离是一样的)
foreach ($nodes as $fromId => $fromNode) {
    foreach ($nodes as $toId => $toNode) {
        if ($fromId !== $toId) {
            $distance = haversineDistance($fromNode->getData(), $toNode->getData());
            $graph->addEdge(new Edge($fromNode, $toNode, $distance));
            $graph->addEdge(new Edge($toNode, $fromNode, $distance));
        }
    }
}

3. 实现TSP的求解逻辑

因为path-finder没自带TSP求解,我们得自己写算法。小规模问题用动态规划就很合适,能算出精确的最短路径:

function tspDynamicProgramming($graph, $startNodeId) {
    $nodes = $graph->getNodes();
    $nodeIds = array_keys($nodes);
    $n = count($nodeIds);
    
    // 给每个节点编个索引,方便后续DP表操作
    $indexMap = [];
    foreach ($nodeIds as $index => $id) {
        $indexMap[$id] = $index;
    }
    $startIndex = $indexMap[$startNodeId];
    
    // DP表初始化:dp[mask][u] 表示访问过mask标记的节点,最后停在u节点的最短距离
    $dp = array_fill(0, 1 << $n, array_fill(0, $n, INF));
    $dp[1 << $startIndex][$startIndex] = 0;
    
    // 记录路径的前驱节点,方便最后回溯出完整路径
    $prev = array_fill(0, 1 << $n, array_fill(0, $n, -1));
    
    // 遍历所有可能的节点访问状态
    for ($mask = 1; $mask < (1 << $n); $mask++) {
        foreach ($nodeIds as $uId) {
            $u = $indexMap[$uId];
            if (!($mask & (1 << $u))) continue; // 跳过当前状态没访问过的节点
            foreach ($nodeIds as $vId) {
                $v = $indexMap[$vId];
                if ($mask & (1 << $v)) continue; // 跳过已经访问过的节点
                $newMask = $mask | (1 << $v);
                $edge = $graph->getEdge($nodes[$uId], $nodes[$vId]);
                $distance = $edge->getWeight();
                // 更新最短距离和前驱节点
                if ($dp[$newMask][$v] > $dp[$mask][$u] + $distance) {
                    $dp[$newMask][$v] = $dp[$mask][$u] + $distance;
                    $prev[$newMask][$v] = $u;
                }
            }
        }
    }
    
    // 找到从最后一个节点回到起点的最短回路
    $fullMask = (1 << $n) - 1;
    $minDistance = INF;
    $lastNode = -1;
    foreach ($nodeIds as $uId) {
        $u = $indexMap[$uId];
        $edge = $graph->getEdge($nodes[$uId], $nodes[$startNodeId]);
        $distance = $edge->getWeight();
        if ($dp[$fullMask][$u] + $distance < $minDistance) {
            $minDistance = $dp[$fullMask][$u] + $distance;
            $lastNode = $u;
        }
    }
    
    // 回溯得到完整路径
    $path = [];
    $currentMask = $fullMask;
    $currentNode = $lastNode;
    while ($currentNode !== -1) {
        $path[] = $nodeIds[$currentNode];
        $prevNode = $prev[$currentMask][$currentNode];
        $currentMask &= ~(1 << $currentNode);
        $currentNode = $prevNode;
    }
    // 反转路径并补上回到起点的最后一步
    $path = array_reverse($path);
    $path[] = $startNodeId;
    
    return [
        'distance' => $minDistance,
        'path' => $path
    ];
}

// 调用这个函数就能得到结果啦
$result = tspDynamicProgramming($graph, 'start');
echo "最短路径总距离:{$result['distance']} 公里\n";
echo "路径节点顺序:" . implode(' -> ', $result['path']) . "\n";

一些要注意的点

  • 如果你的地点数量超过15个,动态规划的速度会慢到离谱(时间复杂度是O(n²*2ⁿ)),这时候建议换贪心算法、遗传算法这类近似解法,或者直接用专门的TSP求解库。
  • 其实要是你不需要用path-finder管理图结构,也可以直接用距离矩阵来实现TSP,完全不用依赖这个库哦。

内容的提问来源于stack exchange,提问作者marcus

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 08:34:10