图及其与图相关的算法

请添加图片描述
⭐️前言⭐️

本篇文章主要介绍图及其与图相关的算法

🍉欢迎点赞 👍 收藏留言评论 📝私信必回哟😁

🍉博主将持续更新学习记录收获,友友们有任何问题可以在评论区留言

🍉博客中涉及源码及博主日常练习代码均已上传GitHub


请添加图片描述

📍内容导读📍

  • 🍅图概念
  • 🍅图的表达方式
  • 🍅图的抽象数据结构
  • 🍅图的宽度优先遍历 BFS
  • 🍅图的深度优先遍历 DFS
  • 🍅图的拓扑排序
  • 🍅最小生成树算法Kruskal
  • 🍅最小生成树算法Prim
  • 🍅单元最短路径算法Dijkstra

🍅图概念

1)由点的集合和边的集合构成
2)虽然存在有向图和无向图(无向a-b相当于有向,边是相互的)的概念,但实际上都可以用有向图来表达
3)边上可能带有权重
在这里插入图片描述

🍅图的表达方式

1)邻接表法
在这里插入图片描述

2)邻接矩阵法
在这里插入图片描述

以上两种是教科书上的讲法,实际题目中通常不以以上形式出现,而是以下这种方式:
在这里插入图片描述

甚至一个数组也能表示一个图结构。

🍅图的抽象数据结构

因为图有多种不同的表达方式,所以为了解题,我们可以抽象出一种图结构,在得到不同的图的表达方式后,转换到我们自己熟悉的图结构上,用模板来完成解题。

以下就是抽象出来的图结构的代码:
首先是点结构的描述:

public class Node {
    public int value;
    public int in;   // 入度:表示有多少个节点指向该节点
    public int out;  // 出度:表示该节点指向了多少个节点
    public ArrayList<Node> nexts;  // 存储该节点直接指向节点的集合
    public ArrayList<Edge> edges;  // 存储该节点直接访问的边的集合

    public Node(int value) {
        this.value=value;
        in=0;
        out=0;
        nexts=new ArrayList<>();
        edges=new ArrayList<>();
    }
}

边结构的描述:

public class Edge {
    public int weight;  // 边的权重(距离长度)
    public Node from;   // 边的起始节点
    public Node to;     // 边的终止节点

    public Edge(int weight, Node from, Node to) {
        this.weight = weight;
        this.from = from;
        this.to = to;
    }
}

图结构的描述:

public class Graph {
    public HashMap<Integer,Node> nodes;
    public HashSet<Edge> edges;
    
    public Graph() {
        nodes=new HashMap<>();
        edges=new HashSet<>();
    }
}

生成图:

public class GraphGenerator {
    /*
    输入N*3的矩阵
    [5,0,7]
    [3,0,1]
    ......
    [weight,from节点上面的值,to节点上面的值]
     */
    public static Graph createGraph(int[][] matrix) {
        Graph graph=new Graph();
        for (int i = 0; i < matrix.length; i++) {
            int weight=matrix[i][0];
            int from=matrix[i][1];
            int to=matrix[i][2];
            if(!graph.nodes.containsKey(from)) {
                graph.nodes.put(from,new Node(from));
            }
            if(!graph.nodes.containsKey(to)) {
                graph.nodes.put(to,new Node(to));
            }
            Node fromNode=graph.nodes.get(from);
            Node toNode=graph.nodes.get(to);
            Edge newEdge=new Edge(weight,fromNode,toNode);
            fromNode.nexts.add(toNode);
            fromNode.out++;
            toNode.in++;
            fromNode.edges.add(newEdge);
            graph.edges.add(newEdge);
        }
        return graph;
    }
}

🍅图的宽度优先遍历 BFS

1、利用队列实现(与二叉树BFS类似)
2、从源节点开始依次把邻节点进队列,然后弹出(弹出时打印节点)
3、每弹出一个点,把该节点所有没有进过队列的邻结点入队列
4、直到队列为空
代码实现:

public class BFS {
    public static void bfs(Node start) {
        if(start==null) {
            return;
        }
        Queue<Node> queue=new LinkedList<>();
        HashSet<Node> set=new HashSet<>();
        queue.add(start);
        set.add(start);
        while (!queue.isEmpty()) {
            Node cur=queue.poll();
            System.out.println(cur.value);
            for(Node next: cur.nexts) {
                if(!set.contains(next)) {
                    queue.offer(next);
                    set.add(next);
                }
            }
        }
    }
}

🍅图的深度优先遍历 DFS

1、利用栈实现(栈记录遍历的路径)
2、从源节点开始把节点按深度放入栈,然后弹出
3、每弹出一个点,就把该节点下一个没有进过栈的邻节点放入栈(入栈后打印节点)
4、直到栈为空
代码实现:

public class DFS {
    public static void dfs(Node start) {
        if(start==null) {
            return;
        }
        Stack<Node> stack=new Stack<>();
        HashSet<Node> set=new HashSet<>();
        stack.add(start);
        set.add(start);
        System.out.println(start.value);
        while (!stack.isEmpty()) {
            Node cur=stack.pop();
            for(Node next: cur.nexts) {
                if(!set.contains(next)) {
                    stack.push(cur);
                    stack.push(next);
                    set.add(next);
                    System.out.println(next.value);
                    break;
                }
            }
        }
    }
}

🍅图的拓扑排序

常见的应用场景是编译排序、事件安排:
比如包A需要包B、C,包B需要包D,包C需要包E、F,如果想要加载好包A,首先需要加载D得到B,加载E、F得到C,再通过B、C得到A。

想要实现拓扑排序,就需要按照以下步骤操作:
1)在图中找到所有入度为0的点(拓扑序的起始处)输出
2)把所有入度为0的点在图中删掉,继续找入度为0的点输出,周而复始
3)图的所有点都被删除后,依次输出的顺序就是拓扑排序。

要求:有向图且其中无环

代码实现:

public class TopologySort {
    public static List<Node> sortedTopology(Graph graph) {
        // key:某个节点  value:剩余的入度
        HashMap<Node,Integer> inMap=new HashMap<>();
        // 只有剩余入度为0的点,才进入这个队列
        Queue<Node> zeroInQueue=new LinkedList<>();
        for(Node node:graph.nodes.values()) {
            inMap.put(node, node.in);
            if(node.in==0) {
                zeroInQueue.offer(node);
            }
        }
        List<Node> result=new ArrayList<>();
        while (!zeroInQueue.isEmpty()) {
            Node cur=zeroInQueue.poll();
            result.add(cur);
            for (Node next: cur.nexts) {
                inMap.put(next,inMap.get(next)-1);
                if(inMap.get(next)==0) {
                    zeroInQueue.offer(next);
                }
            }
        }
        return result;
    }
}

题目练习:https://www.lintcode.com/problem/127/
在这里插入图片描述

BFS解法:
与演示代码思想类似,利用入度为0的节点做为解题点。

public class TopologicalOrderBFS {
    // 不提交该类
    public static class DirectedGraphNode {
        public int label;
        public ArrayList<DirectedGraphNode> neighbors;

        public DirectedGraphNode(int label) {
            this.label = label;
            neighbors=new ArrayList<>();
        }
    }

