首页 > 编程语言 >Java 迪杰斯特拉 算法实现

Java 迪杰斯特拉 算法实现

时间:2023-09-01 15:56:21浏览次数:37  
标签:Java List 迪杰 util 斯特拉 import new path 节点

在这里记录下自己写的迪杰斯特拉代码。

思路

本质是贪心算法:

  • 开始时设定两个集合:S,T;S存入已经遍历的点,T存所有未遍历的点;
  • 首先将起点放入S中,更新T中所有节点的权重(和起点联通的节点更新权重,其他节点权重设为无穷大);
  • 在T中寻找权重最低的点(假设是M点),将M点放入S中,同时更新T里所有节点的权重(判断是否要进行松弛操作,即节点的权重是否大于M点权重+M到此节点的权重,如果是,那将权重更新为M点权重+M到此节点权重);
  • 重复上一步,直到从T里取出的是终点,结束;

代码

首先是定义了节点结构、用于遍历的节点结构和约束条件;

节点结构

import lombok.Getter;
import lombok.Setter;

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

/**
 * 图节点
 *
 * @author ljmine on 2023/7/25
 **/
@Getter
@Setter
public class GraphNode {
    //节点id
    String nodeId;
    // 与此节点相连的节点以及权重,key:相连节点id,value:此节点到相连节点的权重
    // 这里用Map的方式模拟了图里的链路
    Map<String, Integer> nearNodeValueTable = new HashMap<>();

    public GraphNode copy() {
        GraphNode graphNode = new GraphNode();
        graphNode.setNodeId(this.getNodeId());
        Map<String, Integer> copyMap = new HashMap<>(this.nearNodeValueTable);
        graphNode.setNearNodeValueTable(copyMap);
        return graphNode;
    }
}

用于遍历用的节点结构

import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;

/**
 * 遍历节点
 *
 * @author ljmine on 2023/7/25
 **/
@Getter
@Setter
@EqualsAndHashCode(of = "nodeId")
public class TraverseNode {
    // 记录到达此节点的前一节点,用于最后回溯路径
    String preNodeId;

    String nodeId;
    // 权重,默认为int最大值
    Integer weight = Integer.MAX_VALUE;
}

约束条件

import javafx.util.Pair;
import lombok.Getter;
import lombok.Setter;

import java.util.ArrayList;
import java.util.List;

/**
 * 算路约束
 *
 * @author ljmine on 2023/7/25
 **/
@Getter
@Setter
public class Constraint {

    List<String> excludeNode = new ArrayList<>();

    List<Pair<String, String>> excludeLink;

    List<String> includeNode = new ArrayList<>();
}

输出结果结构

import javafx.util.Pair;
import lombok.Getter;
import lombok.Setter;

import java.util.ArrayList;
import java.util.List;

@Getter
@Setter
public class CalcResult {
    // 输出路径
    List<String> resultPath = new ArrayList<>();
    // 此路径总权重
    Integer weight = Integer.MAX_VALUE;
    // 算多条路径时使用
    List<Pair<List<String>,Integer>> otherPaths = new ArrayList<>();
}

功能实现

以下是实现代码,可以实现必排节点约束和顺序必经约束,对于无序必经约束,迪杰斯特拉只能暴力穷举,不太实用,这里就不写了:

import com.example.CalcResult;
import com.example.Constraint;
import com.example.GraphNode;
import com.example.TraverseNode;
import javafx.util.Pair;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 迪杰斯特拉算法
 *
 * @author ljmine on 2023/7/24
 **/
public class Dijkstra {

    private final List<GraphNode> graph;

    private final String startNodeId;

    private final String endNodeId;

    private final Constraint constraint;
    private final List<TraverseNode> traversedNodes = new ArrayList<>();

    private final List<TraverseNode> noTraverseNodes = new ArrayList<>();

    public Dijkstra(List<GraphNode> graph, String startNodeId, String endNodeId, Constraint constraint) {
        List<GraphNode> copyGraph = new ArrayList<>();
        graph.forEach(graphNode -> {
            GraphNode copy = graphNode.copy();
            copyGraph.add(copy);
        });
        this.graph = copyGraph;
        this.startNodeId = startNodeId;
        this.endNodeId = endNodeId;
        this.constraint = constraint;
    }

    public CalcResult calcShortPath(boolean needPrint) {
        init();
        CalcResult result = findPath();
        if(needPrint){
            result.getResultPath().forEach(System.out::println);
            System.out.println("weight:" + result.getWeight());
        }
        return result;
    }

    /**
     * 初始化,构造S,T两个集合
     */
    private void init() {
        noTraverseNodes.clear();
        traversedNodes.clear();
        if (constraint != null) {
            // 有必排约束
            deleteNodeByExcludeConstraint();
            deleteNearNodeByExcludeConstraint();
        }
        graph.forEach(graphNode -> {
            TraverseNode traverseNode = new TraverseNode();
            traverseNode.setNodeId(graphNode.getNodeId());
            traverseNode.setWeight(Integer.MAX_VALUE);
            if (startNodeId.equals(traverseNode.getNodeId())) {
                traverseNode.setWeight(0);
            }
            noTraverseNodes.add(traverseNode);
        });
    }

