JavaDS —— 图

news/2024/9/20 3:20:32 标签: java, 数据结构,

的概念

是由顶点集合以及顶点之间的关系组成的一种数据结构:G = (V,E)
其中 V 表示的是顶点集合 : V = { x | x 属于某个数据对象集} 是有穷非空集合
E 叫做边的集合 : E = {(x, y) | x, y 属于 V} 或者 E = {<x, y> | x, y 属于 V 并且 Path(x,y) 是顶点间关系的有穷集合,也叫做边的集合}

(x,y) 表示 x 到 y 是一条双向通道,即(x,y) 是无方向的;Path<x,y> 表示 x 到 y 的一条单向通道,即Path<x,y> 是有方向的

顶点和边:中结点称为顶点,第i个顶点记作vi。两个顶点vi和vj相关联称作顶点vi和顶点vj之间有一条边,
中的第k条边记作ek,ek = (vi,vj)或<vi,vj>。

有向和无向:在有向中,顶点对<x, y>是有序的,顶点对<x,y>称为顶点x到顶点y的一条边(弧),<x,
y>和<y, x>是两条不同的边,比如下G3和G4为有向。在无向中,顶点对(x, y)是无序的,顶点对(x,y)
称为顶点x和顶点y相关联的一条边,这条边没有特定方向,(x, y)和(y,x)是同一条边,比如下G1和G2为
无向。注意:无向边(x, y)等于有向边<x, y>和<y, x>。

在这里插入<a class=图片描述" />

完全在有n个顶点的无向中,若有n * (n-1)/2条边,即任意两个顶点之间有且仅有一条边,则称此
无向完全

在n个顶点的有向中,若有n * (n-1)条边,即任意两个顶点之间有且仅有方向相反的边,则称此为有向完全

邻接顶点:在无向中G中,若(u, v)是E(G)中的一条边,则称u和v互为邻接顶点,并称边(u,v)依附于顶点u
和v;在有向G中,若<u, v>是E(G)中的一条边,则称顶点u邻接到v,顶点v邻接自顶点u,并称边<u, v>与
顶点u和顶点v相关联。

顶点的度:顶点v的度是指与它相关联的边的条数,记作deg(v)。在有向中,顶点的度等于该顶点的入度与
出度之和,其中顶点v的入度是以v为终点的有向边的条数
,记作indev(v); 顶点v的出度是以v为起始点的有向
边的条数
,记作outdev(v)。因此:dev(v) = indev(v) + outdev(v)。注意:对于无向,顶点的度等于该顶点的入度和出度,即dev(v) = indev(v) = outdev(v)。

路径:在G = (V, E)中,若从顶点vi出发有一组边使其可到达顶点vj,则称顶点vi到顶点vj的顶点序列为从
顶点vi到顶点vj的路径。

路径长度:对于不带权的,一条路径的路径长度是指该路径上的边的条数;对于带权的,一条路径的路
径长度是指该路径上各个边权值的总和。

简单路径与回路:若路径上各顶点v1,v2,v3,…,vm均不重复,则称这样的路径为简单路径。若路 径上
第一个顶点v1和最后一个顶点vm重合,则称这样的路径为回路或环。
在这里插入<a class=图片描述" />

:设G = {V, E}和G1 = {V1,E1},若V1属于V且E1属于E,则称G1是G的子
在这里插入<a class=图片描述" />

要求顶点齐全即可。

连通:在无向中,若从顶点v1到顶点v2有路径,则称顶点v1与顶点v2是连通的。如果中任意一 对顶点都是连通的,则称此为连通

强连通:在有向中,若在每一对顶点vi和vj之间都存在一条从vi到vj的路径,也存在一条从vj到 vi的路
径,则称此是强连通

生成树:一个连通的最小连通子称作该的生成树。有n个顶点的连通的生成树有n个顶点和n-1条
边。

的存储结构

存储,我们需要保存节点与关系(节点与节点的关系,是否连通,是否带有权重),的存储结构有两种,一种是邻接矩阵,另一种是邻接表,在后面的算法中,本文章会以邻接矩阵为例子提供演示。

邻接矩阵

