SpringBoot 与 JGraphT 整合,实现物流路径优化系统

通过使用图论算法,我们可以将运输网络建模为一个图,其中节点代表城市或仓库,边代表连接这些地点的道路或
首页 新闻资讯 行业资讯 SpringBoot 与 JGraphT 整合,实现物流路径优化系统

在物流行业中,合理的路线规划可以减少运输成本、提高效率,并且降低对环境的影响。通过使用图论算法,我们可以将运输网络建模为一个图,其中节点代表城市或仓库,边代表连接这些地点的道路或航线。然后,我们可以通过各种图论算法来寻找最优路径。

558d6c724ef6cc3457c4994a36d1c251b82260.jpg

一、JGraphT

1. 开源且成熟

  • 开源: JGraphT是一个完全开源的Java库,遵循Apache License 2.0协议。这意味着你可以自由地使用、修改和分发该库。

  • 成熟: JGraphT已经存在多年,拥有大量的用户基础和社区支持。这确保了其稳定性和可靠性。

2. 功能丰富

  • 多种图类型: 支持有向图、无向图、加权图等多种图类型。

  • 丰富的算法集: 提供了许多常用的图论算法,包括最短路径(Dijkstra、A*)、最小生成树(Kruskal、Prim)、最大流等。

  • 灵活的数据结构: 支持不同的顶点和边数据结构,可以根据具体需求进行定制。

3. 易于集成

  • 纯Java实现: 完全用Java编写,易于与其他Java项目集成。

  • 良好的文档: 提供详细的API文档和示例代码,便于开发者快速上手。

  • 活跃社区: 拥有一个活跃的社区,可以获取及时的支持和帮助。

4. 高性能

  • 高效的算法实现: JGraphT中的算法经过优化,能够在大规模图数据上高效运行。

  • 内存管理: 有效的内存管理和数据结构设计,减少了不必要的开销。

二、我们选择JGraphT的理由

1. 快速开发

  • 简化复杂性: 使用JGraphT可以轻松实现复杂的图论算法,无需从头开始编写底层代码。

  • 节省时间: 减少了开发周期,加快了产品上市速度。

2. 可靠性和可维护性

  • 稳定的库: JGraphT经过长期测试和优化,确保了系统的可靠性和稳定性。

  • 清晰的架构: 代码结构清晰,易于维护和扩展。

3. 成本效益

  • 免费使用: 开源特性意味着无需支付额外费用。

  • 高质量支持: 社区支持强大,出现问题时可以获得及时的帮助。

三、代码实操

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.jgrapht</groupId><artifactId>jgrapht-core</artifactId><version>1.5.2</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>

四、城市类

packagecom.example.logistics.model;importlombok.Data;importjavax.validation.constraints.NotBlank;importjavax.validation.constraints.NotNull;/**
 * 城市模型类,表示图中的节点。
 */@DatapublicclassCity{@NotBlank(message="城市ID不能为空")privateString id;@NotBlank(message="城市名称不能为空")privateString name;}

五、道路类