    // 提交以下代码
    public static ArrayList<DirectedGraphNode> topSort(ArrayList<DirectedGraphNode> graph) {
        HashMap<DirectedGraphNode,Integer> indegreeMap=new HashMap<>();
        for (DirectedGraphNode cur:graph) {
            indegreeMap.put(cur,0);
        }
        for (DirectedGraphNode cur:graph) {
            for (DirectedGraphNode next: cur.neighbors) {
                indegreeMap.put(next,indegreeMap.get(next)+1);
            }
        }
        Queue<DirectedGraphNode> zeroQueue=new LinkedList<>();
        for (DirectedGraphNode cur:indegreeMap.keySet()) {
            if(indegreeMap.get(cur)==0) {
                zeroQueue.offer(cur);
            }
        }
        ArrayList<DirectedGraphNode> result=new ArrayList<>();
        while (!zeroQueue.isEmpty()) {
            DirectedGraphNode cur=zeroQueue.poll();
            result.add(cur);
            for(DirectedGraphNode next: cur.neighbors) {
                indegreeMap.put(next,indegreeMap.get(next)-1);
                if(indegreeMap.get(next)==0) {
                    zeroQueue.offer(next);
                }
            }
        }
        return result;
    }
}

DFS解法:
解法1:根据一个节点可以到达节点的节点次来比较拓扑序

public class TopologicalOrderDFS1 {
    // 不提交该类
    public static class DirectedGraphNode {
        public int label;
        public ArrayList<DirectedGraphNode> neighbors;

        public DirectedGraphNode(int label) {
            this.label = label;
            neighbors=new ArrayList<>();
        }
    }

    // 提交以下的

    /**
     * 该类用于记录每个节点可以访问的节点次,越大拓扑序越靠前
     */
    public static class Record {
        public DirectedGraphNode node;
        public long nodes;

        public Record(DirectedGraphNode node, long nodes) {
            this.node = node;
            this.nodes = nodes;
        }
    }

    public static ArrayList<DirectedGraphNode> topSort(ArrayList<DirectedGraphNode> graph) {
        HashMap<DirectedGraphNode,Record> order=new HashMap<>();
        for(DirectedGraphNode cur:graph) {
            f(cur,order);
        }
        ArrayList<Record> records=new ArrayList<>();
        for(Record r:order.values()) {
            records.add(r);
        }
        records.sort(new Comparator<Record>() {
            @Override
            public int compare(Record o1, Record o2) {
                return o1.nodes == o2.nodes ? 0 : (o1.nodes > o2.nodes ? -1 : 1);
            }
        });
        ArrayList<DirectedGraphNode> result=new ArrayList<>();
        for (Record r:records) {
            result.add(r.node);
        }
        return result;
    }

    // 返回cur节点可到的所有节点次
    public static Record f(DirectedGraphNode cur,HashMap<DirectedGraphNode,Record> order) {
        if(order.containsKey(cur)) {
            return order.get(cur);
        }
        long nodes=0;
        for(DirectedGraphNode next: cur.neighbors) {
            nodes+=f(next,order).nodes;
        }
        Record ans=new Record(cur,nodes+1);
        order.put(cur,ans);
        return ans;
    }
}

解法2: 根据一个节点可以到达的最大深度来比较拓扑序

public class TopologicalOrderDFS2 {
    // 不要提交这个类
    public static class DirectedGraphNode {
        public int label;
        public ArrayList<DirectedGraphNode> neighbors;

        public DirectedGraphNode(int x) {
            label = x;
            neighbors = new ArrayList<DirectedGraphNode>();
        }
    }

    // 提交下面的
    public static class Record {
        public DirectedGraphNode node;
        public int deep;

        public Record(DirectedGraphNode node, int deep) {
            this.node = node;
            this.deep = deep;
        }
    }

    public static ArrayList<DirectedGraphNode> topSort(ArrayList<DirectedGraphNode> graph) {
        HashMap<DirectedGraphNode,Record> order=new HashMap<>();
        for(DirectedGraphNode cur:graph) {
            f(cur,order);
        }
        ArrayList<Record> records=new ArrayList<>();
        for(Record r:order.values()) {
            records.add(r);
        }
        records.sort(new Comparator<Record>() {
            @Override
            public int compare(Record o1, Record o2) {
                return o2.deep- o1.deep;
            }
        });
        ArrayList<DirectedGraphNode> result=new ArrayList<>();
        for(Record r:records) {
            result.add(r.node);
        }
        return result;
    }

