算法.图论-建图/拓扑排序及其拓展

文章目录

    • 建图的三种方式
      • 邻接矩阵
      • 邻接表
      • 链式前向星
    • 拓扑排序
      • 拓扑排序基础原理介绍
      • 拓扑排序步骤解析
      • 拓扑排序模板leetcode-课程表
    • 拓扑排序拓展
      • 食物链计数
      • 喧闹与富有
      • 并行课程

建图的三种方式

我们建图的三种方式分别是邻接矩阵, 邻接矩阵, 链式前向星

邻接矩阵

假设我们的点的个数为N个, 我们就把他们的下标依次标为1, 2 ,…, 然后在一个矩阵表上进行边的添加, 比如我要在点2和点4之间添加一个权值为5的边, 那么我就在矩阵matrix[2][4] = 5即可, 唯一注意的就是如果是无向图, 就需要把matrix[4][2]位置也设置为5, 所以这个方法的弊端是十分明显的, 就是消耗的空间过大, 所以我们在大厂的笔试或者是比赛, 不会用这种方式进行建图, 下面是邻接矩阵法的代码实现

public class day23 {
    public static void main(String[] args) {
        int[][] edges = {{1,2,5},{5,3,1},{1,4,4},{2,5,2}};
        //测试一下使用邻接矩阵法建图
        GraphUseMatrix graphUseMatrix = new GraphUseMatrix();
        graphUseMatrix.build(5);
        graphUseMatrix.directGraph(edges);
        //graphUseMatrix.unDirectGraph(edges);
        graphUseMatrix.showGraph();
    }
}

/**
 * 邻接矩阵建图的方法
 * 下面我们介绍的所用的图都是带权值的图, 不带权的更简单就不说了
 */
class GraphUseMatrix{
    //设置一个最大的点数(根据题意)
    private static final int MAXN = 11;
    //设置一个最大的边数(根据题意, 无向图 * 2)
    private static final int MAXM = 21;

    //构建一个当前的点数curn
    private static int curN = 0;
    //设置一个邻接的矩阵(大小就是(点数 + 1) * (点数 + 1), 这里我们保证是够用的)
    private static final int[][] matrix = new int[MAXN][MAXN];

    //初始化矩阵的方法(传入的点的数量)
    public void build(int n){
        curN = n;
        //清空矩阵的脏数据
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= n; j++){
                //把邻接矩阵中的数据刷为最大值(因为权值可能为0)
                matrix[i][j] = Integer.MAX_VALUE;
            }
        }
    }

    //添加边的方法
    private void addEdge(int from, int to, int weight){
        matrix[from][to] = weight;
    }

    //在有向带权图中添加边
    public void directGraph(int[][] edges){
        for(int[] edge : edges){
            addEdge(edge[0], edge[1], edge[2]);
        }
    }

    //在无向带权图中添加边
    public void unDirectGraph(int[][] edges){
        for(int[] edge : edges){
            addEdge(edge[0], edge[1], edge[2]);
            addEdge(edge[1], edge[0], edge[2]);
        }
    }

    //展示图的方式
    public void showGraph(){
        System.out.println("邻接矩阵法建图展示");
        for(int i = 1; i <= curN; i++){
            for(int j = 1; j <= curN; j++){
                if(matrix[i][j] == Integer.MAX_VALUE){
                    System.out.print("∞  ");
                }else{
                    System.out.print(matrix[i][j] + "  ");
                }
            }
            System.out.println();
        }
    }
}

上述代码的测试结果见下
在这里插入图片描述
在这里插入图片描述

测试结果也是很明显的, 证明我们之前的代码是没有问题的, 无向图总体是按照正对角线呈对称分布

邻接表