无向的邻接矩阵是对称的,第i行(列)元素之和,就是顶点i的度。有向的邻接矩阵则不一定是对称
的,第i行(列)元素之后就是顶点i 的出(入)度。0 表示不连通, 1 表示连通
在这里插入<a class=图片描述" />
如果边带有权值,并且两个节点之间是连通的,上中的边的关系就用权值代替,如果两个顶点不通,
则使用无穷大代替。
下面代码实现,这里把自己和自己的距离也处理为无穷大,方便后面算法的实现。

在这里插入<a class=图片描述" />
用邻接矩阵存储的优点是能够快速知道两个顶点是否连通,缺陷是如果顶点比较多,边比较少时,矩
阵中存储了大量的0成为系数矩阵,比较浪费空间,并且要求两个节点之间的路径不是很好求

分析实现

首先通过成员变量,一个存储顶点的数组(当然你也可以使用map 来进行映射,因为后面要获取顶点下标,当顶点个数一多,效率就会低下)
还有必不可少的邻接矩阵——二维数组,用来保存边与边的关系
最后还要有一个布尔类型的变量,因为有两种,要么是有向要么是无向,所以这个变量来保存当前的类型。

接着就是简单提供构造方法和初始化顶点数组

然后开始写添加边的代码,添加边也很简单,传入三个参数:起始顶点,目标顶点以及权重,我们只要获取到顶点的下标,将其对应到矩阵上就可以完成了,但是要注意无向的处理,由于无向是一个对称矩阵,边与边的关系也是双向连通的,所以要单独处理。

最后就是获取度,注意的是有向的度的获取,有向的度分为入度和出度。

最终代码

java">package graph;

import java.util.Arrays;

public class GraphByMatrix {
    public char[] arrayV;//顶点数组
    public int[][] matrix;//邻接矩阵
    public boolean isDirect;//是否为有向

    //构造方法,提供size : 顶点个数
    //arrayV 顶点数组
    public GraphByMatrix(int size,boolean isDirect) {
        this.arrayV = new char[size];
        matrix = new int[size][size];
        for (int i = 0; i < matrix.length; i++) {
            Arrays.fill(matrix[i],Integer.MAX_VALUE);
        }
        this.isDirect = isDirect;
    }

    //初始化顶点数组
    public void initArrayV(char[] arrayV) {
        for (int i = 0; i < arrayV.length; i++) {
            this.arrayV[i] = arrayV[i];
        }
    }

    //添加边
    public void addEdge(char srcV, char destV, int weight) {
        int indexSrc = getIndexV(srcV);
        int indexDest = getIndexV(destV);
        matrix[indexSrc][indexDest] = weight;
        //无向是对称矩阵,单独处理
        if(!isDirect) {
            matrix[indexDest][indexSrc] = weight;
        }
    }

    //获得下标
    private int getIndexV(char v) {
        for (int i = 0; i < arrayV.length; i++) {
            if(arrayV[i] == v) {
                return i;
            }
        }
        return -1;
    }

    //获得顶点的度
    public int getDegreeOfV(char v) {
        int count = 0;
        int index = getIndexV(v);
        for (int i = 0; i < matrix.length; i++) {
            if(matrix[index][i] != Integer.MAX_VALUE) {
                count++;
            }
        }

        //有向,要单独处理入度
        if(isDirect) {
            for (int i = 0; i < matrix.length; i++) {
                if(i != index && matrix[i][index] != Integer.MAX_VALUE) {
                    count++;
                }
            }
        }

        return count;
    }

    public void printGraph() {
        for (int i = 0; i < arrayV.length; i++) {
            System.out.print(arrayV[i] + " ");
        }
        System.out.println();
        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < matrix.length; j++) {
                if(matrix[i][j] == Integer.MAX_VALUE) {
                    System.out.print("∞ ");
                } else {
                    System.out.print(matrix[i][j] + " ");
                }
            }
            System.out.println();
        }
    }
}

邻接表

邻接表使用数组表示顶点的集合,使用链表表示边的关系
在这里插入<a class=图片描述" />

注意:无向中同一条边在邻接表中出现了两次。如果想知道顶点vi的度,只需要知道顶点vi边链表集
合中结点的数目即可。

在这里插入<a class=图片描述" />