    public static Record f(DirectedGraphNode cur,HashMap<DirectedGraphNode,Record> order) {
        if(order.containsKey(cur)) {
            return order.get(cur);
        }
        int follow=0;
        for(DirectedGraphNode next: cur.neighbors) {
            follow=Math.max(follow,f(next,order).deep);
        }
        Record ans=new Record(cur,follow+1);
        order.put(cur,ans);
        return ans;
    }
}

🍅最小生成树算法Kruskal

最小生成树一定是无向图
该算法就是求可以遍历到所有节点的最小权值边的集合,如下图所示,无向图(图左)的最小生成树就是图右
在这里插入图片描述

通过并查集(点)+优先级队列(边权值)来解

算法设计思想总结如下:
1)总是从权值小的边开始考虑,依次考察权值依次变大的边(借助优先级队列)
2)当前的边要么进入最小生成树的集合,要么丢弃
3)如果当前的边进入最小生成树的集合中不会形成环,就要当前边(借助并查集判断是否成环)
4)如果当前的边进入最小生成树的集合中会形成环,就不要当前边
5)考察完所有的边之后,最小生成树的集合也就得到了
因为要求最小生成树,所以首先从权值小的边开始考虑;如果有环,说明该边可以连接的节点,可以通过其他边实现,又因为是从权值小的开始考虑的,所以该边舍弃;
判断是否有环可以通过并查集,判断点所在集合是否是同一个集合即可判断是否有环。
代码实现:

public class Kruskal {
    public static class UnionFind {
        // key:某个节点  value:key节点的代表节点
        private HashMap<Node,Node> representMap;
        // key:某个集合的代表节点  value:key所在集合的节点个数
        private HashMap<Node,Integer> sizeMap;

        public UnionFind(Graph graph) {
            representMap=new HashMap<>();
            sizeMap=new HashMap<>();
            for (Node node:graph.nodes.values()) {
                representMap.put(node,node);
                sizeMap.put(node,1);
            }
        }

        private Node findRepresent(Node x) {
            Stack<Node> path=new Stack<>();
            while (x!=representMap.get(x)) {
                path.add(x);
                x=representMap.get(x);
            }
            while (!path.isEmpty()) {
                representMap.put(path.pop(),x);
            }
            return x;
        }

        public boolean isSameSet(Node a,Node b) {
            return findRepresent(a)==findRepresent(b);
        }

        public void union(Node a,Node b) {
            if(a==null||b==null) {
                return;
            }
            Node fa=findRepresent(a);
            Node fb=findRepresent(b);
            if(fa!=fb) {
                int aSetSize=sizeMap.get(fa);
                int bSetSize=sizeMap.get(fb);
                if(aSetSize>=bSetSize) {
                    representMap.put(fb,fa);
                    sizeMap.put(fa,aSetSize+bSetSize);
                    sizeMap.remove(fb);
                }else {
                    representMap.put(fa,fb);
                    sizeMap.put(fb,aSetSize+bSetSize);
                    sizeMap.remove(fa);
                }
            }
        }
    }

    public static Set<Edge> kruskal(Graph graph) {
        UnionFind unionFind=new UnionFind(graph);
        // 默认小根堆
        PriorityQueue<Edge> priorityQueue=new PriorityQueue<>(new Comparator<Edge>() {
            @Override
            public int compare(Edge o1, Edge o2) {
                return o1.weight-o2.weight;
            }
        });
        for (Edge edge:graph.edges) {
            priorityQueue.add(edge);
        }
        Set<Edge> result=new HashSet<>();
        while (!priorityQueue.isEmpty()) {
            Edge edge=priorityQueue.poll();
            if(!unionFind.isSameSet(edge.from,edge.to)) {
                result.add(edge);
                unionFind.union(edge.from,edge.to);
            }
        }
        return result;
    }
}

🍅最小生成树算法Prim

点解锁边,边解锁点