邻接表是一种动态的建图的方式, 关于大厂的笔试面试题, 邻接表以及完全够用了, 如果涉及到比赛内容的话, 我们会使用链式前向星建图法, 等会我们会介绍到这种算法, 说回来邻接表, 其实就是一个
ArrayList<ArrayList<int(无权)/int>的结构, 也就是顺序表套顺序表的结构, 假如我们要建立一个从点2到点4的边,如果不带权值, 我们就让外层顺序表2下标对应的顺序表添加一个元素4, 如果同时带有一个权值的话, 我们就添加一个数组(假设权值是8)我们就让2下标对应的顺序表添加一个[4,8]数组, 下面是邻接表的代码实现

public class day23 {
    public static void main(String[] args) {
        int[][] edges = {{1, 2, 5}, {5, 3, 1}, {1, 4, 4}, {2, 5, 2}};
//        //测试一下使用邻接矩阵法建图
//        GraphUseMatrix graphUseMatrix = new GraphUseMatrix();
//        graphUseMatrix.build(5);
//        graphUseMatrix.unDirectGraph(edges);
//        graphUseMatrix.showGraph();

        //测试一下邻接表法建图
        GraphUseList graphUseList = new GraphUseList();
        graphUseList.build(5);
        graphUseList.directGraph(edges);
        //graphUseList.unDirectGraph(edges);
        graphUseList.showGraph();
    }
}

/**
 * 邻接表建图的方法(是一种动态的结构)
 * 和邻接矩阵一样, 我们在这里介绍的都是带权的图
 */
class GraphUseList {

    //构建一个当前的点数
    private static int curN = 0;

    //邻接表的主体
    private static ArrayList<ArrayList<int[]>> list = new ArrayList<>();

    //初始化邻接表(传入一个点的数量)
    public void build(int n) {
        //设置当前的点数
        curN = n;
        //上来直接清空邻接表
        list.clear();
        //开始构建顺序表(新添加n+1个列表)
        for (int i = 0; i <= n; i++) {
            list.add(new ArrayList<>());
        }
    }

    //添加边的方法
    private void addEdge(int from, int to, int weight) {
        list.get(from).add(new int[]{to, weight});
    }

    //构建一个有向的图
    public void directGraph(int[][] edges) {
        for (int[] edge : edges) {
            addEdge(edge[0], edge[1], edge[2]);
        }
    }

    //构建一个无向的图
    public void unDirectGraph(int[][] edges) {
        for (int[] edge : edges) {
            addEdge(edge[0], edge[1], edge[2]);
            addEdge(edge[1], edge[0], edge[2]);
        }
    }

    //展示邻接表图的方式
    public void showGraph() {
        System.out.println("邻接表法建图展示");
        for (int i = 1; i <= curN; i++) {
            System.out.print("点" + i + "(邻点/权值) : ");
            for (int[] elem : list.get(i)) {
                System.out.print("[" + elem[0] + "," + elem[1] + "]");
            }
            System.out.println();
        }
    }
}

执行结果如下图所示
在这里插入图片描述
在这里插入图片描述

链式前向星

前两种建图的方式都有着明显的缺点, 第一个虽然是静态空间但是空间的大小过大, 第二个虽然使用的空间不是很大但是是一种动态的结果, 就会在时间上大打折扣, 所以我们就想要一种既可以是静态空间又可以做到省空间省时间的结构, 我们就需要下面的链式前向星建图法, 这个方法有点类似与前缀树的静态空间建树法(之前有)和链表头插法的结合, 下面我们分析一下建图的过程

准备过程 :
我们准备三个数组, 把每一个加入的边都设置一个编号(从1开始逐渐增加)

数组数组解释
head数组下标表示点的编号, head[i]表示这个点的’头边’的编号
next数组下标表示边的编号, next[i]表示这个边的下一条边的编号
to数组下标表示边的编号, to[i]表示这条边到达的点的编号
weight数组下标表示边的编号, weight[i]表示这条边的权值

具体的例子
假设此时我们准备了五个点, 然后执行4次加边的操作
head数组长度准备为 6 (5 + 1) , next, to, weight 数组的长度都准备 5 (4 + 1)
下面执行加边的过程

[3,1,2], 这条边编号为1, 出发点是3, 终点是1, 权值是2, 在head中插入头边head[3] = 1(这里就是用的头插法, 这里是首条边), next[1] = 0 (头边所以是0) , to[1] = 1, weight[1] = 2

