图的常见算法汇总笔记(包括图的DFS,BFS,Dijkstra算法,Floyed算法,Prim算法,Kruskal算法)

22种图分析算法介绍 图分析算法,以图论为驱动,进行算法优化,结合应用工程,业务形态研究,不同领域场景模拟不同网络结构,通过自由刻画网络图形关系,验证结构合理性,如边的有向和无向及权重,从而辅助分析图形关系、图结构分析、网络结构分析等研究工作。 阅读详情
AI摘要

本文总结了图的常见算法,包括图的表示、实现、DFS与BFS遍历、Dijkstra算法求最短路径、Floyd算法优化、Prim算法求最小生成树和Kruskal算法。实例演示了如何解决实际问题,如修路最优化和连通图构建。

图的常见算法汇总笔记

1. 图的概念:

图是一种数据结构,其中结点可以具有零个或多个相邻元素。两个结点之间的连接称为边。 结点也可以称为顶点。如图:

在这里插入图片描述

其中:
在这里插入图片描述

2. 图的表示方式:

第一种表示方式:二维数组表示(邻接矩阵):

在这里插入图片描述

第二种表示方式:链表表示(邻接表):

在这里插入图片描述

注意:

  • 我们一般在算法题中采用邻接矩阵的形式存储图的顶点,边的信息

3. 图的实现:

我们这里使用邻接矩阵创建一个简单的图,里面包括图的一些基本的API:

  • 插入一个顶点的方法
  • 添加一条边的方法
  • 获取两个顶点之间的权值的方法
  • 获取边的个数的方法
  • 图的邻接矩阵的显示输出打印的方法
  • 获取顶点的个数方法
public class Graph {
    //定义图的几个内部属性:
    private List<Integer> vertexList; //存储顶点的集合(这里的顶点为int类型: 顶点0,顶点1,...)
    private int[][] edges; //用来保存图对应的邻接矩阵
    private int edgeOfNums; //记录边的条数
    
    /*
    * 传入参数n : 表示图的顶点的个数
    * */
    public Graph(int n) {
        this.vertexList = new ArrayList<Integer>(n);
        this.edges = new int[n][n];
        edgeOfNums = 0;
    }
    
    //添加顶点的API
    public void addVertex(Integer vertex) {
        vertexList.add(vertex);
    }
    
    //添加边的方法: 参数1: 顶点1 参数2: 顶点2 参数3 : 顶点1和顶点2之间的权值
    public void insertEdge(int v1,int v2,int weight) {
        //注意: 我们这里定义的图为无向图,所以添加的是两条边
        edges[v1][v2] = weight;
        edges[v2][v1] = weight;
        edgeOfNums++;
    }
    
    //获取两个图之间的权值
    public int getWeight(int v1,int v2) {
        return edges[v1][v2];
    }
    
    //获取图的边的条数
    public int getEdgeOfNums() {
        return edgeOfNums;
    }
    
    //图的邻接矩阵的输出打印方法
    public void showGraph() {
        for (int[] edge : edges) {
            System.out.println(Arrays.toString(edge));
        }
    }
    
    //获取顶点的个数
    public int getVertexNum() {
        return vertexList.size();
    }
}

上面的无向图的测试如下所示:

public static void main(String[] args) {
    Graph graph = new Graph(5);
    graph.addVertex(0);
    graph.addVertex(1);
    graph.addVertex(2);
    graph.addVertex(3);
    graph.addVertex(4);
    graph.insertEdge(0,1,3);
    graph.insertEdge(1,2,4);
    graph.insertEdge(2,3,5);
    graph.insertEdge(3,4,6);
    System.out.println("无向图的邻接矩阵打印如下:");
    graph.showGraph();
    System.out.println("无向图的边的条数为: " + graph.getEdgeOfNums());
    System.out.println("无向图的边的顶点个数为: " + graph.getVertexNum());
}

测试结果:
无向图的邻接矩阵打印如下:
[0, 3, 0, 0, 0]
[3, 0, 4, 0, 0]
[0, 4, 0, 5, 0]
[0, 0, 5, 0, 6]
[0, 0, 0, 6, 0]
无向图的边的条数为: 4
无向图的边的顶点个数为: 5

4. 图的两种遍历方式(面试热点)----即图的DFS遍历和BFS遍历

基础框架

DFS: Depth First Search 深度优先搜索,简称深搜
BFS:Breadth First Search 广度优先搜索,简称广搜

DFS算法框架
def dfs(n){                         //可以描述阶段的状态
	if(valid) {收集结果,返回}	        //出口条件
	if(pruning) return;             //剪枝,这一步是为了加快回溯过程,降低程序执行时间
	for(i:1~p){                      //选择该阶段的所有决策
		选择可行决策;                   //剪枝的一种 
		add;						  //标记已访问该点
		DFS(n+1);                     //进入下一阶段
		recover;                      //回溯还原
	}
}
BFS算法框架
def bfs(){
    q.push(head);//一般为q这种优先队列来处理bfs问题
    while(!q.empty()){
        temp=q.front;//弹出元素
        q.pop(); 
        if(temp为目标状态)	输出解 
        if(temp不合法)		continue;
        if(temp合法)		q.push(temp+Δ);
    }
}

一般也会设置一些visit[] 来记录元素访问与否,做剪枝操作.
举个例子,假如你在学校操场,老师叫你去国旗那集合,你会怎么走? 假设你是瞎子,你看不到周围,那如果你运气差,那你可能需要把整个操场走完才能找到国旗。这便是盲目式搜索,即使知道目标地点,你可能也要走完整个地图。 假设你眼睛没问题,你看得到国旗,那我们只需要向着国旗的方向走就行了,我们不会傻到往国旗相反反向走,那没有意义。 这种有目的的走法,便被称为启发式的

左图为BFS,右图为DFS:

在这里插入图片描述

图的DFS实现:

图的DFS思想

我们从一个顶点一直往深处进行搜索遍历,走到不能再往下走以后,我们就回退到上一步,直到找到解或者将所有顶点走完