小根堆(解锁的边)+哈希Set(被解锁的点)
1)可以从任意节点出发来寻找最小生成树
2)某个点被加入到解锁点集合后,解锁这个点出发的所有新的边
3)在所有解锁的边中选取最小的边,然后看看这个边会不会形成环(就是该边连接的节点是不是已经被解锁过了)
4)如果会,不要当前边,继续考察剩下解锁的边中最小的边,重复3
5)如果不会,要当前边,将该边指向的节点加入到被选取的点中,重复2
6)当所有的点被选取后,最小生成树就得到了。

代码实现:

public class Prim {
    public static Set<Edge> prim(Graph graph) {
        // 解锁的边进入小根堆
        PriorityQueue<Edge> priorityQueue=new PriorityQueue<>(new Comparator<Edge>() {
            @Override
            public int compare(Edge o1, Edge o2) {
                return o1.weight- o2.weight;
            }
        });
        // 解锁的点进入set集合
        HashSet<Node> nodeSet=new HashSet<>();

        Set<Edge> result=new HashSet<>();
        for (Node node:graph.nodes.values()) { // 随便挑了一个点
            if(!nodeSet.contains(node)) {
                nodeSet.add(node);
                for(Edge edge:node.edges) { // 由一个点解锁所有相连边
                    priorityQueue.add(edge);
                }
                while (!priorityQueue.isEmpty()) {
                    Edge edge=priorityQueue.poll();// 弹出解锁的边中,最小的边
                    Node toNode=edge.to;
                    if(!nodeSet.contains(toNode)) {
                        nodeSet.add(toNode);
                        result.add(edge);
                        for(Edge nextEdge: toNode.edges) {
                            priorityQueue.add(nextEdge);
                        }
                    }
                }
            }
            // break
        }
        return result;
    }
}

🍅单元最短路径算法Dijkstra

必须是有向无负权重的图,一定是要给定一个出发点,要求的是从出发点到所有可到达点的最短距离的一张表。

1)必须指定源点
2)生成一个源点到各个点的最小距离表,一开始只有一条记录,及源点到自己的距离为0,到其他点的距离为正无穷
3)从距离表中拿出没选过点里的最小记录,通过这个点发出的边,更新源点到各个点的最小距离表,不断重复这一步
4)源点到所有的点的记录如果都被拿过一遍,过程停止,最小距离表拿到了
在这里插入图片描述

代码实现:

public class Dijkstra {
    public static HashMap<Node,Integer> dijkstra(Node from) {
        HashMap<Node,Integer> distanceMap=new HashMap<>();// 距离表
        distanceMap.put(from,0);
        // 被选择过的点
        HashSet<Node> selectedNodes=new HashSet<>();
        Node minNode=getMinDistanceAndUnselectedNode(distanceMap,selectedNodes);
        while (minNode!=null) {
            // 原始点 -> minNode(跳转点)  最小距离 distance
            int distance=distanceMap.get(minNode);
            for(Edge edge: minNode.edges) {
                Node toNode=edge.to;
                if(!distanceMap.containsKey(toNode)) {
                    distanceMap.put(toNode,distance+edge.weight);
                }else {
                    distanceMap.put(edge.to,Math.min(distanceMap.get(toNode),distance+edge.weight));
                }
            }
            selectedNodes.add(minNode);
            minNode=getMinDistanceAndUnselectedNode(distanceMap,selectedNodes);
        }
        return distanceMap;
    }

    private static Node getMinDistanceAndUnselectedNode(HashMap<Node, Integer> distanceMap, HashSet<Node> selectedNodes) {
        Node minNode=null;
        int minDistance=Integer.MAX_VALUE;
        for(Map.Entry<Node,Integer> entry:distanceMap.entrySet()) {
            Node node=entry.getKey();
            int distance= entry.getValue();
            if(!selectedNodes.contains(node)&&distance<minDistance) {
                minNode=node;
                minDistance=distance;
            }
        }
        return minNode;
    }
}