    private CalcResult findPath() {
        if (constraint != null) {
            // 必经约束,这里要求必经必须有序,无序必经迪杰斯特拉处理不好,需要遗传算法解决
            CalcResult result = segmentDijkstraByIncludeConstraint();
            if (result != null) {
                return result;
            }
        }
        while (!noTraverseNodes.isEmpty()) {
            findNextNode();
        }
        List<String> path = new ArrayList<>();
        tidyUpPath(path, traversedNodes, endNodeId, true);

        CalcResult calcResult = new CalcResult();
        calcResult.setResultPath(path);
        traversedNodes.stream().filter(node ->
                Objects.equals(node.getNodeId(), endNodeId)).findFirst().ifPresent(endNode ->
                calcResult.setWeight(endNode.getWeight()));

        return calcResult;
    }

    private CalcResult segmentDijkstraByIncludeConstraint() {
        List<String> includeNodeIds = constraint.getIncludeNode();
        if (includeNodeIds != null && !includeNodeIds.isEmpty()) {
            CalcResult calcResult = new CalcResult();
            includeNodeIds.add(0, startNodeId);
            includeNodeIds.add(endNodeId);
            List<String> path = new ArrayList<>();
            Integer weight = 0;
            CalcResult segmentResult;
            Constraint segmentConstraint = new Constraint();
            for (int i = 0; i < includeNodeIds.size() - 1; i++) {
                Dijkstra dijkstra = new Dijkstra(this.graph, includeNodeIds.get(i), includeNodeIds.get(i + 1), segmentConstraint);
                segmentResult = dijkstra.calcShortPath(false);
                path.addAll(segmentResult.getResultPath());
                weight += segmentResult.getWeight();
                // 经过的点作为必排进行下次算路,否则可能出现回环
                List<String> excludeNodes = path.subList(0, path.size() - 1);
                segmentConstraint.getExcludeNode().addAll(excludeNodes);
            }
            path = path.stream().distinct().collect(Collectors.toList());
            calcResult.setResultPath(path);
            calcResult.setWeight(weight);
            return calcResult;
        }
        return null;
    }

    private void deleteNodeByExcludeConstraint() {
        List<String> excludeNodeIds = constraint.getExcludeNode();
        if (excludeNodeIds != null && !excludeNodeIds.isEmpty()) {
            List<GraphNode> excludeNode = this.graph.stream().filter(graphNode ->
                    excludeNodeIds.contains(graphNode.getNodeId())).collect(Collectors.toList());
            for (GraphNode graphNode : excludeNode) {
                this.graph.remove(graphNode);
                graphNode.getNearNodeValueTable().keySet().forEach(nodeId -> this.graph.stream().filter(node ->
                        Objects.equals(nodeId, node.getNodeId())).findFirst().ifPresent(nearNode ->
                        nearNode.getNearNodeValueTable().remove(graphNode.getNodeId())));
            }
        }
    }

    private void deleteNearNodeByExcludeConstraint() {
        List<Pair<String, String>> excludeLinkIds = constraint.getExcludeLink();
        if (excludeLinkIds != null && !excludeLinkIds.isEmpty()) {
            for (Pair<String, String> excludeLinkId : excludeLinkIds) {
                String firstGraphNodeId = excludeLinkId.getKey();
                String secondGraphNodeId = excludeLinkId.getValue();
                this.graph.stream().filter(node ->
                        Objects.equals(node.getNodeId(), firstGraphNodeId)).findFirst().ifPresent(firstNode ->
                        firstNode.getNearNodeValueTable().remove(secondGraphNodeId));
                this.graph.stream().filter(node ->
                        Objects.equals(node.getNodeId(), secondGraphNodeId)).findFirst().ifPresent(secondNode ->
                        secondNode.getNearNodeValueTable().remove(firstGraphNodeId));
            }
        }
    }

    /**
     * 循环此方法,每次从T里取权重最低的点,放入S中,并更新T里的权重
     */
    private void findNextNode() {
        noTraverseNodes.stream().min(Comparator.comparing(TraverseNode::getWeight)).ifPresent(minNode -> {
            Integer curWeight = minNode.getWeight();
            GraphNode curNode = graph.stream().filter(node ->
                    Objects.equals(node.getNodeId(), minNode.getNodeId())).findFirst().orElse(null);
            if (curNode == null) {
                System.out.println("error! node is null");
                return;
            }
            Map<String, Integer> nearNodes = curNode.getNearNodeValueTable();
            noTraverseNodes.forEach(noTraverseNode -> {
                String nodeId = noTraverseNode.getNodeId();
                if (nearNodes.containsKey(nodeId)) {
                    if (noTraverseNode.getWeight() > curWeight + nearNodes.get(nodeId)) {
                        noTraverseNode.setWeight(curWeight + nearNodes.get(nodeId));
                        noTraverseNode.setPreNodeId(curNode.getNodeId());
                    }
                }
            });
            noTraverseNodes.remove(minNode);
            traversedNodes.add(minNode);
        });
    }
	