代码实现如下

public class day23 {
    public static void main(String[] args) {
        int[][] edges = {{1, 2, 5}, {5, 3, 1}, {1, 4, 4}, {2, 5, 2}};
//        //测试一下使用邻接矩阵法建图
//        GraphUseMatrix graphUseMatrix = new GraphUseMatrix();
//        graphUseMatrix.build(5);
//        graphUseMatrix.unDirectGraph(edges);
//        graphUseMatrix.showGraph();

//        //测试一下邻接表法建图
//        GraphUseList graphUseList = new GraphUseList();
//        graphUseList.build(5);
//        graphUseList.unDirectGraph(edges);
//        graphUseList.showGraph();

        //测试一下链式前向星建图
        GraphUseLinkedStar graphUseLinkedStar = new GraphUseLinkedStar();
        graphUseLinkedStar.build(5);
        graphUseLinkedStar.unDirectGraph(edges);
        graphUseLinkedStar.showGraph();
    }
}


/**
 * 链式前向星建图法(静态的建图法)
 */
class GraphUseLinkedStar {
    //定义点最大值
    private static final int MAXN = 11;
    //定义边最大值
    private static final int MAXM = 22;
    //构建head数组(以点为准)
    private static final int[] head = new int[MAXN];
    //构建next数组(以边为准)
    private static final int[] next = new int[MAXM];
    //准备to数组(以边为主)
    private static final int[] to = new int[MAXM];
    //准备weight数组(以边为主)
    private static final int[] weights = new int[MAXM];
    //定义一下当前点的个数
    private static int curN = 0;
    //定义一个计数器用于给边编号
    private static int cnt = 0;

    //传入一个点数n用来初始化图结构
    public void build(int n){
        //更新计数器
        cnt = 1;
        //初始化当前节点个数
        curN = n;
        //清除head即可(这里不用重置to, weights, next)
        Arrays.fill(head, 1, n + 1, 0);
    }

    //添加边的方法(其实就是链表的头插法)
    private void addEdge(int from, int ton, int weight){
        next[cnt] = head[from];
        to[cnt] = ton;
        weights[cnt] = weight;
        head[from] = cnt++;
    }

    //构建一个有向带权图
    public void directGraph(int[][] edges){
        for(int[] edge : edges){
            addEdge(edge[0], edge[1], edge[2]);
        }
    }

    //构建一个无向带权图
    public void unDirectGraph(int[][] edges){
        for(int[] edge : edges){
            addEdge(edge[0],edge[1],edge[2]);
            addEdge(edge[1],edge[0],edge[2]);
        }
    }

    //展示图的方法(类似于链表的遍历)
    public void showGraph(){
        System.out.println("链式前向星建图展示");
        for(int i = 1; i <= curN; i++){
            System.out.print("点" + i + "(邻点/权值) : ");
            for(int ei = head[i]; ei != 0; ei = next[ei]){
                System.out.print("[" + to[ei] + "," + weights[ei] + "]");
            }
            System.out.println();
        }
    }
}

执行结果如下
在这里插入图片描述

在这里插入图片描述

拓扑排序

拓扑排序基础原理介绍

拓扑排序的存在条件是在一个有向且无环图中的排序, 拓扑排序在某种程度上反应的是一件事的执行的先后顺序, 请看下图