图的DFS算法步骤
  • 第一步: 我们访问起始顶点
  • 第二步: 若当前访问顶点的邻接顶点有未访问的顶点,我们就任选一个进行访问.如果不存在未访问的邻接顶点,我们就回退到最近访问的顶点,直到与起始顶点想通的所有顶点全部访问完
  • 第三步: 若途中还有顶点未被访问(说明这个图不是整体连通的,即为非连通图),则我们再选择一个顶点作为起始顶点,重复步骤2
//方式1: 使用递归实现
public class DFS_Test {
    public static void main(String[] args) {
        //定义一个图结构: 1:表示连通,0:表示不连通
        int[][] graph = {
                { 0, 1, 1, 0, 0 },
                { 0, 0, 1, 0, 1 },
                { 0, 0, 0, 0, 0 },
                { 1, 1, 0, 0, 1 },
                { 0, 0, 1, 0, 0 }
        };
        //使用递归法进行图的DFS遍历
        DFSRecursion(graph);

    }

    private static void DFSRecursion(int[][] graph) {
        //获取顶点的个数,即为graph的长度
        int vertex_nums = graph.length;
        //定义一个访问数组: true[该顶点已被访问] fasle[该顶点未被访问]
        boolean[] visited = new boolean[vertex_nums];
        //为了防止该图不是连通图,我们在整个dfs遍历外加了一个for循环,如果是连通图则不需要!
        for (int i = 0; i < vertex_nums; i++) {
            if (!visited[i]) {
                dfs(graph,i,visited);
            }
        }
    }

    private static void dfs(int[][] graph, int vertex, boolean[] visited) {
        //第一步: 将该顶点标记为已访问
        visited[vertex] = true;
        //输出打印该顶点信息
        System.out.print(vertex + "-->");
        int n = graph.length;
        for (int i = 0; i < n; i++) {
            //找到与vertex相邻的未被访问的顶点,进行dfs递归访问
            if (!visited[i] && graph[vertex][i] == 1) {
                dfs(graph,i,visited);
            }
        }
    }
}
//方式2:使用栈实现
public class DFS_Test1 {
    public static void main(String[] args) {
        int[][] graph = {
                { 0, 1, 1, 0, 0 },
                { 0, 0, 1, 0, 1 },
                { 0, 0, 0, 0, 0 },
                { 1, 1, 0, 0, 1 },
                { 0, 0, 1, 0, 0 }
        };
        //使用栈实现图的DFS遍历
        DFSByStack(graph);
    }

    private static void DFSByStack(int[][] graph) {
        //首先我们需要定义一个栈结构
        Stack<Integer> stack = new Stack<>();
        int vertex_num = graph.length;
        boolean[] visited = new boolean[vertex_num];
        //同理这里的for循环是为了防止该图不是连通图
        for (int i = 0; i < vertex_num; i++) {
            if (!visited[i]) {
                stack.push(i);
                visited[i] = true;
                //定义一个变量
                boolean hasNext;
                System.out.print(i + "-->");
                while (!stack.empty()) {
                    //获取栈顶元素
                    int vertex= stack.peek();
                    //hasNext表示是否有新的顶点入栈,没有就弹出栈顶元素,有的话进行下一次循环
                    hasNext = false;
                    for (int j = 0; j < vertex_num; j++) {
                        if (!visited[j] && graph[vertex][j] == 1) {
                            stack.push(j);
                            visited[j] = true;
                            hasNext = true;
                            System.out.print(j + "-->");
                            break;
                        }
                    }
                    //如果没有下一个顶点,就回溯,将栈顶元素删除
                    if (!hasNext) {
                        stack.pop();
                    }
                }
            }
        }
    }
}

图的BFS实现:

图的BFS思想:

从某一个顶点开始遍历,将其的邻接顶点遍历完,然后再任选一个邻接点,把与之邻接的未被遍历完的顶点走完,如此反复直到遍历完所有的顶点结束.

图的BFS算法步骤:
  • 第一步: 访问指定的起始顶点
  • 第二步: 访问当前顶点的邻接顶点有未被访问的顶点,并将其放在队列中
  • 第三步: 删除队列的首节点,访问当前队列的队首顶点,重复步骤二,直到队列为空
  • 若途中还有顶点未被访问,则再任意选择一个未被访问的顶点作为起始顶点,重复步骤二(主要是针对图是非连通图)
public class BFS_Test {
    public static void main(String[] args) {
        int[][] graph = {
                { 0, 1, 0, 1, 0 },
                { 0, 0, 1, 0, 1 },
                { 0, 0, 0, 0, 0 },
                { 1, 1, 0, 0, 1 },
                { 0, 0, 1, 0, 0 }
        };
        BFSByQueue(graph);
    }

    //使用队列实现BFS
    private static void BFSByQueue(int[][] graph) {
        //定义一个队列
        Queue<Integer> queue = new LinkedList<>();
        int n = graph.length;
        boolean[] visited = new boolean[n];

        //为了预防不是连通图,我们定义一个for循环,如果该图是连通图的话则不需要for循环
        for (int i = 0; i < n; i++) {
            if (!visited[i]) {
                //将该顶点入队列
                queue.add(i);
                visited[i] = true;
                System.out.print(i + "-->");
                while (queue.size() != 0) {
                    //从队列头部弹出顶点
                    int vertex = queue.poll();
                    //遍历所有与vertex相邻的点,依次加入队列中,并遍历他们
                    for (int j = 0; j < n; j++) {
                        if (!visited[j] && graph[vertex][j] == 1) {
                            queue.add(j);
                            visited[j] = true;
                            System.out.print(j + "-->");
                        }
                    }
                }
            }
        }

    }
}

5. 关于图的常见的算法1(DIjkstra算法)

图的常见问题(最短路径问题)

最短路径问题: 描述的是存在以下顶点和每一个顶点和其他顶点之间的距离,要求我们求出某一个顶点(原点)到达另一个顶点(终点)的最短路径,即原点存在很多路径到达终点,但是肯定存在一条路径,使得沿着这条路径的权值总和最小.

DIjkstra算法介绍

