分布图最短路径算法比较
用户维护好仓区的点和线,生成分布图时,用户任意选取两个点,后端求出当前最短路径。
假设图G(m, n),m个顶点,n条边
算法对比:
- floyd算法
时间复杂度o(m3)
缺点:时间复杂度过高 - dijkstra算法
时间复杂度o(m2),使用优先队列可以降到o(m * logm)
邻接矩阵存储:适合稠密图
邻接表存储:适合稀疏图
缺点:不适合负权场景
项目业务场景不存在边为负权,并且大部分为稀疏图,所以采用dijkstra算法,使用邻接表存储各个节点离初始点的最短距离,通过给点增加前置节点属性保存最短路径轨迹。
点对象:
public class Point extends BaseComponent { private static final long serialVersionUID = 4160598807426369809L; /** * 所属组件id */ private Long componentId; private String uuid; /** * 所属组件类型 */ private int componentType; private List<Line> lineList; /** * 初始节点到当前节点最短路径的上一个节点 */ private Point prePoint; /** * 初始节点到该节点的最短距离 */ private Double minDistance;
基础组件对象
public class BaseComponent implements Serializable { private static final long serialVersionUID = 1L; protected Long id; /** * 左上角X坐标 */ protected Double rendX; /** * 左上角Y坐标 */ protected Double rendY; /** * 配送中心编号 */ protected Integer distributionNo; /** * 配送中心名称 */ protected String distributionName; /** * 仓号 */ protected Integer wareNo; /** * 仓库名称 */ protected String wareName; /** * 0 删除 1 新增 2更新 */ protected Integer operateType; /** * 修改人 */ protected String updateUser; /** * 创建人 */ protected String createUser; /** * 创建时间 */ protected Date createTime; /** * 修改时间 */ protected Date updateTime; /** * 删除标识(0:删除 1:正常) */ protected Integer yn; /** * 时间戳 */ protected Date ts;
不使用优先队列:
private List<String> findMinDistance(List<Point> pointList, String startUUid, String endUUid) { Point startPoint = null; for (Point point : pointList) { if (point.getUuid().equals(startUUid)) { startPoint = point; break; } } if (startPoint != null) { //起点到各个点的最短距离 Map<String, Double> pointDistanceMap = new HashMap<>(); //起点到各个点的最短路径的前置节点 Map<String, String> prePointMap = new HashMap<>(); //各个点的遍历状态 Map<String, Boolean> pointStateMap = new HashMap<>(); pointStateMap.put(startUUid, false); //顶点关联的线 Map<String, List<Line>> pointLineMap = pointList.stream() .filter(point -> CollectionUtils.isNotEmpty(point.getLineList())) .collect(Collectors.toMap(Point::getUuid, Point::getLineList, (v1, v2) -> v2)); //初始化起点的边 for (Line line : startPoint.getLineList()) { prePointMap.put(line.getUuid(), startUUid); pointDistanceMap.put(line.getUuid(), line.getDistance()); } if (!pointDistanceMap.isEmpty()) { //离初始节点距离最近点 Map.Entry<String, Double> minDistancePoint = pointDistanceMap.entrySet().stream().min(Comparator.comparing(Map.Entry::getValue)).get(); //离初始节点距离最近点的uuid String minDistancePointUUid = minDistancePoint.getKey(); //minDistancePointUUid.equals(endUUid)说明走了到终点 pointDistanceMap.isEmpty()说明走不到终点 while (!minDistancePointUUid.equals(endUUid) && !pointDistanceMap.isEmpty()) { for (Line line : pointLineMap.get(minDistancePointUUid)) { String currentLineEndPointUUid = line.getUuid(); //当前线的终点没有走过 if (pointStateMap.get(currentLineEndPointUUid) == null || pointStateMap.get(currentLineEndPointUUid)) { //当前线的终点到起点的已有路径距离 Double currentEndPointDistance = pointDistanceMap.get(currentLineEndPointUUid); //如果从当前线走,初始点到该线终点的距离 = 该线起点到初始点的最短距离 + 该线距离 Double distance = minDistancePoint.getValue() + line.getDistance(); if (currentEndPointDistance == null) { prePointMap.put(currentLineEndPointUUid, minDistancePointUUid); pointDistanceMap.put(currentLineEndPointUUid, distance); } else { //当前线的终点到起点的已有路径距离 > 从当前线走,初始点到该线终点的距离 if (currentEndPointDistance > distance) { //更改终点前置节点 prePointMap.put(currentLineEndPointUUid, minDistancePointUUid); //更改终点最短距离 pointDistanceMap.put(currentLineEndPointUUid, distance); } } } } //去掉当前最短距离的节点 pointDistanceMap.remove(minDistancePointUUid); //设置状态为不可走 pointStateMap.put(minDistancePointUUid, false); if (!pointDistanceMap.isEmpty()) { //找到离初始节点距离最近点 minDistancePoint = pointDistanceMap.entrySet().stream().min(Comparator.comparing(Map.Entry::getValue)).get(); //离初始节点距离最近点的uuid minDistancePointUUid = minDistancePoint.getKey(); } } if (minDistancePointUUid.equals(endUUid)) { List<String> list = new ArrayList<>(); while (prePointMap.containsKey(endUUid)) { list.add(endUUid); endUUid = prePointMap.get(endUUid); } list.add(endUUid); Collections.reverse(list); return list; } } } return null; } public static void main(String[] args) { List<Point> pointList = new ArrayList<>(); Point pointA = new Point("A"); Point pointB = new Point("B"); Point pointC = new Point("C"); Point pointD = new Point("D"); Point pointE = new Point("E"); Point pointF = new Point("F"); Point pointG = new Point("G"); List<Line> lineListA = new ArrayList<>(); lineListA.add(new Line("B", 5D)); lineListA.add(new Line("C", 2D)); pointA.setLineList(lineListA); List<Line> lineListB = new ArrayList<>(); lineListB.add(new Line("A", 5D)); lineListB.add(new Line("D", 1D)); lineListB.add(new Line("E", 6D)); pointB.setLineList(lineListB); List<Line> lineListC = new ArrayList<>(); lineListC.add(new Line("A", 2D)); lineListC.add(new Line("D", 6D)); lineListC.add(new Line("F", 8D)); pointC.setLineList(lineListC); List<Line> lineListD = new ArrayList<>(); lineListD.add(new Line("B", 6D)); lineListD.add(new Line("C", 1D)); lineListD.add(new Line("E", 1D)); lineListD.add(new Line("F", 2D)); pointD.setLineList(lineListD); List<Line> lineListE = new ArrayList<>(); lineListE.add(new Line("B", 6D)); lineListE.add(new Line("D", 1D)); lineListE.add(new Line("G", 7D)); pointE.setLineList(lineListE); List<Line> lineListF = new ArrayList<>(); lineListF.add(new Line("C", 8D)); lineListF.add(new Line("D", 2D)); lineListF.add(new Line("G", 3D)); pointF.setLineList(lineListF); List<Line> lineListG = new ArrayList<>(); lineListG.add(new Line("F", 3D)); lineListG.add(new Line("E", 7D)); pointG.setLineList(lineListG); pointList.add(pointA); pointList.add(pointB); pointList.add(pointC); pointList.add(pointD); pointList.add(pointE); pointList.add(pointF); pointList.add(pointG); List list = new MapServiceImpl().findMinDistance(pointList, "A", "G"); if (!list.isEmpty()) { list.forEach( str -> { System.out.println(str + " "); } ); } }
使用优先队列:
public List<String> findMinDistanceNew(List<Point> pointList, String startUUid, String endUUid) { Point startPoint = null; for (Point point : pointList) { if (point.getUuid().equals(startUUid)) { startPoint = point; break; } } if (startPoint != null) { //起点到各个点的最短距离 Map<String, Double> pointDistanceMap = new HashMap<>(pointList.size()); //起点到各个点的最短路径的前置节点 Map<String, String> prePointMap = new HashMap<>(pointList.size()); //各个点的遍历状态 Map<String, Boolean> pointStateMap = new HashMap<>(pointList.size()); pointStateMap.put(startUUid, false); //顶点关联的线 Map<String, List<Line>> pointLineMap = new HashMap<>(pointList.size()); pointList.forEach( point -> { if (CollectionUtils.isNotEmpty(point.getLineList())) { pointLineMap.put(point.getUuid(), point.getLineList()); } } ); Queue<Point> pointDistanceQueue = new PriorityQueue(pointLineMap.size(), new Comparator<Point>() { @Override public int compare(Point o1, Point o2) { return Double.compare(o1.getMinDistance(), o2.getMinDistance()); } }); //初始化起点的边 for (Line line : startPoint.getLineList()) { prePointMap.put(line.getUuid(), startUUid); pointDistanceMap.put(line.getUuid(), line.getDistance()); pointDistanceQueue.offer(new Point(line.getUuid(), line.getDistance())); } if (!pointDistanceQueue.isEmpty()) { //离初始节点距离最近点+去掉当前最短距离的节点 Point minPoint = pointDistanceQueue.poll(); //离初始节点距离最近点的uuid String minDistancePointUUid = minPoint.getUuid(); //minDistancePointUUid.equals(endUUid)说明走到了终点 pointDistanceQueue.isEmpty()说明走不到终点 while (!minDistancePointUUid.equals(endUUid) && !pointDistanceQueue.isEmpty()) { for (Line line : pointLineMap.get(minDistancePointUUid)) { String currentLineEndPointUUid = line.getUuid(); //当前线的终点没有走过 if (pointStateMap.get(currentLineEndPointUUid) == null || pointStateMap.get(currentLineEndPointUUid)) { //当前线的终点到起点的已有路径距离 Double currentEndPointDistance = pointDistanceMap.get(currentLineEndPointUUid); //如果从当前线走,初始点到该线终点的距离 = 该线起点到初始点的最短距离 + 该线距离 Double distance = minPoint.getMinDistance() + line.getDistance(); if (currentEndPointDistance == null) { prePointMap.put(currentLineEndPointUUid, minDistancePointUUid); pointDistanceQueue.offer(new Point(line.getUuid(), distance)); pointDistanceMap.put(currentLineEndPointUUid, distance); } else { //当前线的终点到起点的已有路径距离 > 从当前线走,初始点到该线终点的距离 if (currentEndPointDistance > distance) { //更改终点前置节点 prePointMap.put(currentLineEndPointUUid, minDistancePointUUid); //更改终点最短距离 pointDistanceMap.put(currentLineEndPointUUid, distance); pointDistanceQueue.offer(new Point(line.getUuid(), distance)); } } } } //设置状态为不可走 pointStateMap.put(minDistancePointUUid, false); if (!pointDistanceQueue.isEmpty()) { //找到离初始节点距离最近点 minPoint = pointDistanceQueue.poll(); //离初始节点距离最近点的uuid minDistancePointUUid = minPoint.getUuid(); } } if (minDistancePointUUid.equals(endUUid)) { List<String> list = new ArrayList<>(); while (prePointMap.containsKey(endUUid)) { list.add(endUUid); endUUid = prePointMap.get(endUUid); } list.add(endUUid); Collections.reverse(list); return list; } } } return null; }
使用优先队列对比测试(空间换时间)
图G(7,10)
代码预热后测试(类加载是按需加载,第一次跑会进行类加载过程,会消耗较多时间,所以先空跑两次预热后再进行时间的统计):
调用次数 | 10 | 1000 | 2000 |
优化前消耗时间 | 1ms | 28ms | 35ms |
优化后消耗时间 | <1ms | 12ms | 15ms |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构