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

开发
通过使用图论算法,我们可以将运输网络建模为一个图,其中节点代表城市或仓库,边代表连接这些地点的道路或航线。然后,我们可以通过各种图论算法来寻找最优路径。

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

一、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>

四、城市类

package com.example.logistics.model;

import lombok.Data;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

/**
 * 城市模型类,表示图中的节点。
 */
@Data
public class City {
    @NotBlank(message = "城市ID不能为空")
    private String id;

    @NotBlank(message = "城市名称不能为空")
    private String name;
}

五、道路类

package com.example.logistics.model;

import lombok.Data;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;

/**
 * 道路模型类,表示图中的边。
 */
@Data
public class Road {
    @NotNull(message = "源城市ID不能为空")
    private String sourceId;

    @NotNull(message = "目标城市ID不能为空")
    private String targetId;

    @Min(value = 0, message = "权重必须非负")
    private double weight; // 表示距离或其他成本
}

六、运输数据

package com.example.logistics.model;

import lombok.Data;

import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import java.util.List;

/**
 * 运输数据模型类,包含城市和道路的信息。
 */
@Data
public class TransportData {
    @NotEmpty(message = "城市列表不能为空")
    @Valid
    private List<City> cities;

    @NotEmpty(message = "道路列表不能为空")
    @Valid
    private List<Road> roads;
}

七、处理无效请求的异常

package com.example.logistics.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

/**
 * 自定义异常类,用于处理无效请求的情况。
 */
@ResponseStatus(HttpStatus.BAD_REQUEST)
public class BadRequestException extends RuntimeException {
    public BadRequestException(String message) {
        super(message);
    }
}

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

package com.example.logistics.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

/**
 * 自定义异常类,用于处理资源未找到的情况。
 */
@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String message) {
        super(message);
    }
}

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

package com.example.logistics.service;

import com.example.logistics.exception.ResourceNotFoundException;
import com.example.logistics.model.City;
import com.example.logistics.model.Road;
import org.jgrapht.Graph;
import org.jgrapht.graph.DefaultWeightedEdge;
import org.jgrapht.graph.SimpleWeightedGraph;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 交通网络服务类,负责构建和管理图模型。
 */
@Service
public class TransportationNetworkService {

    private final Logger logger = LoggerFactory.getLogger(TransportationNetworkService.class);

    private Map<String, City> cityMap = new HashMap<>();
    private Graph<City, DefaultWeightedEdge> graph;

    /**
     * 初始化图对象。
     */
    public TransportationNetworkService() {
        this.graph = new SimpleWeightedGraph<>(DefaultWeightedEdge.class);
    }

    /**
     * 根据输入的城市和道路信息构建图模型。
     *
     * @param cities 城市列表
     * @param roads  道路列表
     */
    public void buildNetwork(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 {
                throw new BadRequestException("重复的城市ID: " + city.getId());
            }
        }

        for (Road road : roads) {
            City source = cityMap.get(road.getSourceId());
            City target = cityMap.get(road.getTargetId());
            if (source == null || target == null) {
                throw new BadRequestException("无效的道路连接城市: " + road.getSourceId() + " 和 " + road.getTargetId());
            }
            DefaultWeightedEdge edge = graph.addEdge(source, target);
            graph.setEdgeWeight(edge, road.getWeight());
        }
    }

    /**
     * 获取当前构建的图模型。
     *
     * @return 图模型
     */
    public Graph<City, DefaultWeightedEdge> getGraph() {
        return graph;
    }

    /**
     * 根据城市ID查找城市对象。
     *
     * @param id 城市ID
     * @return 找到的城市对象
     * @throws ResourceNotFoundException 如果找不到对应的城市
     */
    public City findCityById(String id) {
        City city = cityMap.get(id);
        if (city == null) {
            throw new ResourceNotFoundException("未找到ID为 " + id + " 的城市");
        }
        return city;
    }
}

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

package com.example.logistics.service;

import org.jgrapht.alg.shortestpath.DijkstraShortestPath;
import org.jgrapht.alg.spanning.KruskalMinimumSpanningTree;
import org.jgrapht.graph.DefaultWeightedEdge;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Set;

/**
 * 路径优化服务类,使用图论算法计算最优路径。
 */
@Service
public class PathOptimizer {

    private final Logger logger = LoggerFactory.getLogger(PathOptimizer.class);

    @Autowired
    private TransportationNetworkService networkService;

    /**
     * 计算最小生成树(MST)。
     *
     * @return 最小生成树的边集合
     */
    public Set<DefaultWeightedEdge> computeMST() {
        logger.info("正在计算最小生成树(MST)");
        KruskalMinimumSpanningTree<City, DefaultWeightedEdge> mstAlg =
                new KruskalMinimumSpanningTree<>(networkService.getGraph());
        return mstAlg.getSpanningTree();
    }