加强堆实现优化:
由于普通方法需要每次都获取未被选择且距离最小的点,方法实现较为繁琐,可以通过借助加强堆来实现。
直接在堆上实现节点记录类的增加和修改。

代码实现:

public class Dijkstra {
    public static class NodeRecord {
        public Node node;
        public int distance;

        public NodeRecord(Node node, int distance) {
            this.node = node;
            this.distance = distance;
        }
    }

    public static class NodeHeap {
        private Node[] nodes;// 实际的堆结构
        // key:某个node   value:上面堆中的位置
        private HashMap<Node,Integer> heapIndexMap;// 反向索引表
        // key:某个节点    value:从源节点触发到该节点的目前最小距离
        private HashMap<Node,Integer> distanceMap;
        private int size;// 堆上有多少个点

        public NodeHeap(int size) {
            nodes=new Node[size];
            heapIndexMap=new HashMap<>();
            distanceMap=new HashMap<>();
            size=0;
        }

        public boolean isEmpty() {
            return size==0;
        }

        public void addOrUpdateOrIgnore(Node node,int distance) {
            if(inHeap(node)) {   // update
                distanceMap.put(node,Math.min(distanceMap.get(node),distance));
                heapInsert(heapIndexMap.get(node));
            }
            if(!isEntered(node)) {   //  add
                nodes[size]=node;
                heapIndexMap.put(node,size);
                distanceMap.put(node,distance);
                heapInsert(size++);
            }
            //  ignore
        }

        public NodeRecord pop() {
            NodeRecord nodeRecord=new NodeRecord(nodes[0],distanceMap.get(nodes[0]));
            swap(0,size-1);
            heapIndexMap.put(nodes[size-1],-1);
            distanceMap.remove(nodes[size-1]);
            nodes[size-1]=null;
            shiftDown(0,--size);
            return nodeRecord;
        }

        private void heapInsert(int child) {
            int parent=(child-1)/2;
            while (child>0) {
                if(distanceMap.get(child)<distanceMap.get(parent)) {
                    swap(child,parent);
                    child=parent;
                    parent=(child-1)/2;
                }else {
                    break;
                }
            }
        }

        private void shiftDown(int parent,int size) {
            int child=parent*2+1;
            while (child<size) {
                if(child+1<size&&distanceMap.get(child)>distanceMap.get(child+1)) {
                    child++;
                }
                if(distanceMap.get(child)<distanceMap.get(parent)) {
                    swap(child,parent);
                    parent=child;
                    child=2*parent+1;
                }else {
                    break;
                }
            }
        }

        private void swap(int index1,int index2) {
            heapIndexMap.put(nodes[index1],index2);
            heapIndexMap.put(nodes[index2],index1);
            Node tmp=nodes[index1];
            nodes[index1]=nodes[index2];
            nodes[index2]=tmp;
        }

        private boolean isEntered(Node node) {
            return heapIndexMap.containsKey(node);
        }

        private boolean inHeap(Node node) {
            return isEntered(node)&&heapIndexMap.get(node)!=-1;
        }
    }
    
    // 改进后的dijkstra算法
    // 从head出发,所有head能到达的节点,生成到达每个的路径记录并返回
    public static HashMap<Node,Integer> dijkstra(Node head,int size) {
        NodeHeap nodeHeap=new NodeHeap(size);
        nodeHeap.addOrUpdateOrIgnore(head,0);
        HashMap<Node,Integer> result=new HashMap<>();
        while (!nodeHeap.isEmpty()) {
            NodeRecord record=nodeHeap.pop();
            Node cur=record.node;
            int distance=record.distance;
            for(Edge edge: cur.edges) {
                nodeHeap.addOrUpdateOrIgnore(edge.to,edge.weight+distance);
            }
            result.put(cur,distance);
        }
        return result;
    }
}