    public static void tidyUpPath(List<String> path, List<TraverseNode> traversedNodes, String endNodeId, boolean needRev) {
        if (endNodeId == null) {
            if (needRev) {
                Collections.reverse(path);
            }
            return;
        }
        TraverseNode curNode = traversedNodes.stream().filter(node ->
                endNodeId.equals(node.getNodeId())).findFirst().orElse(null);
        if (curNode != null) {
            path.add(endNodeId);
            tidyUpPath(path, traversedNodes, curNode.getPreNodeId(), needRev);
        }
    }
}

标签:Java,List,迪杰,util,斯特拉,import,new,path,节点
From: https://www.cnblogs.com/liu-im/p/17672088.html

相关文章

  • Java是一种广泛使用的面向对象编程语言
    Java是一种广泛使用的面向对象编程语言,具有以下特性:平台无关性:Java语言编写的程序可以在不同的操作系统和硬件平台上运行,因为Java语言通过Java虚拟机(JVM)实现了平台无关性。面向对象:Java是一种完全面向对象的编程语言,支持封装、继承和多态等面向对象的基本特性。强类型语言:Java是一......
  • Java Swing查看字体和设置全局字体
    查看支持的字体以下代码用于运行时在控制台打印支持的字体GraphicsEnvironmentgEnv=GraphicsEnvironment.getLocalGraphicsEnvironment();finalStringAvailableFontFamilyNames[]=gEnv.getAvailableFontFamilyNames();Stream.of(AvailableFontFamilyNames).forEach(Sys......
  • 基于JavaWeb的科技创新管理系统的设计与实现-计算机毕业设计源码+LW文档
    选题意义: 现代企业越来越重视管理观念的改变,并随着信息化技术的发展,企业信息化程度逐渐提高,许多企业使用管理系统来提高管理效率,比如企业的OA办公管理,通过系统实现员工工作流程的管理以及各项事宜系统化管理。对企业的产品管理方面,使用产品采购管理系统、产品销售管理系统和产品......
  • Java并发编程:volatile关键字解析
    Java并发编程:volatile关键字解析volatile这个关键字可能很多朋友都听说过,或许也都用过。在Java5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果。在Java5之后,volatile关键字才得以重获生机。volatile关键字虽然从字面上理解起来比较简单,但是......
  • 超全面的JavaWeb笔记day10<Response&Request&路径&编码>
    1、Response2、Request3、路径4、编码请求响应流程图 response1、response概述response是Servlet.service方法的一个参数,类型为javax.servlet.http.HttpServletResponse。在客户端发出每个请求时,服务器都会创建一个response对象,并传入给Servlet.service()方法。response对象是用来......
  • Java8之Stream流
    先贴上几个案例,水平高超的同学可以挑战一下:1.从员工集合中筛选出salary大于8000的员工,并放置到新的集合里。2.统计员工的最高薪资、平均薪资、薪资之和。3.将员工按薪资从高到低排序,同样薪资者年龄小者在前。4.将员工按性别分类,将员工按性别和地区分类,将员工按薪资是否高于8000......
  • java锁升级的过程
    当我们只有一个线程的时候锁是无效的,所以在这个时候如果你加了一个锁那么这个锁叫做偏向锁,偏向我这个线程,当线程数量不是很多比如只有三五个线程,那么他们会进行锁争抢,这个时候锁会升级为自旋锁,当线程数量在增多,锁就会变成重量锁,Sys就是重量级锁......
  • 再看java枚举
    每一个枚举都是一个一个常量,遵循对象不可变,但对象中的内容可变,这个原则枚举也可以说是对象,不过这个对象比较特殊,在赋值的时候不需要使用new,只需要声明变量以及构造方法就能赋值,赋值方式,枚举名字(name,age)......
  • 如何使用javascript制作一个网页端3D贪吃蛇游戏(附源码)
    3D网页版贪吃蛇游戏!下面来具体讲一下如何实现。该游戏使用Hightopo的SDK制作,总共100多行代码,没有WebGL基础的同学们也可很快掌握。场景初始化首先,我们对页面进行初始化,包括初始化3D场景,设置地面网格,以及开启事件监听等。主要代码及注释如下:w=40;//网格间距m=20;//......
  • javascript 两个小于号<<是什么操作符?
    JavaScript中有一个特殊的运算符,被称为“双小于号”。这个运算符的符号是“<<”,作用是将一个数的二进制形式向左移动指定的位数。在移位过程中,右侧的位将会自动补0。双小于号在JavaScript中常常用于进行位运算操作,让我们来了解一下它的具体用法。参考:https://www.yzktw.com.cn/po......