在这里插入图片描述
这张图中, 黑色的字符表示节点的名称, 蓝色的数字指的是该位置的入度是多少, 上面我们提到过, 拓扑排序的过程可以视为完成某一件事的先后顺序, 假设我们最终想要完成的任务是f, 那我们下面的字符的序列也就是完成最终事件的顺序
拓扑排序不是唯一的, 比如下面的图
在这里插入图片描述
在这张图中, 先完成a还是先完成b都是可以的, 所以产生的拓扑排序的情况就有两种, 这就说明拓扑排序的结果可能有多种, 只要满足每一个节点的前面所需要的节点都完成了就可以
拓扑排序的应用举例 :
比如在计算机编译程序的过程中, 编译一个程序需要另外的程序结果才能编译完成, 所以很自然的就会出现程序编译的先后顺序问题, 见下图, 左侧的表格是编译一个程序所需要的已经完成的编译结果, 右面是完成a的编译过程的图, 右下角的两串字符串都是完成的顺序, 这同样说明拓扑排序不是唯一的
在这里插入图片描述
拓扑排序要求一个图有向, 这个很好理解, 做事情的步骤肯定是有先后的顺序的, 而且要无环, 这个我们就拿上面的编译过程理解, 假如a的编译过程依赖b, b的编译过程依赖a, 那这不就是矛盾了吗, 两者互相依赖谁也完成不了

拓扑排序步骤解析

实现拓扑排序用的是零入度删除法, 需要用到队列(特殊状况下用小根堆), 核心就是删除0入度的节点和因为该节点所造成的入度影响
这就解释了上面的图为什么我们要进行入度的标记, 首先图解一下0入度删除法的步骤
在这里插入图片描述
最终组合出来的删除节点的步骤就是最终的拓扑排序的答案

拓扑排序模板leetcode-课程表

leetcode210-课程表题目链接
在这里插入图片描述

class Solution {
    //我们采用邻接表建图就已经够用了
    private static ArrayList<ArrayList<Integer>> list = new ArrayList<>();
    //课的最大数目
    private static final int MAXN = 2001;
    //同时定义一个入读表
    private static final int[] indegree = new int[MAXN];
    //当前的数据个数
    private static int curN = 0;

    public int[] findOrder(int numCourses, int[][] prerequisites) {
        build(numCourses);
        directGraph(prerequisites);
        //定义一个计数器看一看队列弹出了多少次(建议使用数组形式队列)
        int cntNum = 0;
        Queue<Integer> queue = new ArrayDeque<>();
        //首先遍历入度数组加入入度为0的节点
        for (int i = 0; i < numCourses; i++) {
            if (indegree[i] == 0) {
                queue.offer(i);
            }
        }

        //从队列中逐渐弹出元素进行判断
        int[] res = new int[numCourses];
        while (!queue.isEmpty()) {
            int index = queue.poll();
            //在返回数组中添加上该元素
            res[cntNum++] = index;
            //遍历其所属的列表删除这个元素造成的入度
            for(int elem : list.get(index)){
                if(--indegree[elem] == 0){
                    queue.offer(elem);
                }
            }
        }
        return cntNum == res.length ? res : new int[0];
    }

    //初始化图结构
    private static void build(int n) {
        //更新当前的数据个数
        curN = n;
        //更新顺序表
        list.clear();
        for (int i = 0; i < n; i++) {
            list.add(new ArrayList<>());
        }
        //更新入度表
        Arrays.fill(indegree, 0, n, 0);
    }

    //建图的主函数(本质是有向无权图), 建图的过程中同时完成入度的统计
    private static void directGraph(int[][] edges) {
        for (int[] edge : edges) {
            addEdge(edge[1], edge[0]);
            indegree[edge[0]]++;
        }
    }

    //插入边的函数
    private static void addEdge(int from, int to) {
        list.get(from).add(to);
    }
}

拓扑排序拓展

关于拓扑排序的最基础的用法我们在上一节就已经探讨过了, 这节课我们讨论的是拓扑排序的最重要的技巧, 就是根据拓扑排序逐段的向下推送消息, 其实有点类似于树形dp

食物链计数

在这里插入图片描述
上面我们仅仅介绍了一部分流程而已, 但是整体上的逻辑就是这样的, 建图之后跑拓扑排序, 然后计数即可
洛谷-食物链测试链接
代码实现如下, 使用的是链式前向星的建图方式, 然后注意一下比赛平台的IO方式即可

import java.util.*;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
import java.io.OutputStreamWriter;
import java.io.IOException;
//计算食物链的条数, 用的是一种向下推送的技巧(建图使用链式前向星建图法)
public class Main{