迪杰斯特拉算法是由荷兰计算机科学家狄克斯特拉于1959 年提出的,因此又叫Dijkstra算法。是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。

迪杰斯特拉算法主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。迪杰斯特拉算法采用的是贪心策略,将Graph中的节点集分为最短路径计算完成的节点集S和未计算完成的节点集T,每次将从T中挑选V0->Vt最小的节点Vt加入S,并更新V0经由Vt到T中剩余节点的更短距离,直到T中的节点全部加入S中,它贪心就贪心在每次都选择一个距离源点最近的节点加入最短路径节点集合。

迪杰斯特拉算法只支持非负权图,它计算的是单源最短路径,即单个源点到剩余节点的最短路径,时间复杂度为O(n²).

最短路径问题

在以下有向图G=(V,E)中,假设每条边的长度为w[i],求出顶点V0到其他各个顶点的最短距离

在这里插入图片描述

我们采取的策略是贪心思想:,思路图解如下图所示:

第一步: 标记顶点0位原点,设置其已被访问

在这里插入图片描述

第二步: 找到顶点0到顶点3的路径距离最短,将顶点3标记为已访问,并同步更新此时顶点0到其他顶点的距离;

在这里插入图片描述

第三步:此时找到顶点0到顶点1的路径最短,将顶点1标记为已访问,并同步更新此时顶点0到其他顶点的路径

在这里插入图片描述

重复上面的步骤:第四步

在这里插入图片描述

第五步:

在这里插入图片描述

第六步:

在这里插入图片描述

第七步:

在这里插入图片描述

第八步: 此时所有的顶点已经都被访问,原点到其他顶点的最短路径距离已经求出,算法结束

在这里插入图片描述

代码实现:

public class FindMinPathByDijkstra {
    private static int MaxValue = 10000;
    public static void main(String[] args) {

        Scanner sc = new Scanner(System.in);
        //获取顶点的个数和边的条数
        int vertex_num = sc.nextInt();
        int edge_num = sc.nextInt();
        //定义图的邻接矩阵
        int[][] matrix = new int[vertex_num][vertex_num];
        //初始化邻接矩阵:除了本身顶点到本身顶点的距离为0,其他赋值为MaxValue
        for (int i = 0; i < vertex_num; i++) {
            for (int j = i + 1; j < vertex_num; j++) {
                if (matrix[i][j] == 0) {
                    matrix[i][j] = MaxValue;
                    matrix[j][i] = MaxValue;
                }
            }
        }
        //输出每条边的信息
        for (int i = 0; i < edge_num; i++) {
            System.out.println("请输入第" + (i + 1) + "条起始边: ");
            int source = sc.nextInt();
            System.out.println("请输入第" + (i + 1) + "条目标边: ");
            int target = sc.nextInt();
            System.out.println("输入权值: ");
            int weight = sc.nextInt();
            matrix[source][target] = weight;
        }

        //输入最短路径的原点
        int source = sc.nextInt();
        //使用Dijkstra算法进行该原点到各个顶点之间的最短路径
        dijkstra(matrix,source);
    }

    private static void dijkstra(int[][] matrix, int source) {
        //定义paths数组: 记录原点到达每一个顶点的最短路径
        int[] paths = new int[matrix.length];
        //初始化一下
        for (int i = 0; i < matrix.length; i++) {
            paths[i] = matrix[source][i];
        }
        //定义visited数组: 记录达到该顶点的最短路径是否已经求出: 初始化为false
        boolean[] visited = new boolean[matrix.length];

        //我们初始化原点为已经访问,并且到达自身的最短路径为[已经在前面初始化过了]
        visited[source] = true;

        //我们找到此时原点到其余顶点最短距离
        for (int i = 0; i < matrix.length; i++) {
            //记录最短路径长度和最短路径的顶点
            int min = Integer.MAX_VALUE;
            int index = -1;
            for (int j = 0; j < matrix.length; j++) {
                if (!visited[j] && paths[j] < min) {
                    min = paths[j];
                    index = j;
                }
            }
            //如果找到一条最短路径[source -> j],将其置为已访问
            if (index != -1)
                visited[index] = true;

            //此时更新最短距离
            for (int k = 0; k < matrix.length; k++) {
                if (!visited[k] && min + matrix[index][k] < paths[k]) {
                    //此时更新paths[k]的值
                    paths[k] = min + matrix[index][k];
                }
            }
        }

        //输出打印原点到其他顶点的最短路径距离
        for (int i = 0; i < matrix.length; i++) {
            if (i != source) {
                if (paths[i] == MaxValue) {
                    System.out.println("顶点" + source + "到顶点" + i + "不可达!");
                }else {
                    System.out.println("顶点" + source + "到顶点" + i + "最短路径距离为:" + paths[i]);
                }
            }
        }
    }
}

注意:

  • Dijkstra算法适用于非负权值的图,对于权值为负的图不再适用
  • Dijkstra算法一般求解有向无环图

6. 关于图的常见的算法2(Floyd算法)

Floyd算法介绍:

Floyd算法求解图的最短路径问题是基于动态规划的角度,我们从任意顶点i到任意顶点j的最短路径不外乎两种可能:

  • 第一种是直接从顶点i到顶点j
  • 第二种是顶点i经过若干个顶点到达顶底j

那么我们假设dis(i,j)即为顶点i到顶点j的最短路径的距离,这对于每一个顶点k来说 :

如果我们判断:
dis(i,k)+dis(k,j)<dis(i,j)是否成立 dis(i,k) +dis(k,j) < dis(i,j) 是否成立 dis(i,k)+dis(k,j)<dis(i,j)是否成立
如果成立,则证明从顶点i到达顶点k,再从顶点k到达顶点j的路径更加地短,我们便设置 :
dis(i,j)=dis(i,k)+dis(k,j) dis(i,j) = dis(i,k) +dis(k,j) dis(i,j)=dis(i,k)+dis(k,j)
这样一来遍历完所有的顶点k,此时dis(i,j)中记录的即为所有顶点i到顶点j的最短路径距离