有向中每条边在邻接表中只出现一次,与顶点vi对应的邻接表所含结点的个数,就是该顶点的
出度,也称出度表,要得到vi顶点的入度,必须检测其他所有顶点对应的边链表,看有多少边顶点的dst
取值是i。

在代码的实现中,我们不采用两张表,而是使用一张表处理,就是入边表。

分析实现

首先创建链表的结点:起始位置,目标位置,权重,next 域,构造方法,注意起始位置和目标位置采用 int 类型保存,通过顶点数组来获取下标。

我们使用ArrayList 来实现邻接表,保存链表。
使用char 类型的数组保存顶点
使用 Boolean 类型的变量保存的类型。

注意ArrayList 的初始化,要对每一个区域赋值为null,如果不这样做,ArrayList 内部的有效数据是为空的,不是 null,这会导致你后面的链表的插入发生越界访问!!!

添加边:
首先我们要先检查这个边是否已经保存进邻接表里了,如果已经有了这条边,就直接返回,如果没有,我们才继续进行添加,添加边的时候,我们采用头插法,然后我们还需要进行考虑无向,因为无向的边是双向关系的,所以还要再添加一条边进去。

获得边的度:
无向:直接遍历对应的链表即可。
但是如果是有向,我们还需要考虑入度的问题,这时候上面求出了出度,如果是有向,还需要遍历ArrayList 进行检查。

最终代码

java">package graph;

import java.util.ArrayList;

public class GraphByNode {
    static class Node{
        public int src;//起始位置
        public int dest;//目标位置
        public int weight;//权重
        public Node next;

        public Node(int src, int dest, int weight) {
            this.src = src;
            this.dest = dest;
            this.weight = weight;
        }
    }

    public ArrayList<Node> edgeList;//邻接表
    public boolean isDirect;//是否为有向
    public char[] arrayV;//顶点数组

    //构造方法
    //注意ArrayList 的处理,如果不赋 null, ArrayList 的有效数据为 0, 无法进行访问的
    public GraphByNode(int size, boolean isDirect) {
        this.edgeList = new ArrayList<>(size);
        for (int i = 0; i < size; i++) {
            edgeList.add(null);
        }
        this.isDirect = isDirect;
        this.arrayV = new char[size];
    }

    //初始化顶点数组
    public void initArrayV(char[] arrayV) {
        for (int i = 0; i < arrayV.length; i++) {
            this.arrayV[i] = arrayV[i];
        }
    }

    //添加边
    public void addEdge(char src, char dest, int weight) {
        int indexSrc = getIndexOfV(src);
        int indexDest = getIndexOfV(dest);

        addNode(indexSrc,indexDest,weight);

        //无向,再次添加
        if(!isDirect) {
            addNode(indexDest,indexSrc,weight);
        }
    }

    private void addNode(int indexSrc, int indexDest, int weight) {
        //首先判断这个节点是否存在
        Node cur = edgeList.get(indexSrc);
        while(cur != null) {
            if(cur.dest == indexDest) {
                return;
            }
            cur = cur.next;
        }

        //没有存储,进行存储
        Node newNode = new Node(indexSrc,indexDest,weight);
        newNode.next = edgeList.get(indexSrc);
        edgeList.set(indexSrc,newNode);
    }

    //获取下标
    private int getIndexOfV(char v) {
        for (int i = 0; i < arrayV.length; i++) {
            if(arrayV[i] == v) {
                return i;
            }
        }
        return -1;
    }

    //获取度
    public int getDegreeOfV(char v) {
        int count = 0;
        int index = getIndexOfV(v);
        Node cur = edgeList.get(index);
        while(cur != null) {
            count++;
            cur = cur.next;
        }

        //有向,单独处理入度
        if(isDirect) {
            for (int i = 0; i < arrayV.length; i++) {
                if (i != index) {
                    cur = edgeList.get(i);
                    while (cur != null) {
                        if (cur.dest == index) {
                            count++;
                        }
                        cur = cur.next;
                    }
                }
            }
        }
        return count;
    }

    //打印邻接表
    public void printGraph() {
        for (int i = 0; i < arrayV.length; i++) {
            Node cur = edgeList.get(i);
            while(cur != null) {
                System.out.print(cur.dest + ":" + cur.weight + "-> ");
                cur = cur.next;
            }
            System.out.println();
        }
    }

}