⭐️最后的话⭐️
总结不易,希望uu们不要吝啬你们的👍哟(^U^)ノ~YO!!如有问题,欢迎评论区批评指正😁

请添加图片描述

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

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

相关文章

智慧档案馆八防是怎么建设的?都需要注意哪些内容

智慧档案馆八防环境监控系统一体化解决系统方案 智慧档案库房一体化平台通过智慧档案管理&#xff0c;实现智慧档案感知协同处置功能&#xff1b;实现对档案实体的智能化识别、定位、跟踪监控&#xff1b;实现对档案至智能密集架、空气恒湿净化一体设备、安防设备&#xff0c…

Linux守护进程

守护进程 Linux/Unix 会话 会话首进程 进程组 组长进程&#xff1a;第一个启动的进程叫组长进程。 关闭终端&#xff1a;进程组里全部进程关闭。 setsid()创建一个新的会话。&#xff08;必须是组员进程才可以创建一个新的会话&#xff09; 1.先fork()&#xff0c;退出父进程 2…

电力系统的虚假数据注入攻击和MTD系统研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

字符串最后一个单词的长度

描述 计算字符串最后一个单词的长度&#xff0c;单词以空格隔开&#xff0c;字符串长度小于5000。&#xff08;注&#xff1a;字符串末尾不以空格为结尾&#xff09; 输入描述&#xff1a; 输入一行&#xff0c;代表要计算的字符串&#xff0c;非空&#xff0c;长度小于500…

mysql触发器监听数据投递中间件

目前市面上有许多的 CDC&#xff08;Change Data Capture&#xff09; 框架用于监听数据库的数据变动&#xff0c;例如&#xff1a;canal、Debezium、Maxwell等都是用来解析 binlog 日志实现事件的监听。但是有一个情况就是如果公司对 binlog 日志文件的权限管控的很严格&#…

【学习日记2023.6.2】之 管理端报表统计

文章目录 11. 管理端报表统计11.1 Apache ECharts11.1.1 介绍11.1.2 入门案例 11.2 营业额统计11.2.1 需求分析和设计11.2.2 代码开发Controller层Service层接口Service层实现类Mapper层 11.2.3 功能测试11.2.4 提交代码 11.3 用户统计11.3.1 需求分析和设计11.3.2 代码开发Con…

如何编写接口自动化框架系列通过yaml来管理测试用例(四)

本文是接口自动化测试框架系列篇的第四篇 &#xff0c;主要介绍yaml包的使用 。自动化测试的本质是将功能测试用例交给代码去 目录 1. yaml介绍&#xff1f; 2.python中的yaml包 3.项目中使用yaml包 4 项目总结 执行 &#xff0c;测试人员往往是在自动化框架添加对应的测试…

排查Javascript内存泄漏案例(一)

Chrome DevTools里的Performance面板和Memory面板可以用来定位内存问题。 如何判断应用发生内存泄漏&#xff1f; 为了证明螃蟹的听觉在腿上&#xff0c;一个专家捉了只螃蟹并冲它大吼&#xff0c;螃蟹很快就跑了。然后捉回来再冲它吼&#xff0c;螃蟹又跑了。最后专家把螃蟹的…

WPS 借助 ML Kit 无缝翻译 43 种语言,每年净省 6,500 万美元

△ 动画说明: 在笔记本电脑屏幕中&#xff0c;汉字 "文" 将变为字母 "A"&#xff0c;代表文本的横线将逐一出现&#xff0c;就像有人在输入内容一样。 WPS 是一款办公套件软件&#xff0c;可让用户轻松查看和编辑其所有文档、演示文稿、电子表格等。作为一…

RK3568 AP6275S蓝牙驱动程序调度过程

1、前言 今年3月份调度了RK3568驱动程序&#xff0c;当时由于时间的问题&#xff0c;AP6275S蓝牙驱动程序没有调试成功。当时仔细检查的设备树的配置。 wireless_bluetooth: wireless-bluetooth {compatible "bluetooth-platdata";clocks <&rk809 1>;cl…