Floyd算法思路

  • 我们首先从任意一条边开始,所有两点之间的距离即为边的权,如果两点之间没有边相连,则权为无穷大,我们进行这样的初始化工作
  • 对于每一对顶点(i,j),我们判断是否存在顶点k,使得顶点i到顶点k,再到顶点j比已知的路径更加地短,如果存在则选择更新它
  • Floyd算法的时间复杂度为 : O(n ^ 3)

以下图为例,对Floyd 算法进行演示:

在这里插入图片描述

代码实现

//这里是基于无向图的实现
public class FindMinPathByFloyd {
    private static int MaxValue = 10000;
    private static int[][] dis;
    public static void main(String[] args) {
        int[][] matrix = {
                {0, 5, 7, MaxValue, MaxValue, MaxValue, 2},
                {5, 0, MaxValue, 9, MaxValue, MaxValue, 3},
                {7, MaxValue, 0, MaxValue, 8, MaxValue, MaxValue},
                {MaxValue, 9, MaxValue, 0, MaxValue, 4, MaxValue},
                {MaxValue, MaxValue, 8, MaxValue, 0, 5, 4},
                {MaxValue, MaxValue, MaxValue, 4, 5, 0, 6},
                {2, 3, MaxValue, MaxValue, 4, 6, 0}
        };
        //使用Dijkstra算法进行各个顶点之间的最短路径
        floyed(matrix);
        show(dis);
    }

    //输出最短路径的方法
    private static void show(int[][] dis) {
        for (int i = 0; i < dis.length; i++) {
            for (int j = 0; j < dis.length; j++) {
                System.out.print("(顶点" + i + "到" + "顶点" + j + "最短路径距离为: " + dis[i][j] + ") ");
            }
            System.out.println();
        }
    }

    private static void floyed(int[][] matrix) {
        //这里为了不改变原有的邻接矩阵的值,我们额外定义一个dis数组: 记录边的信息,可以不定义,直接拿matrix进行操作
        dis = matrix;
        int len = 0;
        for (int k = 0; k < dis.length; k++) { //k为中间的顶点的下标
            for (int i = 0; i < dis.length; i++) { //起始顶点i
                for (int j = 0; j < dis.length; j++) { //目标顶点j
                    len = dis[i][k] + dis[k][j];
                    if (len < dis[i][j]) {
                        dis[i][j] = len; //更新顶点i到顶点j的最短路径距离
                    }
                }
            }
        }
    }
}

输出结果:
(顶点0到顶点0最短路径距离为: 0) (顶点0到顶点1最短路径距离为: 5) (顶点0到顶点2最短路径距离为: 7) (顶点0到顶点3最短路径距离为: 12) (顶点0到顶点4最短路径距离为: 6) (顶点0到顶点5最短路径距离为: 8) (顶点0到顶点6最短路径距离为: 2) 
(顶点1到顶点0最短路径距离为: 5) (顶点1到顶点1最短路径距离为: 0) (顶点1到顶点2最短路径距离为: 12) (顶点1到顶点3最短路径距离为: 9) (顶点1到顶点4最短路径距离为: 7) (顶点1到顶点5最短路径距离为: 9) (顶点1到顶点6最短路径距离为: 3) 
(顶点2到顶点0最短路径距离为: 7) (顶点2到顶点1最短路径距离为: 12) (顶点2到顶点2最短路径距离为: 0) (顶点2到顶点3最短路径距离为: 17) (顶点2到顶点4最短路径距离为: 8) (顶点2到顶点5最短路径距离为: 13) (顶点2到顶点6最短路径距离为: 9) 
(顶点3到顶点0最短路径距离为: 12) (顶点3到顶点1最短路径距离为: 9) (顶点3到顶点2最短路径距离为: 17) (顶点3到顶点3最短路径距离为: 0) (顶点3到顶点4最短路径距离为: 9) (顶点3到顶点5最短路径距离为: 4) (顶点3到顶点6最短路径距离为: 10) 
(顶点4到顶点0最短路径距离为: 6) (顶点4到顶点1最短路径距离为: 7) (顶点4到顶点2最短路径距离为: 8) (顶点4到顶点3最短路径距离为: 9) (顶点4到顶点4最短路径距离为: 0) (顶点4到顶点5最短路径距离为: 5) (顶点4到顶点6最短路径距离为: 4) 
(顶点5到顶点0最短路径距离为: 8) (顶点5到顶点1最短路径距离为: 9) (顶点5到顶点2最短路径距离为: 13) (顶点5到顶点3最短路径距离为: 4) (顶点5到顶点4最短路径距离为: 5) (顶点5到顶点5最短路径距离为: 0) (顶点5到顶点6最短路径距离为: 6) 
(顶点6到顶点0最短路径距离为: 2) (顶点6到顶点1最短路径距离为: 3) (顶点6到顶点2最短路径距离为: 9) (顶点6到顶点3最短路径距离为: 10) (顶点6到顶点4最短路径距离为: 4) (顶点6到顶点5最短路径距离为: 6) (顶点6到顶点6最短路径距离为: 0) 

7. 关于图的常见的算法3(Prim算法)

Prim算法介绍

Prim算法主要用于求解最小生成树问题

何谓最小生成树?

给定一个带权值的无向连通图,如何选取一棵生成树,使树上所有边上权值的总和为最小,这叫最小生成树
我们说:存在N个顶点,最少一定有N-1条边,包含全部顶点,并且使得这N-1条边都在图中,最终这N - 1条边的权值之和最小!

Prim算法生成最小生成树的思想

普利姆(Prim)算法求最小生成树,也就是在包含n个顶点的连通图中,找出只有(n-1)条边包含所有n个顶点的连通子图,也就是所谓的极小连通子图
设G=(V,E)是连通网,T=(U,D)是最小生成树,V,U是顶点集合,E,D是边的集合 
第1步: 若从顶点u开始构造最小生成树,则从集合V中取出顶点u放入集合U中,标记顶点v的visited[u]=true
第2步: 集合U中顶点ui与集合V-U中的顶点vj之间存在边,则寻找这些边中权值最小的边,但不能构成回路,将顶点vj加入集合U中,将边(ui,vj)加入集合D中,标记visited[vj] = true
重复步骤2,直到顶点u和顶点v相等,即说明所有的顶点都被标注为访问过,此时D中存在n - 1条边