的遍历

从这里开始,我们以邻接矩阵为例子进行代码的讲解与分析。

的遍历,以无向为例

广度优先遍历

在这里插入<a class=图片描述" />
广度优先遍历类似二叉树的层次遍历。

广度优先遍历的英文全称为 Breadth-First Search,简写为 bfs

分析:
类似二叉树的层次遍历,在二叉树的层次遍历我们借助了一个队列,这里我们也使用一个队列来保存顶点。

然后我们开始进行运行,以下面的为例:
在这里插入<a class=图片描述" />

先从A开始遍历,把A放入队列,在出队列的同时,遍历矩阵把B和C放入队列中,然后进行B的出队列,这时候又遍历矩阵B那行,这时候A和C 进来了,你会发现A重复进来了,所以为例避免这种情况,我们使用一个辅助数组来记录某个顶点是否被遍历了,也就是在出队列和进队列的时候把这个顶点标记为 true,表示已经被遍历过了。

java">    public void bfs(char src) {
        int n = arrayV.length;
        boolean[] check = new boolean[n];//辅助数组
        Queue<Character> queue = new LinkedList<>();
        queue.offer(src);
        while(!queue.isEmpty()) {
            char ch = queue.poll();
            System.out.print(ch + " ");
            int index = getIndexV(ch);
            check[index] = true;
            for (int i = 0; i < n; i++) {
                if(matrix[index][i] != Integer.MAX_VALUE && !check[i]) {
                    queue.offer(arrayV[i]);
                    check[i] = true;
                }
            }
        }
        System.out.println();
    }

深度优先遍历

在这里插入<a class=图片描述" />
深度优先遍历类似二叉树的前序遍历。

深度遍历英文全称为Depth First Search,简写为 dfs

思路:
使用一个辅助数据记录当前的顶点是否被访问过
然后我们使用递归,每次遇到未被访问过的顶点,进行递归处理。

java">    public void dfs(char v) {
        boolean[] visited = new boolean[arrayV.length];
        int index = getIndexV(v);
        dfsChile(index,visited);
    }

    private void dfsChile(int index, boolean[] visited) {
        System.out.print(arrayV[index] + "->");
        visited[index] = true;

        for (int i = 0; i < arrayV.length; i++) {
            if(!visited[i] && matrix[index][i] != Integer.MAX_VALUE) {
                dfsChile(i,visited);
            }
        }
    }

最小生成树

连通中的每一棵生成树,都是原的一个极大无环子,即:从其中删去任何一条边,生成树 就不在连
通;反之,在其中引入任何一条新边,都会形成一条回路。

若连通由n个顶点组成,则其生成树必含n个顶点和n-1条边。因此构造最小生成树的准则有三条:

  1. 只能使用中的边来构造最小生成树
  2. 只能使用恰好n-1条边来连接中的n个顶点
  3. 选用的n-1条边不能构成回路

构造最小生成树的方法:Kruskal算法和Prim算法。这两个算法都采用了逐步求解的贪心策略

贪心算法:是指在问题求解时,总是做出当前看起来最好的选择。也就是说贪心算法做出的不是整体最优的
的选择,而是某种意义上的局部最优解。贪心算法不是对所有的问题都能得到整体最优解。

Kruskal 算法

KrusKal 算法中文名称为:克鲁斯卡尔算法

任给一个有n个顶点的连通网络N={V,E},
首先构造一个由这n个顶点组成、不含任何边的G={V,NULL},其中每个顶点自成一个连通分量,
其次不断从E中取出权值最小的一条边(若有多条任取其一),若该边的两个顶点来自不同的连通分量,则将此边加入到G中。
如此重复,直到所有顶点在同一个连通分量上为止。

核心:每次迭代时,选出一条具有最小权值,且两端点不在同一连通分量上的边,加入生成树。

Kruskal 算法采用的是全局贪心策略,即每次都在全中获取最小的边,并且要求这条边不能构成一条回路。

在这里插入<a class=图片描述" />

我们来过一下Kruskal 算法的流程:
在这里插入<a class=图片描述" />
纵观全,我们找到 h - g 这条边的全职最小,将其纳入到最小生成树中