packagecom.example.logistics.model;importlombok.Data;importjavax.validation.constraints.Min;importjavax.validation.constraints.NotNull;/**
 * 道路模型类,表示图中的边。
 */@DatapublicclassRoad{@NotNull(message="源城市ID不能为空")privateString sourceId;@NotNull(message="目标城市ID不能为空")privateString targetId;@Min(value=0,message="权重必须非负")privatedouble weight;// 表示距离或其他成本}

六、运输数据

packagecom.example.logistics.model;importlombok.Data;importjavax.validation.Valid;importjavax.validation.constraints.NotEmpty;importjava.util.List;/**
 * 运输数据模型类,包含城市和道路的信息。
 */@DatapublicclassTransportData{@NotEmpty(message="城市列表不能为空")@ValidprivateList<City>cities;@NotEmpty(message="道路列表不能为空")@ValidprivateList<Road>roads;}

七、处理无效请求的异常

packagecom.example.logistics.exception;importorg.springframework.http.HttpStatus;importorg.springframework.web.bind.annotation.ResponseStatus;/**
 * 自定义异常类,用于处理无效请求的情况。
 */@ResponseStatus(HttpStatus.BAD_REQUEST)publicclassBadRequestExceptionextendsRuntimeException{publicBadRequestException(String message){super(message);}}

八、处理资源未找到的异常

packagecom.example.logistics.exception;importorg.springframework.http.HttpStatus;importorg.springframework.web.bind.annotation.ResponseStatus;/**
 * 自定义异常类,用于处理资源未找到的情况。
 */@ResponseStatus(HttpStatus.NOT_FOUND)publicclassResourceNotFoundExceptionextendsRuntimeException{publicResourceNotFoundException(String message){super(message);}}

九、负责构建和管理图模型

packagecom.example.logistics.service;importcom.example.logistics.exception.ResourceNotFoundException;importcom.example.logistics.model.City;importcom.example.logistics.model.Road;importorg.jgrapht.Graph;importorg.jgrapht.graph.DefaultWeightedEdge;importorg.jgrapht.graph.SimpleWeightedGraph;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.stereotype.Service;importjava.util.HashMap;importjava.util.List;importjava.util.Map;/**
 * 交通网络服务类,负责构建和管理图模型。
 */@ServicepublicclassTransportationNetworkService{privatefinal Logger logger=LoggerFactory.getLogger(TransportationNetworkService.class);privateMap<String,City>cityMap=newHashMap<>();privateGraph<City,DefaultWeightedEdge>graph;/**
     * 初始化图对象。
     */publicTransportationNetworkService(){this.graph=newSimpleWeightedGraph<>(DefaultWeightedEdge.class);}/**
     * 根据输入的城市和道路信息构建图模型。
     *
     * @param cities 城市列表
     * @param roads  道路列表
     */publicvoidbuildNetwork(List<City>cities,List<Road>roads){logger.info("正在构建包含 {} 个城市和 {} 条道路的交通网络",cities.size(),roads.size());for(City city:cities){if(!cityMap.containsKey(city.getId())){graph.addVertex(city);cityMap.put(city.getId(),city);}else{thrownewBadRequestException("重复的城市ID: "+city.getId());}}for(Road road:roads){City source=cityMap.get(road.getSourceId());City target=cityMap.get(road.getTargetId());if(source==null||target==null){thrownewBadRequestException("无效的道路连接城市: "+road.getSourceId()+" 和 "+road.getTargetId());}DefaultWeightedEdge edge=graph.addEdge(source,target);graph.setEdgeWeight(edge,road.getWeight());}}/**
     * 获取当前构建的图模型。
     *
     * @return 图模型
     */publicGraph<City,DefaultWeightedEdge>getGraph(){returngraph;}/**
     * 根据城市ID查找城市对象。
     *
     * @param id 城市ID
     * @return 找到的城市对象
     * @throws ResourceNotFoundException 如果找不到对应的城市
     */publicCityfindCityById(String id){City city=cityMap.get(id);if(city==null){thrownewResourceNotFoundException("未找到ID为 "+id+" 的城市");}returncity;}}

十、使用图论算法计算最优路径

packagecom.example.logistics.service;importorg.jgrapht.alg.shortestpath.DijkstraShortestPath;importorg.jgrapht.alg.spanning.KruskalMinimumSpanningTree;importorg.jgrapht.graph.DefaultWeightedEdge;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;importjava.util.Set;/**
 * 路径优化服务类,使用图论算法计算最优路径。
 */@ServicepublicclassPathOptimizer{privatefinal Logger logger=LoggerFactory.getLogger(PathOptimizer.class);@AutowiredprivateTransportationNetworkService networkService;/**
     * 计算最小生成树(MST)。
     *
     * @return 最小生成树的边集合
     */publicSet<DefaultWeightedEdge>computeMST(){logger.info("正在计算最小生成树(MST)");KruskalMinimumSpanningTree<City,DefaultWeightedEdge>mstAlg=newKruskalMinimumSpanningTree<>(networkService.getGraph());returnmstAlg.getSpanningTree();}/**
     * 使用Dijkstra算法计算最短路径。
     *
     * @param source 源城市
     * @param target 目标城市
     * @return 最短路径对象
     */publicDijkstraShortestPath<City,DefaultWeightedEdge>computeShortestPath(City source,City target){logger.info("正在计算从 {} 到 {} 的最短路径",source.getName(),target.getName());DijkstraShortestPath<City,DefaultWeightedEdge>dijkstraAlg=newDijkstraShortestPath<>(networkService.getGraph());returndijkstraAlg.getPath(source,target);}}


我们使用了JGraphT库中的KruskalMinimumSpanningTree类来计算最小生成树。


1. 最小生成树(Minimum Spanning Tree - MST)

最小生成树是指在一个加权无向图中,选取一些边组成一棵树,使得该树的所有顶点都属于原图,并且所有边都在原图中,同时这些边的总权重最小。

Kruskal算法:

(1) 步骤:

  • 将图中的所有边按权重从小到大排序。

  • 依次取出每条边,如果这条边连接的两个顶点不在同一个连通分量中,则将这条边加入最小生成树中,并合并这两个连通分量。

  • 重复上述过程直到形成一个包含所有顶点的树。

(2) 优点: 算法简单易懂,适合稀疏图。

(3) 缺点: 对于稠密图效率较低。


我们使用了JGraphT库中的DijkstraShortestPath类来计算最短路径。


2. 最短路径(Shortest Path)

最短路径是指在图中寻找两点之间的路径,使得路径上所有边的权重之和最小。

Dijkstra算法:

(1) 步骤:

  • 初始化:设置起点的距离为0,其他点的距离为无穷大;初始化优先队列,将起点放入队列。

  • 取出队列中距离最小的顶点u。

  • 更新与u相邻的所有顶点v的距离,如果通过u到达v的距离比之前记录的小,则更新距离并将v加入队列。

  • 重复上述过程直到队列为空。

(2) 优点: 能够有效地解决单源最短路径问题。

(3) 缺点: 不适用于有负权边的图。

十一、Controller

packagecom.example.logistics.controller;importcom.example.logistics.exception.BadRequestException;importcom.example.logistics.model.City;importcom.example.logistics.model.TransportData;importcom.example.logistics.service.PathOptimizer;importcom.example.logistics.service.TransportationNetworkService;importorg.jgrapht.alg.shortestpath.DijkstraShortestPath;importorg.jgrapht.graph.DefaultWeightedEdge;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.http.ResponseEntity;importorg.springframework.validation.BindingResult;importorg.springframework.web.bind.annotation.*;importjavax.validation.Valid;importjava.util.Set;/**
 * 传输控制器类,提供RESTful API接口。
 */@RestController
@RequestMapping("/api/transport")publicclassTransportController{privatefinal Logger logger=LoggerFactory.getLogger(TransportController.class);@AutowiredprivateTransportationNetworkService networkService;@AutowiredprivatePathOptimizer pathOptimizer;/**
     * 构建交通网络。
     *
     * @param data 包含城市和道路信息的传输数据
     * @param bindingResult 绑定结果,用于验证输入数据
     * @return 成功响应
     * @throws BadRequestException 如果输入数据无效
     */@PostMapping("/build-network")publicResponseEntity<Void>buildNetwork(@Valid @RequestBody TransportData data,BindingResult bindingResult){if(bindingResult.hasErrors()){logger.error("验证错误: {}",bindingResult.getAllErrors());thrownewBadRequestException(bindingResult.getAllErrors().toString());}try{networkService.buildNetwork(data.getCities(),data.getRoads());logger.info("交通网络构建成功");returnResponseEntity.ok().build();}catch(Exception e){logger.error("构建交通网络时出错: ",e);thrownewBadRequestException(e.getMessage());}}/**
     * 计算最小生成树(MST)。
     *
     * @return 最小生成树的边集合
     */@GetMapping("/compute-mst")publicResponseEntity<Set<DefaultWeightedEdge>>computeMST(){Set<DefaultWeightedEdge>mstEdges=pathOptimizer.computeMST();logger.info("计算得到的MST边集: {}",mstEdges);returnResponseEntity.ok(mstEdges);}/**
     * 计算最短路径。
     *
     * @param sourceId 源城市ID
     * @param targetId 目标城市ID
     * @return 最短路径对象
     * @throws ResourceNotFoundException 如果找不到对应的源或目标城市
     */@GetMapping("/shortest-path/{sourceId}/{targetId}")publicResponseEntity<DijkstraShortestPath<City,DefaultWeightedEdge>>computeShortestPath(@PathVariable String sourceId,@PathVariable String targetId){City source=networkService.findCityById(sourceId);City target=networkService.findCityById(targetId);DijkstraShortestPath<City,DefaultWeightedEdge>path=pathOptimizer.computeShortestPath(source,target);logger.info("计算得到的最短路径: {}",path);returnResponseEntity.ok(path);}}

十二、LogisticsApplication.java

packagecom.example.logistics;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublicclassLogisticsApplication{publicstaticvoidmain(String[]args){SpringApplication.run(LogisticsApplication.class,args);}}

十三、配置文件 (application.properties)

server.port=8080logging.level.org.springframework.web=INFOlogging.level.com.example.logistics=DEBUG

十四、测试结果

1. 构建交通网络

curl-XPOSThttp://localhost:8080/api/transport/build-network \-H"Content-Type: application/json"\-d '{"cities":[{"id":"C1","name":"City1"},{"id":"C2","name":"City2"},{"id":"C3","name":"City3"}],"roads":[{"sourceId":"C1","targetId":"C2","weight":10},{"sourceId":"C2","targetId":"C3","weight":15},{"sourceId":"C1","targetId":"C3","weight":20}]}'

Respons:

{}

2. 计算最小生成树(MST)

curl-XGEThttp://localhost:8080/api/transport/compute-mst

Respons:

[{"source":{"id":"C1","name":"City1"},"target":{"id":"C2","name":"City2"},"weight":10},{"source":{"id":"C2","name":"City2"},"target":{"id":"C3","name":"City3"},"weight":15}]

3. 计算最短路径

curl-XGEThttp://localhost:8080/api/transport/shortest-path/C1/C3

Respons:

{"startVertex":{"id":"C1","name":"City1"},"endVertex":{"id":"C3","name":"City3"},"length":25,"edgeList":[{"source":{"id":"C1","name":"City1"},"target":{"id":"C2","name":"City2"},"weight":10},{"source":{"id":"C2","name":"City2"},"target":{"id":"C3","name":"City3"},"weight":15}]}