最小生成树的实际问题: 修路问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-984tzhV7-1635410870444)(.\imgs\41.png)]

 胜利乡有7个村庄(A, B, C, D, E, F, G) ,现在需要修路把7个村庄连通各个村庄的距离用边线表示(权值),比如 A – B 距离 5公里,一共存在十条边
 问:如何修路保证各个村庄都能连通,并且总的修建公路总里程最短?

代码实现

public class FindMinCreatedTreeByPrim {
    private static int MaxValue = 10000; //定义MaxValue表示两个顶点之间不连通
    private static int[][] weight; //接收邻接矩阵信息
    private static int n; //顶点的个数
    private static char[] vertexs = {'A','B','C','D','E','F','G'};  //初始化顶点
    public static void main(String[] args) {
        //获取顶点的个数
        n = vertexs.length;
        //输入已知的边的信息
        int[][] matrix = {
                {MaxValue,5,7,MaxValue,MaxValue,MaxValue,2},
                {5,MaxValue,MaxValue,9,MaxValue,MaxValue,3},
                {7,MaxValue,MaxValue,MaxValue,8,MaxValue,MaxValue},
                {MaxValue,9,MaxValue,MaxValue,MaxValue,4,MaxValue},
                {MaxValue,MaxValue,8,MaxValue,MaxValue,5,4},
                {MaxValue,MaxValue,MaxValue,4,5,MaxValue,6},
                {2,3,MaxValue,MaxValue,4,6,MaxValue},
        };
        //选择构建最小生成树的起始顶点
        int start = 0;
        System.out.println("7个村庄(A,B,C,D,E,F,G),以顶点" + vertexs[start] +"作为起始顶点构建的最小生成树为 :");
        prim(start,matrix);   
    }

    private static void prim(int start, int[][] matrix) {
        weight = matrix;
        //定义一个访问数组: true表示该顶点已经被访问
        boolean[] visited = new boolean[n];
        //标记当前顶点已被访问
        visited[start] = true;
        //定义两个顶点的下标,以及一个初始权值
        int u = -1;
        int v = -1;
        int minWeight = MaxValue;
        for (int k = 1; k < n; k++) {
            //n个顶点,即存在n - 1条边:故k∈[1,n -1]
            for (int i = 0; i < n; i++) {   //i表示已经被访问的顶点
                for (int j = 0; j < n; j++) {   //j表示未被访问的顶点
                    if (visited[i] && !visited[j] && weight[i][j] < minWeight) {
                        //更新已经访问过的结点和未访问过的结点间的权值最小的边
                        minWeight = weight[i][j];
                        u = i;
                        v = j;
                    }
                }
            }
            //此时找到一条最小的边
            System.out.println("边<" + vertexs[u] + "," + vertexs[v] + "> 权值:" + minWeight);
            //将此前未访问的顶点v标记为已被访问
            visited[v] = true;
            //重新将minWeight更新为初始值
            minWeight = MaxValue;
        }
    }
}

输出结果:

7个村庄(A,B,C,D,E,F,G),以顶点A作为起始顶点构建的最小生成树为 :
边<A,G> 权值:2
边<G,B> 权值:3
边<G,E> 权值:4
边<E,F> 权值:5
边<F,D> 权值:4
边<A,C> 权值:7

8. 关于图的典型常见算法(Kruskal算法)

介绍

克鲁斯卡尔算法(Kruskal)也是用来求加权连通图的最小生成树的算法

基本思想就是在已知的n个顶点,按照权值从小到大的顺序选择n -1边,并保证这n - 1条边不构成回路

算法实现步骤

首先我们需要构建含n个顶点的图然后根据权值从小到大从连通图中选择边加入到图中,并使得我们选择的边不构成回路,直到选择n -1边结束

我们观察下面n个顶点构成的连通图,现在从中选择n-1条边,构成一颗连通子图,并使得连通子图中n-1条边权值之和最小:

在这里插入图片描述

我们使用Kruskal算法的步骤如下所示:

在这里插入图片描述

实际问题:

我们依旧选择上面的修路问题,使用Kruskal算法进行求解:

 胜利乡有7个村庄(A, B, C, D, E, F, G) ,现在需要修路把7个村庄连通各个村庄的距离用边线表示(权值),比如 A – B 距离 5公里,一共存在十条边
 问:如何修路保证各个村庄都能连通,并且总的修建公路总里程最短?

算法图解

修路的顶点和以及顶点之间的边的信息如下所示:

在这里插入图片描述

排序以后,权值最小的边为A–G(2)

在这里插入图片描述

然后是B–G(3)

在这里插入图片描述

接着是E–G(4)

在这里插入图片描述

D–F(4)

在这里插入图片描述

此时A–B会形成环,选择E–F(5)

在这里插入图片描述

同理,G–F(6)会形成环路,选择A–C(7),此时边的个数为6 = 顶点个数 - 1,此时构成我们需要的最小生成树

在这里插入图片描述

代码实现:

public class FindMinCreatedTreeByKruskal {
    private static int MaxValue = 10000; //定义MaxValue表示两个顶点之间不连通
    private static int[] parent;
    private static int n; //顶点的个数
    private static int edgeNum; //边的条数
    private static char[] vertexs = {'A','B','C','D','E','F','G'};  //初始化顶点
    public static void main(String[] args) {
        //获取顶点的个数
        n = vertexs.length;
        edgeNum = 10; //根据题意,一共存在十条边
        //输入已知的边的信息: 无向图
        int[][] matrix = {
                {MaxValue,5,7,MaxValue,MaxValue,MaxValue,2},
                {5,MaxValue,MaxValue,9,MaxValue,MaxValue,3},
                {7,MaxValue,MaxValue,MaxValue,8,MaxValue,MaxValue},
                {MaxValue,9,MaxValue,MaxValue,MaxValue,4,MaxValue},
                {MaxValue,MaxValue,8,MaxValue,MaxValue,5,4},
                {MaxValue,MaxValue,MaxValue,4,5,MaxValue,6},
                {2,3,MaxValue,MaxValue,4,6,MaxValue},
        };
        //选择构建最小生成树的起始顶点
        Edge[] edges = new Edge[edgeNum];
        int index = 0;
        for (int j = 0; j < matrix.length; j++) {
            for (int i = 0; i <= j; i++) {
                if (matrix[i][j] != MaxValue) {
                    edges[index++] = new Edge(i,j,matrix[i][j]);
                }
            }
        }
        //通过Kruskal算法获取最小生成树
        List<Edge> minTree =  kruskal(n,edges);
        System.out.println("7个村庄(A,B,C,D,E,F,G),使用Kruskal算法构建的最小生成树为 :");
        for (Edge edge : minTree) {
            System.out.println(vertexs[edge.start] + "-->" + vertexs[edge.end] + ": " + edge.weight);
        }

    }

    //获取最下生成树的方法
    private static List<Edge> kruskal(int n, Edge[] edges) {
        //首先定义一个结果集
        List<Edge> ans = new ArrayList<>();
        //我们使用并查集 + 归并排序的思想来实现Kruskal算法
        parent = new int[n];
        for (int i = 0; i < n; i++) {
            parent[i] = i;
        }
        //首先我们使用归并排序对edges数组进行排序: 按照权值的大小进行排序
        sortEdges(edges,0,edges.length - 1);
        int cnt = 0;  //定义一个计数器: 总共n个顶点,最小生成树需要n -1条边
        for (int i = 0; i < edges.length; i++) {
            int x = edges[i].start;
            int y = edges[i].end;
            if (!isConnected(x,y)) {
                ans.add(edges[i]);
                union(x,y);
                ++cnt;
            }
            if (cnt == n - 1)
                break;
        }
        return ans;
    }

    //归并排序
    private static void sortEdges(Edge[] edges, int left, int right) {
        if (left < right) {
            int mid = (left + right) / 2;
            //向左递归
            sortEdges(edges,left,mid);
            //向右递归
            sortEdges(edges,mid + 1,right);
            //合并方法
            merge(edges,left,mid,right);
        }
    }

    //合并的方法
    private static void merge(Edge[] edges, int left, int mid, int right) {
        //定义一个临时数组
        Edge[] temp = new Edge[edges.length];
        int index = left;
        int t = left;
        int next = mid + 1;
        while (left <= mid && next <= right) {
            if (edges[left].weight < edges[next].weight) {
                temp[index++] = edges[left++];
            }else {
                temp[index++] = edges[next++];
            }
        }

        while (left <= mid) {
            temp[index++] = edges[left++];
        }
        while (next <= right) {
            temp[index++] = edges[next++];
        }

        //最后将temp数组元素拷贝到edges数组中
        while (t <= right) {
            edges[t] = temp[t];
            ++t;
        }
    }


    //获取根节点的方法
    public static int find(int x) {
        if (x != parent[x]) {
            parent[x] = find(parent[x]);
        }
        return parent[x];
    }

    //合并两个节点的方法
    public static void union(int x, int y) {
        int Root_X = find(x);
        int Root_Y = find(y);
        if (Root_X == Root_Y) {
            return;
        }
        parent[Root_X] = parent[Root_Y];
    }

    //判断是否成环的方法
    public static boolean isConnected(int x, int y) {
        int Root_X = find(x);
        int Root_Y = find(y);
        return Root_X == Root_Y;
    }
}


//定义一个边类
class Edge {
    int start;  //起始顶点
    int end;    //结束顶点
    int weight; //权值大小

    public Edge(int start, int end, int weight) {
        this.start = start;
        this.end = end;
        this.weight = weight;
    }
}

输出结果:

7个村庄(A,B,C,D,E,F,G),使用Kruskal算法构建的最小生成树为 :
A-->G: 2
B-->G: 3
E-->G: 4
D-->F: 4
E-->F: 5
A-->C: 7
【数据结构与算法】图(Graph)【详解】 图(Graph)是由顶点的有穷非空集合V ( G ) 和顶点之间边的集合E ( G )组成,通常表示为: G = ( V , E ) ,其中,G表示个图,V 是图G中顶点的集合,E是图G中边的集合。若V = { v 1 , v 2 , . . . , v n } ,则用∣ V ∣表示图G中顶点的个数,也称图G的阶,E = { ( u , v ) ∣ u ∈ V , v ∈ V } ,用∣ E ∣表示图G中边的条数。注意:线性表可以是空表,树可以是空树,但图不可以是空图。 阅读详情

相关推荐

图的基本算法

图的存储 1.邻接矩阵—————稠密图的存储(存边多) 2.邻接表 —————稀疏图的存储(存顶点多) 3.十字链表—————邻接表的升级版 4.邻接多重表————邻接矩阵的升级版 主要讲一下邻接矩阵和邻接表为存储结构的算法(STL库的vector实现) DFS:Depth-First-Search 邻接矩阵版: const int MAXV = 1000; const int INF = 100...

qq_35050438的博客 1589

基本的图算法

基本的图算法主要是两个方面:图的表示和图的搜索。我们主要通过邻接链表和邻接矩阵对图进行表示,但是在图算法更重要的是图的搜索,图的搜索指的是系统化的跟随图中的边来访问图中的每个节点,我们可以通过图的搜索算法发现图的结构。或者换个方面想图的算法就是通过图的搜索得到图的结构,所以图的算法就是对基本的搜索加以优化,因此图的搜索技巧是整个图算法的核心。 图的表示 对于一个图,我们有两种标准表示方法表示。一种表示法将图作为邻接链表的组合,另一种表示法则将图作为邻接矩阵来看待,两种方法既可以表示有向图也可以表示无向图

zhousiyuan0515的博客 3613

python算法与数据结构:06图