在这里插入<a class=图片描述" />

接着再次纵观全,发现 i - c 或者 g - f 这两条边都是最小,将谁纳入到最小生成树都是可以的,这里我们将 i - c 纳入。

在这里插入<a class=图片描述" />
纵观全,发现 g - f 最小,纳入生成树中。

在这里插入<a class=图片描述" />
a - b 与 c - f 都是 4 ,将其一纳入即可,这里纳入 a-b

在这里插入<a class=图片描述" />
c-f 进入最小生成树中

在这里插入<a class=图片描述" />
然后你会发现这时候最小的边应该为 i - g ,但是我们的最小生成树要求不能有环,所以这条边不能纳入,然后我们再继续找,这时候 c - d 和 i - h 为最小边,但是由于 i- h 纳入的话,会构成环路,所以只能将 c - d 纳入最小生成树中。

在这里插入<a class=图片描述" />

重复上面的动作,最后得到下面的最小生成树:

在这里插入<a class=图片描述" />

注意最小生成树的结果不是唯一的,只要符合算法思想即可

算法思路分析:
首先我们需要从全中找到一个最小的边,我们可以使用优先级队列构建小根堆,将全部的边纳入到中,通过出队列的方式获取最小的边。

然后我们需要判断最小的边会不会构成回路,如果不会才能纳入到最小生成树中。
判断回路:我们可以采用集合的方式,如果这条边的两个顶点都在生成树的集合中,就不能纳入,这时候我们可以使用并查集来判断,如果不了解并查集可以查阅这一篇博客 JavaDS —— 并查集
在每次纳入生成树的同时,将这两个顶点纳入并查集中。

注意生成树要求 n 个顶点加 n- 1 条边,这是有可能满足不了 n - 1 条边的,就像下面的一样,D 点是隔离的,无法实现最小生成树,这时候你在构建最小生成树的结尾要判断是否构建好了最小生成树,如果没有可以返回 -1


最终代码:

java">    //创建边类
    static class Edge {
        public int src;
        public int dest;
        public int weight;

        public Edge(int src, int dest, int weight) {
            this.src = src;
            this.dest = dest;
            this.weight = weight;
        }
    }

    public int Kruskal(GraphByMatrix minTree) {
        //创建优先级队列
        PriorityQueue<Edge> queue = new PriorityQueue<>(new Comparator<Edge>() {
            @Override
            public int compare(Edge o1, Edge o2) {
                return o1.weight - o2.weight;
            }
        });

        //将所有的边纳入到优先级队列中
        int n = arrayV.length;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if(matrix[i][j] != Integer.MAX_VALUE) {
                    queue.offer(new Edge(i,j,matrix[i][j]));
                }
            }
        }

        UnionFindSet unionFindSet = new UnionFindSet(n);

        //构建生成树
        int size = 0;
        int totalWeight = 0;
        while(!queue.isEmpty() && size < n - 1) {
            Edge tmp = queue.poll();
            int src = tmp.src;
            int dest = tmp.dest;
            if(!unionFindSet.isSameSet(src,dest)) { 
                minTree.addEdge(arrayV[src],arrayV[dest], tmp.weight);
                unionFindSet.union(src,dest);
                totalWeight += tmp.weight;
                size++;
            }
        }

        if(size < n - 1) {
            return -1;
        }
        return totalWeight;
    }

Prime 算法

Prime 算法 中文名称为 普里姆算法

在这里插入<a class=图片描述" />

在这里插入<a class=图片描述" />

Prime 算法采用局部贪心的策略。

我们来走一下这个算法的流程:

首先给定一个初始的顶点,这里给的是 a
在这里插入<a class=图片描述" />
然后从 d 开始寻找最小边,得到 a - b,将其纳入到最小生成树中。在这里插入<a class=图片描述" />
然后我们得到最小生成树的集合为 {a,b},这时候 b 向外延伸, b - c = 8, b - h = 11, a - h = 8,经过比较,b - c 和 a-h 都是最小边,将其一纳入即可,这里纳入的是 b-c
在这里插入<a class=图片描述" />