    private static final int MAXN = 5001;

    private static final int MAXM = 500001;

    private static final int[] head = new int[MAXN];

    private static final int[] next = new int[MAXM];

    private static final int[] to = new int[MAXM];

    private static final int[] indegree = new int[MAXN];

    private static final int[] ans = new int[MAXN];

    private static int resCnt = 0;

    private static int cnt = 0;

    private static final int MOD = 80112002;

    //拓扑排序时需要的队列我们就使用动态的结构模拟看看过不过
    private static final Queue<Integer> queue = new ArrayDeque<>();

    //初始化图的函数
    private static void build(int n){
        cnt = 1;
        resCnt = 0;
        Arrays.fill(head, 0, n + 1, 0);
        Arrays.fill(indegree, 0, n + 1, 0);
        Arrays.fill(ans, 0, n + 1, 0);
        queue.clear();
    }

    //添加边的函数(链式前向星的加边方式, 同时修改入度)
    private static void addEdge(int fro, int t){
        next[cnt] = head[fro];
        to[cnt] = t;
        head[fro] = cnt++;
        indegree[t]++;
    }

    //拓扑排序的过程
    private static void topoSort(int n){
        //首先添加0入度的点入队列(同时在ans中修改等待推送)
        for(int i = 1; i <= n; i++){
            if(indegree[i] == 0){
                queue.offer(i);
                ans[i] = 1;
            }
        }

        //开始进行排序的主逻辑
        while(!queue.isEmpty()){
            int po = queue.poll();
            if(head[po] == 0){
                //此时说明该点是末尾的节点, 直接计数
                resCnt = (ans[po] + resCnt) % MOD;
            }else{
                //此时继续链式前向星的遍历过程
                for(int ei = head[po]; ei > 0; ei = next[ei]){
                    ans[to[ei]] = (ans[to[ei]] + ans[po]) % MOD;
                    if(--indegree[to[ei]] == 0){
                        queue.offer(to[ei]);
                    }
                }
            }
        }
    }
    public static void main(String[] args) throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StreamTokenizer in = new StreamTokenizer(br);
        PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));

        //下面的while循环其实就是建图的过程
        while(in.nextToken() != StreamTokenizer.TT_EOF){
            int n = (int)in.nval;
            build(n);
            in.nextToken();
            int opSize = (int)in.nval;
            for(int i = 0; i < opSize; i++){
                in.nextToken();
                int fro = (int)in.nval;
                in.nextToken();
                int t = (int)in.nval;
                addEdge(fro, t);
            }
            //下面就去跑拓扑排序
            topoSort(n);
            out.println(resCnt);
        }
        
        out.flush();
        out.close();
        br.close();
    }
}

喧闹与富有

在这里插入图片描述
leetcode851.喧闹与富有题目链接
总体上说这个题没什么分析的难度, 就是简单的建出来图之后然后跑拓扑排序就可以了, 传递的信息就是最安静的人的下标, 注意初始化ans数组的时候就把ans数组中每个位置的值初始化为本身的下标(因为自己的安静值肯定是一个限制值)
代码实现如下

//使用链式前向星的建图方式
class LoudAndRich {
    
    private static final int MAXN = 501;

    private static final int MAXM = MAXN * (MAXN - 1) / 2;

    private static final int[] head = new int[MAXN];

    private static final int[] next = new int[MAXM];

    private static final int[] to = new int[MAXM];

    private static final int[] indegree = new int[MAXN];

    private static int cnt = 0;

    private static int curN = 0;

    private static final Queue<Integer> queue = new ArrayDeque<>();

    //初始化图的函数
    private static void build(int n){
        cnt = 1;
        curN = n;
        Arrays.fill(head, 0, n + 1, 0);
        Arrays.fill(indegree, 0, n + 1, 0);
        queue.clear();
    }

    //添加边的函数(同时统计入度)
    private static void addEdge(int fro, int t){
        next[cnt] = head[fro];
        to[cnt] = t;
        head[fro] = cnt++;
        indegree[t]++;
    }

