图的基本知识

news2025/5/25 18:57:22

    • 一、图的定义和基本术语
    • 二、图的存储结构
      • (1)数组(邻接矩阵表示法)
      • (2)数组(邻接矩阵)的实现
      • (3)邻接表(链式表示法)
      • (4)邻接表(链式表示法)实现
    • 三、图的遍历
      • (1)深度优先遍历算法
      • (2)广度优先遍历算法
    • 四、图的应用
      • 1、构造最小生成树
        • MST性质
        • 普利姆算法(Prim)
        • 克鲁斯卡尔算法(Kruskal)
      • 2、最短路径
        • 迪杰斯特拉(Dijkstra)
        • 弗洛伊德(Floyd)

一、图的定义和基本术语

图的定义:G=(V,E)

V:顶点(数据元素)的有穷非空集合

E:边的有穷集合

图是包含顶点和边的集合

类似于下图,G1由 V1、V2、V3、V4 四个顶点,四条边组成

G2 由五个顶点,七条边组成。

其中G1中的边带有方向称为有向图, 不带方向的称为无向图

image-20230827130907946

完全图:任意俩个点都有一条边相连

image-20230827131201342

稀疏图: 有很少的边或者弧(有向图的边)比较少的图(n< nlogn)

稠密图: 有较多的边或者弧

: 边/弧 带权的图

邻接: 边/弧相连的俩个顶点之间的关系

<> 表示有向,vi -> vj

image-20230827131439586

顶点的度: 与该顶点相关联的边的数目,记为 TD(v)

在有向图中,顶点的度等于该顶点的入度和出度之和。

顶点 v 的入度是以 v 为终点的有向边的条数记作 ID(v)

顶点 v 的出度是以 v 为始点的有向边的条数 记作 OD(v)

image-20230827131822784

: 当有向图中仅1个顶点的入度为0,其余顶点的入度均为1,此时是何形状?

答:是一颗树,是一颗有向树

image-20230827132034411

路径: 接续的边构成的顶点序列

路径长度: 路径上边或弧的数目/权值之和。

假设从0到2,路径有: 0、3、2, 0、1、2,0、2… ,路径长度分别为:2、2、1…

image-20230827132301601

回路(环): 第一个顶点和最后一个顶点相同的路径

简单路径: 除路径起点和终点可以相同外,其余顶点均不相同的路径

简单回路(简单环): 除路径起点和终点相同外,其余顶点均不相同的路径。

image-20230830225956559

连通图 (强连通图)

在无 (有) 向图G=(V,{E})中,若对任何两个顶点 v、u都存在从v 到 u 的路径,则称G是连通图 (强连通图)

image-20230827132841922

权与网

图中边或弧所具有的相关数称为权。表明从一个顶点到另一个顶点的距离或耗费。带权的图称为网

子图:设有两个图G= (V,{E})、G1= (V1,{E1}),若V1 ∈ V,E1 ∈ E,则称 G1是G的子图

image-20230830230604784

极小连通子图:该子图是G的连通子图,在该子图中删除任何一条边,子图不在连通

生成树:包含无向图G所有顶点的极小连通子图

生成 森林:对于非连通图,由各个连通分量的生成树的集合

image-20230830231052994

抽象数据类型定义

image-20230827134220773

二、图的存储结构

图的逻辑结构:多对多

图没有顺序存储结构但可以借助**二维数组(邻接矩阵)**来表示元素间的关系。

链式存储结构

普通的链式存储无法实现图,因为不知道图中某个顶点到底有多少个前驱和后继。

因此可以使用多重表的方式实现。

image-20230827135049037

(1)数组(邻接矩阵表示法)

建立一个顶点表 (记录各个顶点信息) 和一个邻接矩阵 (表示各个顶点之间关系)

设图A=(V,E)有n个顶点,则

image-20230827140122825

图的邻接矩阵是一个二维数组 :

如果 i 和 j 顶点之间有边或者弧就记为1,否则就记为0

image-20230827140157833

举例说明-无向图的邻接矩阵

v1 与 v2、v4 顶点有边,在二维数组中对应为 arcs[v1][v2]=1, arcs[v1][v4]=1, 其余为 0。

v2与v1、v3、v5顶点有边,在二维数组中对应为 arcs[v2][v1]=1, arcs[v2][v3]=1,arcs[v2][v5]=1其余为 0

以此类推…

image-20230827140357548

分析1: 通过图中我们可以发现,对角线上的值全为0,这是因为顶点与自身之间没有边

分析2: 求第 i 个顶点的度,就是第 i 行值的和

分析3:如果是完全图,也就是说每俩个顶点都有一条边相连,那么除了对角线的值为0,其余都为 1

举例说明-有向图的邻接矩阵

如果某个顶点有 以自身为起点到其他顶点的弧(出度) 那么记为1,否则为0。

例如: 以 v1为起点的有 v2,v3,在二维数组中 arcs[v1][v2]=1、arcs[v1][v3]=1,其余为0,以此类推…

image-20230827141146465

注: 在有向图的邻接矩阵中

第i行含义:以结点vi为尾的弧(即出度边)

第i列含义: 以结点vi为头的弧(即入度边)

分析1: 有向图的邻接矩阵可能是不对称的。