现在最小生成树的集合为 {a,b.c}, c 开始延伸,c - i = 2 , c - f = 4, c - d = 7 ,并且和前面的 a-h = 8 进行比较,最后纳入 c - i 这条边。
在这里插入<a class=图片描述" />

i 延伸,i - g = 6, i - h = 7 , 并且和前面的为被纳入的最小边集合进行比较,最后将 c - f 纳入生成树中。

在这里插入<a class=图片描述" />
然后以此类推,最后得到:
在这里插入<a class=图片描述" />

算法分析:
我们还是使用优先级队列保存边的关系,但是prime 算法是每进一个顶点,才会将新顶点所在的边关系全部入队。

首先先将初始顶点的边关系信息入队,然后开始找最小边,然后将最小边的信息纳入到生成树中,并且与之对应的新顶点也要纳入到集合中,新顶点的边关系也随之纳入进去。使用循环来进行操作。

最终代码:

java">	//创建边类
    static class Edge {
        public int src;
        public int dest;
        public int weight;

        public Edge(int src, int dest, int weight) {
            this.src = src;
            this.dest = dest;
            this.weight = weight;
        }
    }
    
    public int prime(GraphByMatrix minTree, char src) {
        int totalWeight = 0;
        int n = arrayV.length;
        int size = 0;

        //构建优先级队列
        PriorityQueue<Edge> queue = new PriorityQueue<>(new Comparator<Edge>() {
            @Override
            public int compare(Edge o1, Edge o2) {
                return o1.weight - o2.weight;
            }
        });

        UnionFindSet unionFindSet = new UnionFindSet(n);
        int index = getIndexV(src);
        for (int i = 0; i < n; i++) {
            if(matrix[index][i] != Integer.MAX_VALUE) {
                queue.offer(new Edge(index,i,matrix[index][i]));
            }
        }

        while(!queue.isEmpty()) {
            Edge tmp = queue.poll();
            int indexSrc = tmp.src;
            int indexDest = tmp.dest;
            int weight = tmp.weight;
            if(!unionFindSet.isSameSet(indexSrc,indexDest)) {
                unionFindSet.union(indexSrc,indexDest);
                minTree.addEdge(arrayV[indexSrc],arrayV[indexDest],weight);
                for (int i = 0; i < n; i++) {
                    if(matrix[indexDest][i] != Integer.MAX_VALUE) {
                        queue.offer(new Edge(index,i,matrix[index][i]));
                    }
                }
                totalWeight += weight;
                size++;
            }
        }

        if(size < n - 1) {
            return -1;
        }
        return totalWeight;
    }

最短路径

最短路径问题:从在带权的某一顶点出发,找出一条通往另一顶点的最短路径,最短也就是沿路径各边的权值总和达到最小。

Dijkstra算法

迪杰斯特拉算法

单源最短路径问题:给定一个G = ( V , E ) G=(V,E)G=(V,E),求源结点s ∈ V s∈Vs∈V到中每个结点v
∈ V v∈Vv∈V的最短路径。Dijkstra算法就适用于解决带权重的有向上的单源最短路径问题,同时算法要
中所有边的权重非负。一般在求解最短路径的时候都是已知一个起点和一个终点,所以使用Dijkstra算法
求解过后也就得到了所需起点到终点的最短路径。

针对一个带权有向G,将所有结点分为两组S和Q,S是已经确定最短路径的结点集合,在初始时为空(初始
时就可以将源节点s放入,毕竟源节点到自己的代价是0),Q 为其余未确定最短路径的结点集合,每次从Q
中找出一个起点到该结点代价最小的结点u ,将u 从Q 中移出,并放入S 中,对u 的每一个相邻结点v 进行松
弛操作。松弛即对每一个相邻结点v ,判断源节点s到结点u 的代价与u 到v 的代价之和是否比原来s 到v 的代
价更小,若代价比原来小则要将s 到v 的代价更新为s 到u 与u 到v 的代价之和,否则维持原样。如此一直循
环直至集合Q 为空,即所有节点都已经查找过一遍并确定了最短路径,至于一些起点到达不了的结点在算法
循环后其代价仍为初始设定的值,不发生变化。Dijkstra算法每次都是选择V-S中最小的路径节点来进行更
新,并加入S中,所以该算法使用的是贪心策略。