JavaCV - 图像暗通道去雾

一、效果图 二、实现原理 暗通道先验:首先说在绝大多数非天空的局部区域里,某一些像素总会有至少一个颜色通道具有很低的值,也就是说该区域光强是一个很小的值。所以给暗通道下了个数学定义,对于任何输入的图像J,其暗通道可以用下面的公式来表示:其中JC表示彩色图像每个…

【Vue2.0源码学习】虚拟DOM篇-Vue中的DOM-优化更新子节点

1. 前言 在上一篇文章中&#xff0c;我们介绍了当新的VNode与旧的oldVNode都是元素节点并且都包含子节点时&#xff0c;Vue对子节点是 先外层循环newChildren数组&#xff0c;再内层循环oldChildren数组&#xff0c;每循环外层newChildren数组里的一个子节点&#xff0c;就去…

超100篇! VAD论文梳理汇总!

GitHub的一位博主整理了上百篇语音活动检测&#xff08;VAD&#xff09;的论文&#xff0c;按照其中使用的特征方法以及适用的环境进行了分类整理&#xff0c;时间跨度为从198*年至2019年。此外&#xff0c;还提供了几个VAD代码&#xff0c;它们的性能表现较好。需要的同学可以…

基于深度学习的高精度牙齿健康检测识别系统(PyTorch+Pyside6+YOLOv5模型)

摘要&#xff1a;基于深度学习的高精度牙齿健康检测识别系统可用于日常生活中检测牙齿健康状况&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的牙齿目标检测识别&#xff0c;另外支持结果可视化与图片或视频检测结果的导出。本系统采用YOLOv5目标检测模型训练数…

Niagara—— System和Emitter节点

目录 一&#xff0c;发射器节点 Properties Emitter Spawn Emitter Update Particle Spawn Particle Update Renderer 二&#xff0c;系统节点 Properties System Spawn System Update Niagara是按照从上到下按顺序&#xff0c;依次执行模块Module&#xff08;可编程…

Locust接口性能测试

谈到性能测试工具&#xff0c;我们首先想到的是LoadRunner或JMeter。LoadRunner是非常有名的商业性能测试工具&#xff0c;功能非常强大。但现在一般不推荐使用该工具来进行性能测试&#xff0c;主要是使用也较为复杂&#xff0c;而且该工具体积比较大&#xff0c;需要付费且价…

UE5的IK Rig重定向注意问题

推荐先把官方文档看仔细&#xff0c;明白IK Rig重定向原理&#xff0c;对理解UE5怎么进行动画骨骼重定向&#xff0c;以及重定向后骨骼出现问题应该怎么调整非常有帮助。 IK Rig重定向 主要功能点 IK Rig IK Rig的作用是定义两个转化关系中的源骨骼和目标骨骼的主要部件的骨…

Linux——网络套接字2|Tcp服务器编写

本篇博客先看后面的代码,再回来看上面这些内容。 .hpp文件,基本调用 服务器基本框架

ASEMI代理KY可控硅BT169的工作原理及应用领域

编辑-Z 本文主要介绍了可控硅BT169的工作原理及其在各个领域的用。首先&#xff0c;我们将详细阐述可控硅BT169的工作原理&#xff0c;包括结构特点、工作过程等&#xff1b;其次&#xff0c;我们将探讨可控硅BT169在家用电器、工业控制、电力电子等领域的应用。 1、可控硅BT1…

进程(三)

进程三 2.11 进程同步、进程互斥2.11.1 进程同步2.11.2 进程互斥2.11.3 总结 2.12 进程互斥和软件实现方法2.12.1 单标志法2.12.2 双标志先检查法2.12.3 双标志后检查法2.12.4 Peterson 算法2.12.5 总结 2.13 进程互斥和硬件实现方法2.13.1 中断屏蔽方法2.13.2 TestAndSet 指令…