    /**
     * 使用Dijkstra算法计算最短路径。
     *
     * @param source 源城市
     * @param target 目标城市
     * @return 最短路径对象
     */
    public DijkstraShortestPath<City, DefaultWeightedEdge> computeShortestPath(City source, City target) {
        logger.info("正在计算从 {} 到 {} 的最短路径", source.getName(), target.getName());
        DijkstraShortestPath<City, DefaultWeightedEdge> dijkstraAlg =
                new DijkstraShortestPath<>(networkService.getGraph());
        return dijkstraAlg.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

package com.example.logistics.controller;

import com.example.logistics.exception.BadRequestException;
import com.example.logistics.model.City;
import com.example.logistics.model.TransportData;
import com.example.logistics.service.PathOptimizer;
import com.example.logistics.service.TransportationNetworkService;
import org.jgrapht.alg.shortestpath.DijkstraShortestPath;
import org.jgrapht.graph.DefaultWeightedEdge;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.Set;

/**
 * 传输控制器类,提供RESTful API接口。
 */
@RestController
@RequestMapping("/api/transport")
public class TransportController {

    private final Logger logger = LoggerFactory.getLogger(TransportController.class);

    @Autowired
    private TransportationNetworkService networkService;

    @Autowired
    private PathOptimizer pathOptimizer;

    /**
     * 构建交通网络。
     *
     * @param data 包含城市和道路信息的传输数据
     * @param bindingResult 绑定结果,用于验证输入数据
     * @return 成功响应
     * @throws BadRequestException 如果输入数据无效
     */
    @PostMapping("/build-network")
    public ResponseEntity<Void> buildNetwork(@Valid @RequestBody TransportData data, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            logger.error("验证错误: {}", bindingResult.getAllErrors());
            throw new BadRequestException(bindingResult.getAllErrors().toString());
        }

        try {
            networkService.buildNetwork(data.getCities(), data.getRoads());
            logger.info("交通网络构建成功");
            return ResponseEntity.ok().build();
        } catch (Exception e) {
            logger.error("构建交通网络时出错: ", e);
            throw new BadRequestException(e.getMessage());
        }
    }

    /**
     * 计算最小生成树(MST)。
     *
     * @return 最小生成树的边集合
     */
    @GetMapping("/compute-mst")
    public ResponseEntity<Set<DefaultWeightedEdge>> computeMST() {
        Set<DefaultWeightedEdge> mstEdges = pathOptimizer.computeMST();
        logger.info("计算得到的MST边集: {}", mstEdges);
        return ResponseEntity.ok(mstEdges);
    }

    /**
     * 计算最短路径。
     *
     * @param sourceId 源城市ID
     * @param targetId 目标城市ID
     * @return 最短路径对象
     * @throws ResourceNotFoundException 如果找不到对应的源或目标城市
     */
    @GetMapping("/shortest-path/{sourceId}/{targetId}")
    public ResponseEntity<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);
        return ResponseEntity.ok(path);
    }
}

十二、LogisticsApplication.java

package com.example.logistics;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class LogisticsApplication {
    public static void main(String[] args) {
        SpringApplication.run(LogisticsApplication.class, args);
    }
}

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

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

十四、测试结果

1. 构建交通网络

curl -X POST http://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 -X GET http://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 -X GET http://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
        }
    ]
}

责任编辑:赵宁宁 来源: Java知识日历
相关推荐

2025-02-28 08:40:28

ZooKeeperSpringBoot计费系统

2025-02-26 09:24:54

SpringMySQLMyBatis

2020-04-23 15:08:41

SpringBootMyCatJava

2025-02-14 09:07:35

2022-04-28 07:31:41

Springkafka数据量

2024-09-05 08:58:37

2024-09-11 08:35:54

2023-08-31 08:34:07

Users对象序列化

2023-08-29 08:00:38

2013-11-26 13:23:14

WAN优化Riverbed

2023-09-04 08:00:53

提交事务消息

2024-11-04 08:02:23

SpringRabbitMQ中间件

2022-05-30 07:31:38

SpringBoot搜索技巧

2024-12-24 08:44:55

ActiveMQRabbitMQ交换机

2023-08-09 08:01:00

WebSockett服务器web

2023-01-10 09:48:03

RESTAPIJersey

2023-01-05 09:17:58

2023-01-13 00:02:41

2023-10-09 07:37:01

2023-10-12 08:00:48

点赞
收藏

51CTO技术栈公众号