Dijkstra算法存在的问题是不支持中带负权路径,如果带有负权路径,则可能会找不到一些路径的最短路径。

在这里插入<a class=图片描述" />

java">    public void dijkstra(char vSrc,int[] dist,int[] pPath) {
        int srcIndex = getIndexV(vSrc);
        //距离数据初始化
        Arrays.fill(dist,Integer.MAX_VALUE);
        dist[srcIndex] = 0;
        //路径数组初始化
        Arrays.fill(pPath,-1);
        pPath[srcIndex] = 0;
        //当前顶点是否被访问过?
        int n = arrayV.length;
        boolean[] s = new boolean[n];

        //n个顶点,要更新n次,每次都要从0下标开始,找到一个最小值
        for (int k = 0; k < n; k++) {
            int min = Integer.MAX_VALUE;
            int u = srcIndex;
            for (int i = 0; i < n; i++) {
                if(s[i] == false && dist[i] < min) {
                    min = dist[i];
                    u = i;//更新u下标
                }
            }
            s[u] = true;//u:s
            //松弛u连接出去的所有的顶点 v
            for (int v = 0; v < n; v++) {
                if(s[v] == false && matrix[u][v] != Integer.MAX_VALUE
                        && dist[u] + matrix[u][v] < dist[v]) {
                    dist[v] = dist[u] + matrix[u][v];
                    pPath[v] = u;//更新当前的路径
                }
            }
        }
    }

Bellman-Ford算法

Dijkstra算法只能用来解决正权的单源最短路径问题,但有些题目会出现负权。这时这个算法就不能帮助
我们解决问题了,而bellman—ford算法可以解决负权的单源最短路径问题。它的优点是可以解决有负权
边的单源最短路径问题,而且可以用来判断是否有负权回路。它也有明显的缺点,它的时间复杂度 O(N*E)
(N是点数,E是边数)普遍是要高于Dijkstra算法O(N²)的。像这里如果我们使用邻接矩阵实现,那么遍历所有
边的数量的时间复杂度就是O(N^3),这里也可以看出来Bellman-Ford就是一种
暴力求解更新

在这里插入<a class=图片描述" />

java">    public boolean bellmanFord(char vSrc,int[] dist,int[] pPath) {
        int srcIndex = getIndexV(vSrc);
        //距离数据初始化
        Arrays.fill(dist,Integer.MAX_VALUE);
        dist[srcIndex] = 0;
        //路径数组初始化
        Arrays.fill(pPath,-1);
        pPath[srcIndex] = 0;

        int n = arrayV.length;

        for (int k = 0; k < n; k++) {
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    if(matrix[i][j] != Integer.MAX_VALUE &&
                            dist[i] + matrix[i][j] < dist[j]) {
                        dist[j] = dist[i] + matrix[i][j];
                        pPath[j] = i;
                    }
                }
            }
        }

        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if(matrix[i][j] != Integer.MAX_VALUE &&
                        dist[i] + matrix[i][j] < dist[j]) {
                    return false;
                }
            }
        }
        return true;
    }

Floyd-Warshall算法

Floyd-Warshall算法是解决任意两点间的最短路径的一种算法。
Floyd算法考虑的是一条最短路径的中间节点,即简单路径p={v1,v2,…,vn}上除v1和vn的任意节点。 设k是p
的一个中间节点,那么从i到j的最短路径p就被分成i到k和k到j的两段最短路径p1,p2。p1是从i到k且中间节
点属于{1,2,…,k-1}取得的一条最短路径。p2是从k到j且中间节点属于{1,2,…,k-1}取得的一条最短路
径。

在这里插入<a class=图片描述" />
在这里插入<a class=图片描述" />

Floyd算法本质是三维动态规划,D[i][j][k]表示从点i到点j只经过0到k个点最短路径,然后建立起转移方程,然后通过空间优化,优化掉最后一维度,变成一个最短路径的迭代算法,最后即得到所以点的最短路