图及相关概念 我们前面讲过了树这种非线性表数据结构,今天我们要讲另一种非线性表数据结构,图(Graph)。和树比起来,这是一种更加复杂的非线性表结构。 我们知道,树中的元素我们称为节点,图中的元素我们就叫作顶点(vertex)。从我画的图中可以看出来,图中的一个顶点可以与任意其他顶点建立连接关系。我们把这种建立的关系叫作边(edge)。 叫作顶点的度(degree),就是跟顶点相连接的边的条数。 在图中画一条从A到B的带箭头的边,来表示边的方向。我们把这种边有方向的图叫作“有向图”。以此类推,我们把边没有方

lvqiqi93的博客 797

常见的算法技巧——图算法

这篇文章介绍了常见的算法技巧——图算法。

qq_45902301的博客 1509

用LT1166+MOS管打造实验室级恒流源:闭环反馈与推挽驱动的深度调优

本文详细介绍了基于LT1166电流模式控制器与MOS管推挽架构的实验室级恒流源设计方法。通过闭环反馈调优和动态响应优化,实现带宽达100kHz、纹波低于0.1%的高性能恒流输出,适用于精密仪器校准和半导体测试等场景。重点解析了系统架构设计、MOS管选型策略以及频率响应测试等关键技术要点。

weixin_29290379的博客 203

数模笔记_多变量最优化计算之牛顿法

Date: 2_22 Name: Guo Yehao Theme: Calculation in Optimality with multiple variables Reference: 数学建模方法与分析(华章) 在上一节中,我们讨论多变量最优化计算中的随机搜索算法,它的精度并不是众多算法中最高的。在精度问题上,有的时候问题本身就带有明显的误差性,以至于我们不能将其忽略,比如上一节消防站部署的问题,我们在区域划分时就有了误差,因此我们并不需要对结果要求很高的精确度,所以用一些比较粗略的算法就可以.

weixin_45358177的博客 2415

floyed算法 c语言,[算法] 图论

图的表示邻接矩阵int G [maxv][maxv]或 > G数组元素存储连接与否或连接权重的信息如果有多个边的相关属性,例如多个权重,则可以声明边的结构体 struct edge替换上面的int如果有多个节点的相关属性,例如节点的颜色、访问时间、估计最短距离等,只需再单独声明|V|大小的一维数组即可邻接表vector G[maxv]for(int i = 0; i < E; i++)...

weixin_35804557的博客 305

用MCP扩展Cursor的5种神奇用法:从文件操作到网页抓取实战

本文深入探讨了如何利用MCP(Model Context Protocol)扩展Cursor AI编程助手,解锁五种高效实用场景。从文件系统操作、网页内容抓取,到数据库查询、Figma设计稿对接,乃至构建自定义MCP服务器,文章通过实战案例展示了如何将Cursor从代码编辑器升级为能理解并操作工作环境的智能助手,显著提升开发效率与自动化水平。

weixin_29231027的博客 315

【算法与数据结构】之图论