    public int[] loudAndRich(int[][] richer, int[] quiet) {
        //建图
        int len = quiet.length;
        build(len);
        for(int[] edge : richer){
            addEdge(edge[0], edge[1]);
        }

        //初始化一个答案数组
        int[] ans = new int[len];
        for(int i = 0; i < len; i++){
            ans[i] = i;
        }
        //跑拓扑排序
        topoSort(ans, quiet);
        return ans;
    }

    private static void topoSort(int[] ans, int[] quiet){
        //首先找到0入度的点加入队列
        for(int i = 0; i < curN; i++){
            if(indegree[i] == 0){
                queue.offer(i);
            }
        }

        //遍历图进行ans数组的修改
        while(!queue.isEmpty()){
            int curPointer = queue.poll();
            for(int ei = head[curPointer]; ei > 0; ei = next[ei]){
                if(--indegree[to[ei]] == 0){
                    queue.offer(to[ei]);
                }
                ans[to[ei]] = quiet[ans[curPointer]] < quiet[ans[to[ei]]] ? ans[curPointer] : ans[to[ei]];
            }
        }
    }
}

并行课程

其实就是一个简单的拓扑排序, 建图的时候我们使用了一个新的辅助空间
代码实现如下


class Cource {

    private static final int MAXN = 50001;

    private static final int MAXM = 50001;

    private static final int[] head = new int[MAXN];

    private static final int[] next = new int[MAXM];

    private static final int[] to = new int[MAXM];

    private static final int[] indegree = new int[MAXN];

    private static int curN = 0;

    private static int cnt = 0;

    private static int res = Integer.MIN_VALUE;

    private static final Queue<Integer> queue = new ArrayDeque<>();

    //专属于本题的辅助数组(用于结算新加的time)
    private static final int[] need = new int[MAXN];

    private static final int[] ans = new int[MAXN];

    //初始化图的函数
    private static void build(int n, int[] time){
        cnt = 1;
        curN = n;
        res = Integer.MIN_VALUE;
        Arrays.fill(head, 0, n + 1, 0);
        Arrays.fill(need, 0, n + 1, 0);
        Arrays.fill(indegree, 0, n + 1, 0);
        queue.clear();
        //初始化ans数组
        for(int i = 1; i <= time.length; i++){
            res = Math.max(res, time[i - 1]);
            ans[i] = time[i - 1];
        }
    }

    //添加边的函数(顺带统计入度)
    private static void addEdge(int fro, int t){
        next[cnt] = head[fro];
        to[cnt] = t;
        head[fro] = cnt++;
        indegree[t]++;
    }

    public int minimumTime(int n, int[][] relations, int[] time) {
        //初始化并建图
        build(n, time);
        for(int[] edge : relations){
            addEdge(edge[0], edge[1]);
        }

        //下面就是拓扑排序的主流程
        topoSort();
        return res;
    }