分析2

顶点的出度(以该顶点为起点) = 第i行元素值之和

顶点的入度(以该顶点为终点)=第 i 列元素值之和

顶点的度 = 第i行元素值之和 + 第 i 列元素值之和

举例说明-网的邻接矩阵

如果某个顶点有 以自身为起点到其他顶点的弧 那么记为对应的权值,否则为∞。Wij 表示某个顶点的权值

image-20230827141914211

image-20230827142106326

(2)数组(邻接矩阵)的实现

无向网为例。无向网指:没有方向并且带有权值的图

image-20230827150522937

1、定义存储结构并且进行初始化。初始化时传入一个顶点数组,计算该数组的长度length,邻接矩阵为 length*length的矩阵。并将矩阵全都初始化为最大值

package ChapterSix.graph;

import java.util.Arrays;

/**
 *
 * Author: YZG
 * Date: 2023/8/27 14:24
 * Description: 实现 无向图的邻接矩阵表示法
 */
public class AMGraph {
    Object[] vexs; // 顶点数组
    Object[][] arcs; // 邻接矩阵
    int vexNum, arcNum; // 记录顶点、边的个数
    /**
     * @description 初始化
     * @date 2023/8/27 14:45
     * @param vexs 表示顶点数组
     * @return
     */
    public AMGraph(Object[] vexs) {
        this.vexs = vexs;
        // 顶点个数
        int length = vexs.length;
        this.vexNum = length;
        this.arcs = new Object[length][length];
        // 初始化邻接矩阵的值皆为∞ ,在Java就用integer的最大值表示
        for (int i = 0; i < length; i++) {
            for (int j = 0; j < length; j++) {
                arcs[i][j] = Integer.MAX_VALUE;
            }
        }
    }
}

2、根据传入的顶点、权值构建无向网。

    /**
     * @description 创建无向网
     * @date 2023/8/27 14:50
     * @param v1 顶点1
     * @param v2 顶点2
     * @param weight 顶点1和顶点2之间的权值
     * @return void
     */
    public void createUDN(Object v1, Object v2, int weight) {
        // 找到v1、v2的下标
        int i = findIndex(vexs, v1);
        int j = findIndex(vexs, v2);
        // 防止输入错误
        if (i == -1 || j == -1) throw new RuntimeException("您输入顶点有误");
        // 赋值权值,因为是无向图,所以反向的权值也要赋
        arcs[i][j] = weight;
        arcs[j][i] = weight;
        // 边的个数+1
        this.arcNum++;
    }

3、由于传入的是顶点的名称,还需要一个方法用来找到顶点的下标。

/**
 * @description 根据顶点名称找到对应的下标
 * @date 2023/8/27 14:51
 * @param vexs 顶点数组
 * @param v 顶点名称
 * @return int
 */
public int findIndex(Object[] vexs, Object v) {
    for (int i = 0; i < vexs.length; i++) {
        if (vexs[i]==v) return i;
    }
    return -1;
}

测试

    public static void main(String[] args) {
        AMGraph amGraph = new AMGraph(new Object[]{"v1", "v2", "v3","v4"});
        // 增加边
        amGraph.createUDN("v1","v2",1);
        amGraph.createUDN("v1","v3",2);
        amGraph.createUDN("v1","v4",3);
        amGraph.createUDN("v3","v4",4);
        System.out.println(Arrays.deepToString(amGraph.arcs));
        System.out.println("边的个数:" + amGraph.arcNum);
    }

总结

无向图、有向网 都一样。只不过邻接矩阵存储的数据不一样

无向图:没有权值了,因此在arcs初始化时皆为0,在赋值的时候赋为1

         // 无向图-初始化
          arcs[i][j] = 0; 
		// 无向图-赋值
          arcs[i][j] = arcs[j][i] = 1;

有向网:只需要赋一次权值即可,无需设置反向

 arcs[i][j] = weight;

邻接矩阵的优点

  • 直观、简单、好理解
  • 方便检查任意一对顶点间是否存在边
  • 方便找任一顶点的所有“接点”(有边直接相连的顶点)
  • 方便计算任一顶点的“度”(从该点发出的边数为“出度”,指向该点的边数为“入度”)
    • 无向图: 对应行(或列)非0元素的个数
    • 有向图: 对应行非0元素的个数是"出度", 对应列非0元素的个数是"入度"

缺点

  • 不方便增加和删除顶点
  • 浪费空间,例如存储稀疏图(点很多但是边很少)有大量无效元素
  • 浪费时间,统计稀疏图中一共有多少条边