文章目录图的深度优先遍历图的广度优先遍历寻路算法 图的表示方法: 邻接矩阵:适合表示稠密图 邻接表:适合表示稀疏图   图的深度优先遍历 本博客均以下图为例,给定无向图,和其邻接表adj = {0:[1,2,5,6],1:[0],2:[0],3:[4,5],4:[3,5,6],5:[0,3,4],6:[0,4]},据此写出遍历算法。 图的深度优先遍历复杂度: 稀疏图(邻接表):O(V+...

aaon22357的博客 400

U8采购入库单CO接口开发实战:加载、新增与删除操作详解

本文详细解析了用友U8采购入库单CO接口的开发实战,重点介绍了加载、新增与删除三大核心操作的实现方法。通过C#代码示例和实际项目经验,帮助开发者高效处理采购入库单,提升企业供应链管理效率。文章还涵盖了环境配置、异常处理、性能优化等关键技巧,适合U8二次开发人员参考。

weixin_33462927的博客 373

YOLO11-Seg优化:卷积变体系列篇 | 动态蛇形卷积(Dynamic Snake Convolution) | ICCV2023

动态蛇形卷积(Dynamic Snake Convolution),增强微小特征提取能力,引入到YOLO11-Seg,与C3k2结合实现二次创新

会AI的学姐 370

搜索与图论

对于这道题目而言,我们只需要对于每一个点,删除之后,求出连通块中点的最大值,然后在这些最大值中取一个较小的,递归的话,就是要知道题目的整体流程就好,整体框架是怎么样的。**如果想要增加输出路径这个过程,很简单,开一个数组就好了,新增一个prev二维数组,表示每个点由前面哪一个点到达的,所以这个二维数组。用深度优选搜索可以实现输出路径,用宽搜如何实现输出路径,宽搜的话,就比较复杂一些,但是也是。这个题目无论从哪个点开始搜索,都是没有问题的,因为这是一个无向图,从哪一个边开始都是ok的。

qq_50273182的博客 257

把 ABAP 接到外部世界的几条路, 从 Web Service 到 RFC、BAPI 与 IDoc 的完整理解

摘要: ABAP 的连接能力是其长期成为企业核心的关键,不仅支持通用协议(HTTP、SOAP、XML),还保留了 SAP 特有的技术(RFC、BAPI、IDoc)。Web Service 是跨系统通信的主轴,依赖 SOAP/WSDL 实现契约化协作,而 Enterprise Service 更强调业务语义的复用与治理。经典技术如 RFC 和 BAPI 因高效稳定仍广泛使用,尤其适合紧耦合场景。ABAP 通过分层工具箱(如代理生成、服务注册)实现灵活集成,平衡新旧技术,满足多样化的企业连接需求。 (字数:15

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 138

图论的整理

图论初赛前刘老师草草地过了一遍,那时听得比较迷糊。复赛后详详细细地学习了图论,理解较为深入了一些。图论比较重要的内容分为:dfs/bfs遍历,最短路,生成树,割点割边强连通分量,欧拉回路。图的存储结构:邻接矩阵,邻接表,边表。邻接矩阵书写较为方便,但是寻找邻接点的速度较慢,适用于点数小于5000的图。邻接表寻找邻接点速度快,适用于点数大于5000的图。(无向图中边表要乘2)边表只是单纯的记录边的信...

qq_39692612的博客 251

【STM32系统及仿真】基于STM32+蓝牙设计的智能窗帘系统__资料编号8

本项目设计并实现了一种基于STM32单片机的智能窗帘系统,集成了光敏传感器、温湿度传感器、蓝牙模块和OLED显示屏,实现了窗帘的自动调节、环境监测和远程控制功能。系统采用STM32F103C8T6作为主控制器,通过光敏传感器检测环境光强,温湿度传感器监测室内环境,蓝牙模块实现与手机APP的通信,用户可以通过APP远程控制窗帘的开合状态。系统在检测到环境光照强度或温度超出设定范围时,可自动调节窗帘的开合,以达到节能和提高舒适度的目的。

嵌入式阿齐Archie 2万+

ARM Linux工控板+C# .NET Core:倍福ADS协议轻量化工业控制(附跨平台部署代码)

某自动化设备厂的倍福PLC控制系统曾依赖传统x86工控机:单台设备需配备1台Intel i5工控机(成本约8000元),功耗30W+,且Windows系统稳定性受工业环境干扰(如电磁辐射导致蓝屏)。更棘手的是,设备需部署在产线边缘(空间狭小),x86工控机的体积(4U机架)和散热需求(风扇噪音)难以适配;而多设备部署时,高硬件成本和高功耗导致年运维成本超20万元。

这里只有干货:从 Modbus 通信到 MES 对接,从 YOLO 训练到工控机部署,带你搞定工业软件开发全流程。 954

寒假集训后记

寒假集训结束一段时间来,开学以来一直忙着补考,额额,补考太痛苦来,这学期一定要好好学,再也不想补考了。0 0泪目。 从这周开始专注训练,发现寒假的坑不仅没有弥补,反而因为没有及时弥补而遗忘更多,悔恨补考啊,太耽误训练了!!! 不管怎么说,抱怨不会前进,还是继续努力办法总是有的,因此,便制定弥补计划! 月球美容计划!必须在省赛之前补坑完成! 月球美容计划分前后两期,前期: 每星期一,三

顺便A道题 1137

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

算法之经典图算法

图介绍 图:是一个顶点集合加上一个连接不同顶点对的边的集合组成。定义规定不允许出现重复边(平行边)、连接到顶点自身的边(自环),定义了一个简单图。 自环:连接到顶点自身的边。 平行边:连接同一对顶点的两条边,含有平行边的图称为多重图。 顶点的度数:依附于它的边的总数。 顶点连通:两个顶点之间存在一条连接双方的路径。 连通图:任意一个顶点都存在一条路径到达另一个...

一个菜鸟的博客 2万+

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

图算法介绍

图算法有助于我们理解关联数据。理解网络及其内部联系可以为洞察和创新提供不可思议的潜力。 图算法特别适用于理解结构和揭示高度关联的数据集中模式。目前,大数据汇集、混合和动态更新的需求非常强烈,图算法有助于体现数据的关联性和交互性,针对关系进行更复杂的分析,并可以为AI提供丰富的上下文信息。

weixin_44906759的博客 1608

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

算法学习篇7——图算法

算法类别典型应用场景DFS/BFS社交网络好友推荐、网页爬虫、迷宫求解、游戏AI路径findingDijkstra路由选择(OSPF协议)、交通导航系统、网络延迟优化交通流量预测、网络路由表生成、物流中心选址最小生成树电网设计、通信网络建设、油气管道路线规划拓扑排序任务调度系统、课程安排、软件构建依赖管理、流水线工序安排强连通分量社交网络社区发现、编译器优化(过程间分析)、电子电路设计图算法是计算机科学中的核心内容,掌握这些基础算法不仅有助于解决实际问题,也是学习更高级算法的基础。

a19884792066的博客 2187

算法学习5——图算法

Dijkstra算法用于计算单源最短路径,即从一个起始节点到所有其他节点的最短路径。它适用于非负权重的图,通过贪心策略逐步找到最短路径。

零度°C的博客 1395

图的常见算法

转自https://www.zybuluo.com/pastqing/note/318392#%E9%82%BB%E6%8E%A5%E8%A1%A8adjacency-matrix 一、图的基本知识 图是节点集合的一个拓扑结构,节点之间通过边相连。图分为有向图和无向图。有向图的边具有指向性,即A - B仅表示由A到B的路径,但不意味着B可以连到A。与之对应的,无向图的每一条边都表示一条双向路...

勤学如春起之苗,不见其增,日有所长! 辍学如磨刀之石,不见其损,日有所亏! 677

图 算法 大总结

图大总结

m0_46335449的博客 3127

图的十大经典算法

图的遍历是指从图中的某一个结点出发,按照某种搜索方法沿着图中的边对图中的所有结点访问一次且仅访问一次。注意到树是一种特殊的图,所以树的遍历实际上也可以看作是一种特殊的图的遍历。图的遍历根据访问结点的顺序,主要分为有两种算法:广度优先搜索(Breadth-First-Search)和深度优先搜索(Depth-First-Search)。 广度优先搜索(BFS) 算法思想 首先访问起始结点v,接着由v...

石中路的博客 2万+

数据结构算法--图详解

图G由顶点集合V和边集合E组成,记为。:有向图和无向图。,如果,则G’为G的子图。:n(n - 1)条边的有向图。:n(n - 1) / 2条边的无向图。:图的每条边带有权值。:无向图,一条边的两点互为邻接点。:顶点v到顶点v'的边的权值和为路径长度。:无向图G,如果顶点v到顶点v'存在路径,则两顶点连通。G是一个连通图, G中的极大连通子图为连通分量。:有向图G,如果顶点v到顶点v'存在路径,则两顶点强连通。强连通图只有一个连通分量。

jy18817386398的博客 1551
上一篇: LeetCode每日一题之869.重新排序得到2的幂
下一篇: 并查集算法最全最详细讲解
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值