    private static void topoSort(){
        //首先添加入度为0的点入队列
        for(int i = 1; i <= curN; i++){
            if(indegree[i] == 0){
                queue.offer(i);
            }
        }

        //开始进行拓扑排序的主流程
        while(!queue.isEmpty()){
            int courceNum = queue.poll();
            if(indegree[courceNum] == 0){
                //说明这个时候可以结算了
                ans[courceNum] += need[courceNum];
                res = Math.max(res, ans[courceNum]);
            }else{
                for(int ei = head[courceNum]; ei > 0; ei = next[ei]){
                    need[to[ei]] = Math.max(need[to[ei]], ans[courceNum]);
                    if(--indegree[to[ei]] == 0){
                        queue.offer(to[ei]);
                    }
                }
            }
        }
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Android14请求动态申请存储权限

Android14请求动态申请存储权限 Android14和Android15存储权限有增加多了选择部分&#xff0c;还是全部。一个小小的存储权限真的被它玩出了花来。本来Android13就将存储权限进行了3个细分&#xff0c;是图片&#xff0c;音频还是视频文件。 步骤一&#xff1a;AndroidManife…

24年蓝桥杯及攻防世界赛题-MISC-2

11 Railfence fliglifcpooaae_hgggrnee_o{cr} 随波逐流编码工具 分为5栏时,解密结果为:flag{railfence_cipher_gogogo} 12 Caesar rxms{kag_tmhq_xqmdzqp_omqemd_qzodkbfuaz} mode1 #12: flag{you_have_learned_caesar_encryption} 随波逐流编码工具 13 base64 base64解…

【machine learning-十-梯度下降-学习率】

学习率 学习率不同的学习率 在梯度下降算法中&#xff0c;学习率的选择很重要&#xff0c;不恰当的选择&#xff0c;甚至可能导致损失发散&#xff0c;而非收敛&#xff0c;下面就看一下学习率的影响。 学习率 学习率是下图中的红框圈出来的部分&#xff0c; 学习率是模型的超…

虹科干货 | CAN/CAN FD故障揭秘:快速排查与解决技巧

是否在处理CAN总线问题时感到头疼&#xff1f;是否在寻找简单直接的方法来解决那些看似复杂的连接故障&#xff1f;本文将为您提供实用技巧&#xff0c;让您能够轻松应对这些难题。 CAN总线因其高效、可靠的数据交换能力&#xff0c;在汽车、工业控制、航空航天等多个关键领域得…

《黑神话悟空》开发框架与战斗系统解析

本文主要围绕《黑神话悟空》的开发框架与战斗系统解析展开 主要内容 《黑神话悟空》采用的技术栈 《黑神话悟空》战斗系统的实现方式 四种攻击模式 连招系统的创建 如何实现高扩展性的战斗系统 包括角色属性系统、技能配置文件和逻辑节点的抽象等关键技术点 版权声明 本…

Linux Vim编辑器常用命令

目录 一、命令模式快捷键 二、编辑/输入模式快捷键 三、编辑模式切换到命令模式 四、搜索命令 注&#xff1a;本章内容全部基于Centos7进行操作&#xff0c;查阅本章节内容前请确保您当前所在的Linux系统版本&#xff0c;且具有足够的权限执行操作。 一、命令模式快捷键 二…

图像生成大模型imagen

Imagen 是由谷歌研究团队开发的一种先进的图像生成大模型。它基于文本描述生成高质量的图像&#xff0c;是人工智能在生成视觉内容方面的一大突破。 Imagen 的主要特点包括&#xff1a; 1. 高分辨率和高质量&#xff1a;Imagen 生成的图像具有高分辨率和高质量&#xff0c;细…

springboot宠物智慧医院-计算机毕业设计源码99362

目录 摘要 1 绪论 1.1 选题背景与意义 1.2国内外研究现状 1.3微信开发者工具 1.4小程序框架以及目录结构介绍 1.5论文结构与章节安排 2系统分析 2.1 可行性分析 2.2 系统流程分析 2.2.1系统开发流程 2.2.2 用户登录流程 2.2.3 系统操作流程 2.2.4 添加信息流程 2…

模拟电路分析基础知识总结笔记(电子电路分析与设计前置知识)

必备条件 电子电路的直流分析电子电路的正弦稳态分析RC电路的瞬态分析戴维南定理和诺顿定理拉普拉斯变换&#xff08;看不懂&#xff0c;根本看不懂&#xff09; 电子电路的直流分析 欧姆定律 ​ 在恒定温度下&#xff0c;电压与电流成正比&#xff0c;电压与电阻成正比&am…

对 JavaScript 原型的理解

笔者看了一些有关 JavaScript 原型的文章有感而发&#xff0c;就将所感所悟画了下来如果有理解错误和不足的地方&#xff0c;欢迎各位大佬指出&#xff0c;笔者感激不尽

企业热门进销存管理系统源码 助力中小企业实现低成本实现信息化 带源代码包以及搭建部署教程

系统概述 这款企业热门进销存管理系统是专为中小企业设计开发的综合性管理平台。它涵盖了采购、销售、库存管理等核心业务流程&#xff0c;能够实现企业内部各个环节的紧密连接和协同运作。通过信息化手段&#xff0c;系统能够实时记录和监控企业的业务数据&#xff0c;为企业…

微服务保护学习笔记(五)Sentinel授权规则、获取origin、自定义异常结果、规则持久化

文章目录 前言4 授权规则4.1 基本原理4.2 获取origin4.3 配置授权规则 5 自定义异常结果6 规则持久化 前言 微服务保护学习笔记(一)雪崩问题及解决方案、Sentinel介绍与安装 微服务保护学习笔记(二)簇点链路、流控操作、流控模式(关联、链路) 微服务保护学习笔记(三)流控效果(…

【STL】string 基础,应用与操作

string 1.string相关介绍 STL&#xff08;标准模板库&#xff09;中的string容器是C标准库提供的用于处理和操作字符串的类&#xff0c;位于头文件中。std::string提供了比传统的C风格字符串&#xff08;字符数组&#xff09;更方便和安全的功能&#xff0c;具有动态内存管理…

Redis常见应用场景

目录 一、实现博客点赞功能 二、实现博客点赞用户列表功能 三、好友关注和取关以及求共同关注 四、实现关注推送 1、拉模式 2、推模式 3、推拉结合 四、三种模式对比 这里简单记录一下&#xff0c;没有实现方法&#xff0c;只是帮助记忆 一、实现博客点赞功能 可以通…

六、JSON

文章目录 1. 什么是JSON1.1 JSON 在 JavaScript 中的使用1.1.1 json 的定义1.1.2 json 的访问1.1.3 json 的两个常用方法 1.2、JSON 在 java 中的使用1.2.1、javaBean 和 json 的互转1.2.2、List 和 json 的互转1.2.3、map 和 json 的互转 1. 什么是JSON 1.1 JSON 在 JavaScrip…

【数据结构与算法 | 灵神题单 | 自顶向下DFS篇】力扣1022,623

1. 力扣1022&#xff1a;从根到叶的二进制之和 1.1 题目&#xff1a; 给出一棵二叉树&#xff0c;其上每个结点的值都是 0 或 1 。每一条从根到叶的路径都代表一个从最高有效位开始的二进制数。 例如&#xff0c;如果路径为 0 -> 1 -> 1 -> 0 -> 1&#xff0c;那…

Uniapp的alertDialog返回值+async/await处理确定/取消问题

今天在使用uniui的alertDialog时&#xff0c;想添加一个确定/取消的警告框时 发现alertDialog和下面的处理同步进行了&#xff0c;没有等待alaertDialog处理完才进行 查询后发现问题在于 await 关键字虽然被用来等待 alertDialog.value.open() 的完成&#xff0c;但是 alertDi…

Linux操作系统 进程(3)

接上文 Linux进程优先级之后&#xff0c;我们了解到僵尸进程与孤儿进程的形成原因&#xff0c;既然是因为父进程没有接收子进程的退出状态导致的&#xff0c;那么我们该如何去获取子进程的退出状态呢&#xff1f;那本篇文章将围绕这个问题来解释进程。 环境 &#xff1a; vsco…

【C++】——多态详解

目录 1、什么是多态&#xff1f; 2、多态的定义及实现 2.1多态的构成条件 ​2.2多态语法细节处理 2.3协变 2.4析构函数的重写 2.5C11 override 和 final关键字 2.6重载—重写—隐藏的对比分析 3、纯虚函数和抽象类 4、多态的原理分析 4.1多态是如何实现的 4.2虚函数…

光伏场地建设规划 - 华为OD统一考试(E卷)

2024华为OD机试&#xff08;C卷D卷E卷&#xff09;最新题库【超值优惠】Java/Python/C合集 题目描述 祖国西北部有一片大片荒地&#xff0c;其中零星的分布着一些湖泊&#xff0c;保护区&#xff0c;矿区;整体上常年光照良好&#xff0c;但是也有一些地区光照不太好。某电力公…