邻接矩阵的方式和边的个数没有关系,只和顶点的个数有关,存储空间:O(n2

(3)邻接表(链式表示法)

邻接表的表示方法仍然需要一个顶点表,但与邻接矩阵的顶点表不同的是,这个顶点表中元素的类型是一个结点

data用来存放顶点的信息,firstarc 用来存储第一个边结点的地址,也就是说与data相连的顶点。

image-20230827153351665

邻接表中仍然使用一个结点来表示俩个顶点的关系

adjvex 用来表示当前顶点的地址,nextarc表示下一个边顶点的地址,因此对于某一个顶点来说有几个相连的顶点就有几个结点

image-20230827153907158

如果存储网结果,就在多加一个链域用于存储权值

image-20230827154457111

案例

对于v1顶点来说,与它相邻的顶点有 v4,v2,在顶点表中对应的下标为 3、1

image-20230827154149934

特点

  • 邻接表不唯一,对于相连的顶点可以更改顺序
  • 若无向图中有 n 个顶点、e条边,则其邻接表需 n 个头结点和2e表结点。适宜存储稀疏图
  • 无向图中顶点 vi 的度为第i个单链表中的结点数

存储空间为:O(n+2e)

有向图-邻接表演示

在有向图中只保存以该顶点为起点的弧(出边)的顶点

例如:以v1为起点的弧的顶点为 v2、v3,对应下标 1,2

image-20230828211429792

特点

  • 顶点为Vi 的出度为第 i 个单链表中的结点个数
  • 顶点 Vi 的入度为整个单链表中邻接点域值是 i -1 的结点个数

找出度易,入度难

(4)邻接表(链式表示法)实现

以无向网为例

1、定义 顶点、边顶点和图的存储结构

public class ALGraph {
    // 存储所有顶点的数组
    VNode[] vertices;
    // 顶点数、边数
    int vexNum,arcNum;
}

// 定义顶点结构
class VNode{
    // 顶点信息
    Object data;
    // 指向第一条边顶点的指针
    ArcNode firstarc;

    @Override
    public String toString() {
        return "VNode{" +
                "data=" + data +
                ", firstarc=" + firstarc +
                '}';
    }
}

// 边顶点类型
class ArcNode{
    // 边顶点的索引位置
    int adjvex;
    // 下一个边顶点的地址
    ArcNode nextarc;
    // 顶点信息
    Object info;

    @Override
    public String toString() {
        return "ArcNode{" +
                "adjvex=" + adjvex +
                ", nextarc=" + nextarc +
                ", info=" + info +
                '}';
    }
}

2、初始化,将顶点信息存储在顶点表,并初始化头指针为NULL

public class ALGraph {
    public static void main(String[] args) {
        ALGraph alGraph = new ALGraph(new Object[]{"A","B","C","D"});
        System.out.println(Arrays.toString(alGraph.vertices));
    }
    // 存储所有顶点的数组
    VNode[] vertices;
    // 顶点数、边数
    int vexNum,arcNum;

    // 初始化 vnodes==顶点集合
    public ALGraph(Object[] vnodes) {
        this.vexNum = vnodes.length;
        this.vertices = new VNode[this.vexNum];
        this.arcNum = 0;
        // 将头顶点赋值,指向第一个边为null
        for (int i = 0; i < this.vexNum; i++) {
            VNode vNode = new VNode();
            vNode.data = vnodes[i];
            vNode.firstarc = new ArcNode();
            this.vertices[i] = vNode;
        }
    }
}

// 定义顶点结构
class VNode{
    // 顶点信息
    Object data;
    // 指向第一条边顶点的指针
    ArcNode firstarc;

    @Override
    public String toString() {
        return "VNode{" +
                "data=" + data +
                ", firstarc=" + firstarc +
                '}';
    }
}

// 边顶点类型
class ArcNode{
    // 边顶点的索引位置
    int adjvex;
    // 下一个边顶点的地址
    ArcNode nextarc;
    // 顶点信息
    Object info;

    @Override
    public String toString() {
        return "ArcNode{" +
                "adjvex=" + adjvex +
                ", nextarc=" + nextarc +
                ", info=" + info +
                '}';
    }
}

3、给定顶点和边的权值生成邻接表

    // 生成邻接表 v1 —— v2
    public void createALGraph(Object v1,Object v2,int weight) {
        // 找到俩个顶点的位置
        int i = findIndex(v1);
        int j = findIndex(v2);
        // 生成新的边顶点
        ArcNode arcNode = new ArcNode();
        arcNode.adjvex = j;
        arcNode.nextarc = vertices[i].firstarc;
        arcNode.info = weight;
        vertices[i].firstarc = arcNode;

        // 由于是无向网,反向也得连接
        ArcNode arcNode1 = new ArcNode();
        arcNode1.adjvex = i;
        arcNode1.nextarc = vertices[j].firstarc;
        arcNode1.info = weight;
        vertices[j].firstarc = arcNode1;
    }

    /**
     * @description 根据顶点名称找到对应的下标
     * @date 2023/8/27 14:51
     * @param v 顶点名称
     * @return int
     */
    public int findIndex(Object v) {
        for (int i = 0; i < vertices.length; i++) {
            if (vertices[i].data == v) return i;
        }
        return -1;
    }

总结:

邻接矩阵与邻接表的关系

image-20230828223437687

联系:

无论是邻接矩阵还是邻接表,第 i 行都代表 第 i 个顶点与其他顶点的关系。

区别

对于任一确定的无向图,邻接矩阵是唯一的 (行列号与顶点编号一致),但邻接表不唯一(链接次序与顶点编号无关)

邻接矩阵的空间复杂度为O(n2) , 邻接表的空间复杂度为O(n+e)

用途

邻接矩阵多用于稠密图,而邻接表多用于稀疏图

三、图的遍历

从已给的连通图中某一顶点出发,沿着一些边访遍图中所有的顶点,且使每个顶点仅被访问一次,就叫做图的遍历,它是图的基本运算。

image-20230828230607258

图的特点

图中可能存在回路,且图的任一顶点都可能与其它顶点相通,在访问完某个顶点之后可能会沿着某些边又回到了曾经访问过的顶点

怎么避免重复访问呢?

可以设置一个辅助数组 visited[n] ,用来表示被访问过的顶点,初始都为false,如果第 i 个顶点被访问,设置 visited[i] = true

图的遍历方法

  • 深度优先搜索 (Depth First Search-DFS )
  • 广度优先搜索 ( Breadth Frist Search-BFS)

(1)深度优先遍历算法

image-20230828231527795

案例演示

V1 =》V2 =》 V4 =》 V8 =》 V5 ,发现走不通了回退到 V8 ,仍然没有可以访问的顶点,继续回退

回退到 V1 =》V3 =》V6 =》 V7

image-20230828231635288

深度优先遍历算法实现

以无向网为例,如下图所示,按照深度优先遍历

image-20230829221434660

假设从 v1 出发,与之邻接的第一个顶点为 v2,在 visited 数组中发现 v2 并没有被访问过,因此访问 v2,并修改 v2 的访问状态

image-20230829221918512

访问完 v2,从邻接矩阵中看出,与之邻接的顶点为v1,但是 v1 已经被访问过。回退到 v1,访问下一个邻接顶点 v3,并修改访问状态。

image-20230829222103872
最后访问v4,结束遍历!

代码实现: 完整代码,包括无向网的创建

public class AMGraph {
    public static void main(String[] args) {
        AMGraph amGraph = new AMGraph(new Object[]{"v1", "v2", "v3", "v4"});
        // 增加边
        amGraph.createUDN("v1", "v2", 1);
        amGraph.createUDN("v1", "v3", 2);
        amGraph.createUDN("v1", "v4", 3);
        amGraph.createUDN("v3", "v4", 4);
        System.out.println(Arrays.deepToString(amGraph.arcs));
        // System.out.println("边的个数:" + amGraph.arcNum);

        // 从v1开始深度遍历
        amGraph.DFS(0);

    }

    Object[] vexs; // 顶点数组
    Object[][] arcs; // 邻接矩阵
    int vexNum, arcNum; // 记录顶点、边的个数
    // 辅助数组,记录顶点是否被访问
    boolean[] visited;

    /**
     * @description 初始化
     * @date 2023/8/27 14:45
     * @param vexs 表示顶点数组
     * @return
     */
    public AMGraph(Object[] vexs) {
        this.vexs = vexs;
        // 顶点个数
        int length = vexs.length;
        this.vexNum = length;
        this.arcs = new Object[length][length];
        this.visited = new boolean[length];
        // 初始化访问数组
        Arrays.fill(visited, false);

        // 初始化邻接矩阵的值皆为∞ ,在Java就用integer的最大值表示
        for (int i = 0; i < length; i++) {
            for (int j = 0; j < length; j++) {
                arcs[i][j] = Integer.MAX_VALUE;
                // 无向图
                // arcs[i][j] = 0;
            }
        }
    }

    /**
     * @description 创建无向网
     * @date 2023/8/27 14:50
     * @param v1 顶点1
     * @param v2 顶点2
     * @param weight 顶点1和顶点2之间的权值
     * @return void
     */
    public void createUDN(Object v1, Object v2, int weight) {
        // 找到v1、v2的下标
        int i = findIndex(vexs, v1);
        int j = findIndex(vexs, v2);
        // 防止输入错误
        if (i == -1 || j == -1) throw new RuntimeException("您输入顶点有误");
        // 赋值权重,因为是无向图,所以反向的权值也要赋
        arcs[i][j] = weight;
        arcs[j][i] = weight;
        // 无向图
        // arcs[i][j] = arcs[j][i] = 1;
        // 有向网
        // arcs[i][j] = weight;

        // 边的个数+1
        this.arcNum++;
    }

    /**
     * @description 根据顶点名称找到对应的下标
     * @date 2023/8/27 14:51
     * @param vexs 顶点数组
     * @param v 顶点名称
     * @return int
     */
    public int findIndex(Object[] vexs, Object v) {
        for (int i = 0; i < vexs.length; i++) {
            if (vexs[i] == v) return i;
        }
        return -1;
    }

    /**
     * @description 深度优先遍历算法
     * @date 2023/8/29 22:22
     * @param v 访问的顶点下标
     * @return void
     */
    public void DFS(int v) {
        // 访问当前顶点
        System.out.println(vexs[v]);
        // 更改访问记录值
        visited[v] = true;
        // 访问邻接顶点
        for (int i = 0; i < vexs.length; i++) {
            // 该邻接顶点没有 被访问过
            if (((int) arcs[v][i]) != Integer.MAX_VALUE && !visited[i]) {
                // 递归访问
                DFS(i);
            }
        }
    }
}

(2)广度优先遍历算法

从图的某一结点出发,首先依次访问该结点的所有邻接点v1、v2、…vn ,在按这些顶点被访问的先后次序依次访问与他们相邻接的所有未被访问的顶点。

重复此过程,直到所有顶点均被访问为止!

image-20230829223510230

算法演示

利用邻接表+队列实现广度优先遍历算法

广度优先算法其实和树的层次遍历有些类似,都是一层一层的遍历,因此我们仍然利用 队列 来实现。

以上面那个图为例,求出它的邻接表,如下图所示:

image-20230830221422220

初始化访问数组:

image-20230830221647744

初始化队列:

image-20230830221717224

1、假设我们从 v1 开始,v1 没有被访问,那么将 v1 结点对应的下标入队,同时标记为已访问,标记完,出队进行访问。

image-20230830222210197

2、访问完 v1,通过邻接表,找到 与 v1 相连接弧的下标为 1,2 ,对应的结点 v2,v3 ,v2、v3没有被访问, 将 v2,v3的下标入队。此时队列的情况:

image-20230830223734594

3、入队之后,首先判断 v2 是否被访问过,发现没有则进行标记,然后将 v2 出队访问

image-20230830224148256

4、v2 出队之后,继续寻找与 v2 相邻接的弧,通过邻接表发现有:0,3,4 对应的结点为:v1、v4、v5,发现 v1 被访问了,v4、v5没有被访问,那么将 v4,v5的下标入队。此时队列情况:

image-20230830224555694

5、入队之后,v3没有被访问,进行标记,然后将v3出队访问

image-20230830224803678

6、不断执行上面的操作:找到出队结点的邻接弧 —— 若没有访问过则入队 —— 进行标记 —— 出队访问,直到队列为空。

代码实现

顶点结构

// 定义顶点结构
class VNode {
    // 顶点信息
    Object data;
    // 指向第一条边顶点的指针
    ArcNode firstarc;

    @Override
    public String toString() {
        return "VNode{" +
                "data=" + data +
                ", firstarc=" + firstarc +
                '}';
    }
}

边顶点存储结构

// 边/弧 顶点类型
class ArcNode {
    // 边顶点的索引位置
    int adjvex;
    // 下一个边顶点的地址
    ArcNode nextarc;
    // 顶点信息
    Object info;

    @Override
    public String toString() {
        return "ArcNode{" +
                "adjvex=" + adjvex +
                ", nextarc=" + nextarc +
                ", info=" + info +
                '}';
    }
}

图的存储结构

public class ALGraph {

    // 存储所有顶点的数组
    VNode[] vertices;
    // 顶点数、边数
    int vexNum, arcNum;
    // 辅助数组,记录顶点是否被访问
    boolean[] visited;

    // 初始化 vnodes==顶点集合
    public ALGraph(Object[] vnodes) {
        this.vexNum = vnodes.length;
        this.vertices = new VNode[this.vexNum];
        this.arcNum = 0;
        // 将头顶点赋值,指向第一个边为null
        for (int i = 0; i < this.vexNum; i++) {
            VNode vNode = new VNode();
            vNode.data = vnodes[i];
            vNode.firstarc = new ArcNode();
            this.vertices[i] = vNode;
        }
        // 初始化访问数组
        visited = new boolean[this.vexNum];
        Arrays.fill(visited, false);
    }

    // 生成邻接表 v1 —— v2
    public void createALGraph(Object v1, Object v2, int weight) {
        // 找到俩个顶点的位置
        int i = findIndex(v1);
        int j = findIndex(v2);
        // 生成新的边顶点
        ArcNode arcNode = new ArcNode();
        arcNode.adjvex = j;
        arcNode.nextarc = vertices[i].firstarc;
        arcNode.info = weight;
        vertices[i].firstarc = arcNode;

        // 由于是无向网,反向也得连接
        ArcNode arcNode1 = new ArcNode();
        arcNode1.adjvex = i;
        arcNode1.nextarc = vertices[j].firstarc;
        arcNode1.info = weight;
        vertices[j].firstarc = arcNode1;
    }

    /**
     * @description 根据顶点名称找到对应的下标
     * @date 2023/8/27 14:51
     * @param v 顶点名称
     * @return int
     */
    public int findIndex(Object v) {
        for (int i = 0; i < vertices.length; i++) {
            if (vertices[i].data == v) return i;
        }
        return -1;
    }

广度优先遍历算法实现

LinkedList 为双向循环的队列,addLast 将元素插入队尾(入队),poll 获取对头元素并删除(出队)

  /**
     * @description 广度优先遍历
     * @date 2023/8/29 22:52
     * @param
     * @return void
     */
    public void BFS(int v) {
        // 使用LinkedList模拟循环队列
        LinkedList<Integer> queue = new LinkedList<>();
        // 修改当前顶点访问状态
        visited[v] = true;
        // 将当前顶点插入队尾
        queue.addLast(v);

        while (!queue.isEmpty()) {
            // 出队
            Integer w = queue.poll();
            // 找到w顶点的第一条弧
            ArcNode firstarc = vertices[w].firstarc;
            // 访问
            System.out.println(vertices[w].data);
            // 循环找到与w顶点相邻接的弧
            while (firstarc.nextarc != null) {
                // 弧结点的下标
                int adjvex = firstarc.adjvex;
                // 判断是否访问过
                if (!visited[adjvex]) {
                    // 没有访问过,直接入队
                    queue.addLast(adjvex);
                    // 标记
                    visited[adjvex] =  true;
                }
                // 移动下一个弧的结点
                firstarc = firstarc.nextarc;
            }
        }
    }

四、图的应用

1、构造最小生成树

生成树: 所有顶点均由边连接在一起,但不存在回路的图

image-20230831143709148

特点

  • 生成树的顶点个数与图的顶点个数相同
  • 生成树是图的极小连通子图,去掉一条边则非连通
  • 一个有n个顶点的连通图的生成树有 n-1 条边,反之则不一定
  • 在生成树中再加一条边必然形成回路

无向图的生成树

我们可以利用图的遍历生成最小生成树,将访问结点走过的边加到生成树当中

image-20230831144535336

最小生成树: 给定一个无向网络,在该网的所有生成树中,使得各边权值之和最小的那棵生成树称为该网的最小生成树
也叫最小代价生成树

image-20230831145014503

最小生成树的典型用途

欲在n个城市间建立通信网,则n个城市应铺n-1条线路

但因为每条线路都会有对应的经济成本,而n个城市最多有n(n-1)/2条线路,那么,如何选择n-1条线路,使总费用最少?

此问题我们就可以转化为求最小生成树,n个城市看做n个顶点,线路看做边,经济成本看做权值。

MST性质

构造最小生成树的算法很多,其中多数算法都利用了MST的性质

MST 性质: 设N =(V E) 是一个连通网,U 是顶点集V的一个非空子集。若边(u,v) 是一条具有最小权值的边,其中u∈U,v∈V-U,则必存在一棵包含边(u,v)的最小生成树

image-20230831150601189

MST性质解释

在生成树的构造过程中,图中n个顶点分属两个集合:

  • 已落在生成树上的顶点集: U
  • 尚未落在生成树上的顶点集: V-U

接下来则应在所有连通U中顶点和V-U中顶点的边中选取权值最小的边

image-20230831151037365

普利姆算法(Prim)

算法思想

设 N=(V,E) 是连通图,TE是N上最小生成树中边的集合。

初始令 U={u0} ,u0 ∈ V,TE={}

  • 在所有 U 与 V-U 的边中,找到一条权值最小的边 (u0,v0)
  • 将 (u0,v0) 加入到 TE中, 同时 v0 并入 U
  • 重复上面操作,直到 U=V位置,则 T=(V, TE) 为N的最小生成树

算法演示

1、假设从 V1 开始,U = {V1} V-U= {V2,V3,V4,V5,V6} 相连且权值最小的边为 V1-V3,将 V3 加入到 U, 并且将边加入到 TE 中。

U = {V1, V3},TE={(V1,V3)}

image-20230831153405949

2、U = {V1,V3} V-U= {V2,V4,V5,V6} 相连且权值最小的边为 V3-V6

将 V6 加入到 U 中,相应的边加入到 TE 中。

U = {V1, V3,V6},TE={(V1,V3), ((V3,V6))}

image-20230831153337674

3、重复以上操作,直到 U = V

image-20230831153430406

克鲁斯卡尔算法(Kruskal)

设连通网 N=(V,E),令最小生成树初始状态为只有 n个顶点而无边的非连通图T=(V,{})每个顶点自成一个连通分量

image-20230831155901689

在E中选取代价最小的边(对边按权值大小升序),若该边依附的顶点落在T中不同的连通分量上(即:不能形成环)则将此边加入到 T中;否则,舍去此边,选取下一条代价最小的边

image-20230831160809445

俩种算法的比较

image-20230831161513434

2、最短路径

典型用途:交通网络的问题一从甲地到地之间是否有公路连通?在有多条通路的情况下,哪一条路最短?

那么交通网络用有向图来表示,顶点表示地点,俩个地点的连通用弧表示,权值表示俩地之间的距离。

问题抽象: 在有向网中A点(源点)达 B 点(终点)的多条路径中,寻找一条各边权值之和最小的路径,即最短路径

最短路径与最小生成树不同,路径上不一定包含 n个顶点,也不-定包含 n-1条边

第一类问题: 俩点间的最短路径——迪杰斯特拉(Dijkstra)算法

image-20230831162503544

第二类问题: 某源点到其他各个顶点的最短路径——通常使用弗洛伊德—Floyd算法求解

image-20230831162757912

迪杰斯特拉(Dijkstra)

迪杰斯特拉(Dijkstra)算法是典型最短路径算法,用于计算一个结点到其他结点的最短路径。 它的主要特点是以起始点为中心向外层层扩展(广度优先搜索思想),直到扩展到终点为止。

算法步骤

  • 初始化: 先找出从源点V0,到各终点V的直达路径 (V0,Vk),即通过一条弧到达的路径
  • 选择: 从这些路径中找出一条长度最短的路径 (V0,U)
  • 更新: 然后对其余各条路径进行适当调整
    • 若在图中存在弧 (U,Vk) ,且 (V0,U) + (U,Vk) < (V0,Vk)则以路径 V0,U,Vk) 代替 (V0,Vk)
    • 依此类推在调整后的各条路径中,再找长度最短的路径

迪杰斯特拉 (Dijkstra)算法: 按路径长度递增次序产生最短路径

1、把V分成俩组

  • S:已求出最短路径的顶点的结合
  • T = V-S : 尚未确定最短路径的顶点集合

2、将 T 中顶点按最短路径递增的次序加入到 S 中

  • 保证从源点到 S 中各顶点的最短路径都不大于 源点到T中任何顶点的最短路径长度。

算法演示

初始 S= {V0} , T = {其余顶点}

T中顶点对应的距离值用辅助数组D存放,若有直达的路径,则存储距离值,若不存在则为∞

image-20230901153005912

image-20230901153025559

1、从 V0 开始,找到能够直达的顶点有:V2、V1、V6、V4,其余顶点的距离皆为 ∞

image-20230901162301930

2、在这些直达路径中,找到最短的路径的顶点,加到 S 中。此时 S = {V0 , V2}

T = {V1 , V3 , V4 , V5, V6} ,

image-20230901163939942

3、 加入 V2 顶点后,以 V2 顶点作为中间顶点,若V0 距离这些顶点是否变短了,就更新表中的距离。

以 V3 为例 ,未加入 V2 之前是∞ ,加入之后,路径为 13 ,就更新表中的路径为 13…以此类推

在更新后的路径中,继续找最短的路径,并将顶点加入到 S 中

此时 S = {V0 ,V1 , V2} , T = { V3 , V4 , V5, V6} ,

image-20230901164446131

3、重复上面的操作,直到 S=V,找到所有的顶点即可。

弗洛伊德(Floyd)

求所有顶点间的最短路径:

方法一:每次以一个顶点为源点,重复执行 Dijkstra 算法

方法二: 弗洛伊德算法

算法思想

逐个顶点试探

从vi到vj的所有可能存在的路径中,选出一条长度最短的路径

案例演示

image-20230904114142677

1、初始时设置一个邻接矩阵表示图,存在弧为 权值,否则为 ∞ ,对角线为0

image-20230904113332752

2、逐步试着在原直接路径中增加中间顶点,若加入中间顶点后路径变短,则修改之; 否则,维持原值。所有顶点试探完毕,算法结束

(1)加入A顶点, A——B、C都没有变化,B——A、C也没有变化,C —— B,由于A点的加入,变为可达,C-A-B,路径为7,更新表中的权值

image-20230904113811350

(2)加入B顶点后,A-C路径为 11,加入B顶点,A-B-C 路径为 6,比原来路径小,更新表中的权值

image-20230904114116831

(3)加入C后,B-A 变成了 B-C-A,路径变为5

image-20230904114342852

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1010525.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

6、如何将 Flink 中的数据写入到外部系统(文件、MySQL、Kafka)

目录 1、如何查询官网 2、Flink数据写入到文件 3、Flink数据写入到Kafka 4、Flink数据写入到MySQL 1、如何查询官网 官网链接&#xff1a;官网 2、Flink数据写入到文件 传送门&#xff1a;Flink数据写入到文件 3、Flink数据写入到Kafka 传送门&#xff1a;Flink数据写入…

2023-9-14 最长公共子序列

题目链接&#xff1a;最长公共子序列 #include <iostream> #include <algorithm>using namespace std;const int N 1010;int n, m; char a[N], b[N]; int f[N][N];int main() {cin >> n >> m;cin >> a 1 >> b 1;for(int i 1; i < n…

30WSIP网络音柱

SV-7042VP 30WSIP网络音柱 一、描述 SV-7042VP是我司的一款SIP网络音柱&#xff0c;具有10/100M以太网接口&#xff0c;将网络音源通过自带的功放和喇叭输出播放&#xff0c;可达到功率30W。SV-7042VP作为SIP系统的播放终端&#xff0c;可用于需要广播播放的场所&#xff0c;…

用node开发微信群聊机器人第③章

▍PART 序 怎么https请求第三方api 有哪些免费的、付费的第三方api 记得先去看前几章&#xff0c;看明白了再来看本章&#xff0c;不然你会一脸懵。点合集》#程序员干货 记得先把小程序加个收藏不然等下你要调试的时候找不到》“程序员实用资源” ▍PART 正文 来&#xff…

NeuroFlash:AI文章写作与生成工具

【产品介绍 ​ 】 名称 NeuroFlash 上线时间 2015 具体描述 Neuroflash是一款基于人工智能的文本和图像生成器&#xff0c;可以帮助用户快速创建高质量的内容。Neuroflash拥有超过100种短文和长文的文本类型&#xff0c;涵盖了各种营销场景和需求。只需要输入简单的指示&#…

定时任务框架-xxljob

1.定时任务 spring传统的定时任务Scheduled&#xff0c;但是这样存在这一些问题 &#xff1a; 做集群任务的重复执行问题 cron表达式定义在代码之中&#xff0c;修改不方便 定时任务失败了&#xff0c;无法重试也没有统计 如果任务量过大&#xff0c;不能有效的分片执行 …

stackqueuepriority_queue

目录 一、容器适配器 二、deque 1、deque的相关函数 2、关于deque 3、deque的底层实现 4、deque的设计缺陷 5、结论 三、stack 1、stack的相关函数 2、stack相关函数使用 3、stack模拟实现 四、queue 1、queue的相关函数 2、queue相关函数使用 3、queue的模拟实…

2023-9-14 数字三角形

题目链接&#xff1a;数字三角形 #include <iostream> #include <algorithm>using namespace std;const int N 510, INF 1e9;int n; int a[N][N]; int f[N][N];int main() {cin >> n;for(int i 1; i < n; i )for(int j 1; j < i; j )cin >> …

书剑宠物疫苗接种管理软件操作教程

【软件简介】 书剑宠物疫苗接种管理软件是一款宠物疫苗接种管理的工具&#xff0c;适合宠物诊所使用。具有动物主人建档、宠物疫苗接种登记管理、每日提醒、打印疫苗接种通知卡、自定义短信提醒模板等完善的功能。 另外本软件的特色是同时具有手机网页版功能&#xff0c;手机…

全球十大优质炒黄金交易APP平台排名(信息汇总)

由于近些年全球通货膨胀高企&#xff0c;炒黄金交易平台越来越受到人们的关注。然而&#xff0c;如何在众多的平台中选择适合自己的平台&#xff0c;却是一个值得思考的问题。 随着移动互联网的快速发展&#xff0c;越来越多的投资者更倾向于通过手机APP来炒黄金。本文将为大…

Contents:帮助公司为营销目的创建内容

【产品介绍】 名称 Contents上线时间 2017年5月 具体描述 Contents是一家提供基于人工智能的内容生成平台的企业&#xff0c;可以帮助用户在各种网站和工具中使用最先进的机器学习模型&#xff0c;实现视频编辑、图像生成、3D建模等内容创作。【团队介绍…

“掌握技巧,轻松调整视频时长:打造完美短视频分享“

在今天的高速信息时代&#xff0c;视频已成为我们传递信息和娱乐的主要方式之一。但有时候&#xff0c;视频的时长可能会超出我们的预期&#xff0c;或者我们希望在特定的时间内呈现内容。那么&#xff0c;如何调整视频的时长呢&#xff1f;下面&#xff0c;我们将分享几种简单…

聚精品,通全球 2024中国(杭州)国际电商物流包装产业展览会四月隆重开幕

2024中国&#xff08;杭州&#xff09;国际电商物流包装产业展览会 2024年4月12-14日 | 杭州国际博览中心 同期举办&#xff1a;2024长三角快递物流供应链与技术装备展览会&#xff08;杭州&#xff09; 2024中国&#xff08;杭州&#xff09;国际数字物流技术与应用展览会 展会…

Bearly:基于人工智能的AI写作文章生成工具

【产品介绍】 名称 Bearly 具体描述 Bearly是一个AI人工智能内容创作工具。你可以用Bearly来阅读、写作、创作&#xff0c;提高你的效率。包括使用Bearly来生成网页的摘要、标题、关键点&#xff0c;也可以用Bearly来生成创意内容、艺术图片、文案编辑等。帮助你克…

git工具下载和安装

(1)从git官网下载安装包 然后安装 https://git-scm.com/downloads (2)git 学习参考官方的资料 https://git-scm.com/book/en/v2

PADS出GERBER时 焊盘丢失、焊盘变形问题

一、PCB设计软件PADS出GB焊盘丢失问题解决方案 PCB设计软件PADS出GB焊盘丢失原因&#xff1a;PADS斜角焊盘在输出gerber时需要填充&#xff0c;当填充的线过大(比焊盘宽度大)就会出现焊盘丢失。 问题解决方法&#xff1a;输出光绘时将“填充线宽”改小。 二、PCB设计软件PADS出…

单链表和双链表

单链表和双链表 单链表&#xff1a;只有一个指向下一节点的指针 --> 单向读取 双链表&#xff1a;既有指向下一节点的指针&#xff0c;也有指向上一节点的指针&#xff0c;可以通过此向前查找 单链表和双链表的反转&#xff1a;逆序 整个链表逆序、部分链表逆序&#…

【数据分享】2001-2022年我国省市县镇四级的逐月降水量数据(免费获取/Shp/Excel格式)

气象数据在日常研究中非常常用&#xff0c;之前我们分享过来自国家青藏高原科学数据中心提供的1901-2022年1km分辨率逐月降水栅格数据以及基于该数据处理而得到的1901-2022年1km分辨率的逐年降水栅格数据&#xff08;可查看之前的文章获悉详情&#xff09;&#xff01; 本次我…

设置伙伴(buddy)-给窗口控件增加快捷键

在官方教程或者很多qt程序中经常看到能使用全键盘操作软件&#xff0c;那么QT creator也支持了这一特性&#xff0c;就是使用设置伙伴来实现的。 我们可以在设计界面按照如下几步实现&#xff1a; 先放置label 再放置一个lineEdit控件。 这个时候我们就可以开始伙伴绑定的步骤…

初试占比70%,计算机招生近200人,安徽理工大学考情分析

安徽理工大学 考研难度&#xff08;☆&#xff09; 内容&#xff1a;23考情概况&#xff08;拟录取和复试分析&#xff09;、院校概况、23专业目录、23复试详情、各专业考情分析、各科目考情分析。 正文980字&#xff0c;预计阅读&#xff1a;3分钟 2023考情概况 安徽理工大…