java">    public void floydWarShall(int[][] dist,int[][] pPath) {
        int n = arrayV.length;

        for (int i = 0; i < n; i++) {
            Arrays.fill(dist[i],Integer.MAX_VALUE);
            Arrays.fill(pPath[i],-1);
        }

        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if(matrix[i][j] != Integer.MAX_VALUE) {
                    dist[i][j] = matrix[i][j];
                    pPath[i][j] = i;
                }else {
                    pPath[i][j] = -1;
                }
                if(i == j) {
                    dist[i][j] = 0;
                    pPath[i][j] = -1;
                }
            }
        }

        for (int k = 0; k < n; k++) {
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    if (dist[i][k] != Integer.MAX_VALUE &&
                            dist[k][j] != Integer.MAX_VALUE &&
                            dist[i][k] + dist[k][j] < dist[i][j]) {
                        dist[i][j] = dist[i][k] + dist[k][j];
                        //更新父节点下标
                        //pPath[i][j] = k;//不对
                        //如果经过了 i->k  k->j  此时是k
                        //i->x->s->k   k->..t->...x->j
                        pPath[i][j] = pPath[k][j];
                    }
                }
            }
        }
    }

http://www.niftyadmin.cn/n/5666510.html

相关文章

Linux基础 -- 原子同步之__sync_val_compare_and_swap 用法详解

__sync_val_compare_and_swap 用法详解 __sync_val_compare_and_swap 是 GCC 提供的一个内置函数&#xff0c;用于实现原子操作。它在多线程编程中非常有用&#xff0c;可以用来实现无锁数据结构和同步机制。该函数提供了一个原子性的比较并交换操作&#xff0c;即在一个多线程…

GUI编程18:文本框、密码框、文本域

视频链接&#xff1a;20、文本框、密码框、文本域_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1DJ411B75F?p20&vd_sourceb5775c3a4ea16a5306db9c7c1c1486b5 1.文本框 示例代码&#xff1a; package com.yundait.lesson06;import javax.swing.*; import java.a…

【Vue】2

1 Vue 生命周期 Vue生命周期&#xff1a;一个 Vue 实例从 创建 到 销毁 的整个过程 创建(create)阶段&#xff1a;组件实例化时&#xff0c;初始化数据、事件、计算属性等挂载(mount)阶段&#xff1a;将模板渲染并挂载到 DOM 上更新(update)阶段&#xff1a;当数据发生变化时…

【LabVIEW】事件结构的用法

本篇文章记录我学习LabVIEW的事件结构用法&#xff0c;希望我的分享对你有所帮助&#xff01; 目录 一、案例说明 1、 LabVIEW实现“YAXBXC的计算” 2、添加事件结构 一、案例说明 在LabVIEW实现“YAXBXC的计算”的基础上&#xff0c;加上事件结构&#xff0c;实现单击一次按…

Android 15 正式发布至 AOSP

Google官方宣布&#xff0c;将于近期发布了 Android 15&#xff0c;而在早些时候&#xff0c;Google已经将其源代码推送至 Android 开源项目 (AOSP)。未来几周内&#xff0c;Android 15 将在受支持的 Pixel 设备上正式推出&#xff0c;并将于今年晚些时候在三星、Honor、iQOO、…

JavaWeb JavaScript 11.XML —— 配置文件

生活想埋没我&#xff0c;没想到我是颗种子 —— 24.9.19 一、XML 1.什么是XML XML是EXtensible Markup Languge的缩写&#xff0c;翻译过来就是可扩展标记语言。所以很明显&#xff0c;XML和HTML一样都是标记语言&#xff0c;也就是说它们的基本语法都是标签 可扩展 三个字…

linux-用户与权限管理-sudo 权限管理

Linux 用户与权限管理概述 在Linux操作系统中&#xff0c;用户和权限管理是确保系统安全与稳定运行的关键组成部分。每个Linux系统中&#xff0c;用户被分配不同的角色和权限&#xff0c;基于这些权限执行不同的任务。权限管理的核心是文件和目录的访问控制&#xff0c;以及如…

轻兔推荐 —— 质感文件

via&#xff1a;轻兔推荐 - https://app.lighttools.net/ 简介 质感文件是一款开源的安卓文件管理App&#xff0c;遵循Material Design规范&#xff0c;界面设计很有质感&#xff0c;文件操作方便 - 除了能管理本地文件&#xff0c;也可以添加远程存储&#xff0c;配合NAS使…