图及其与图相关的算法

news2024/11/22 18:57:12

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

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

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

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

🍉博客中涉及源码及博主日常练习代码均已上传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!!如有问题,欢迎评论区批评指正😁

请添加图片描述

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

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

相关文章

如何在 Rocky Linux 上检查磁盘空间?

在 Rocky Linux 上检查磁盘空间是系统管理和维护的重要任务之一。磁盘空间的监控和管理可以帮助我们及时发现和解决存储空间不足的问题&#xff0c;以确保系统的正常运行。本文将详细介绍在 Rocky Linux 上检查磁盘空间的方法。 方法 1&#xff1a;使用 df 命令 df 命令是 Li…

SOLIDWORKS技巧大全培训教程

1 您可以使用 CTRLTAB 键循环进入在 SolidWorks 中打开的文件。 2 使用方向键可以旋转模型。按 CTRL 键加上方向键可以移动模型。按 ALT 键加上方向键可以将模型沿顺时针或逆时1 您可以使用 CTRLTAB 键循环进入在 SolidWorks 中打开的文件。 2 使用方向键可以旋转模型。按 CTRL…

【CloudCompare教程】012:基于点云数据的测量功能

本文讲解CloudCompare基于点云数据的测量功能,主要有:点云索引、坐标、距离、角度、面积、标签等。 文章目录 一、加载地形点云数据二、基于点云数据的测量功能1. 选择单点并显示信息2. 选择两点并显示分割信息3. 选择三点并显示相关三角形信息4. 定义矩形2D标签5. 保存当前标…

Milvus向量数据库

Milvus vector database 第一章 Milvus概述 Milvus创建于2019年&#xff0c;唯一的目标是&#xff1a;存储、索引和管理由深度神经网络和其他机器学习(ML)模型生成的大量嵌入向量embedding vectors。 存储对象&#xff1a;向量 NOTE&#xff1a;embedding vectors是对非结构…

c#快速入门(下)

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;那个传说中的man的主页 &#x1f3e0;个人专栏&#xff1a;题目解析 &#x1f30e;推荐文章&#xff1a;题目大解析2 目录 &#x1f449;&#x1f3fb;Inline和lambda委托和lambda &#x1f449;&#x1f…

上海斯歌荣获中国低代码/零代码行业“卓越影响力厂商奖”

3月19日&#xff0c;在「第三届中国 ISIG 产业智能大会」隆重的颁奖典礼上&#xff0c;上海斯歌被授予“卓越影响力厂商奖”&#xff0c;并入围国内权威咨询机构LowCode低码时代的《2022年中国低代码&零代码行业研究报告》卓越影响力榜单。 「第三届ISIG中国产业智能大会」…

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

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

惠普83752B高功率合成扫频器,20 GHz

惠普83752B扫频仪为元器件测试市场带来了卓越的综合性能&#xff0c;在通用台式扫频仪、扫频仪或标量测试应用中&#xff0c;83752B提供了性价比最高的性能。这款扫频仪在保持模拟源速度的同时&#xff0c;提供了卓越的精度和稳定性。全合成CW、步进和科坡扫描模式可在宽带和窄…

QT+OpenGL几何着色器

QTOpenGL几何着色器 本篇完整工程见gitee:QtOpenGL 对应点的tag&#xff0c;由turbolove提供技术支持&#xff0c;您可以关注博主或者私信博主 几何着色器 几何着色器的输入是一个图元&#xff08;如点或者三角形&#xff09;的一组顶点几何着色器可以再顶点发送到下一着色器…

Linux——进程的等待

目录 前言&#xff1a; 一.进程等待 父进程回收子进程信息的相关函数1&#xff1a;wait函数 实验案例1&#xff1a;设置wait函数参数为NULL 实验案例2&#xff1a;wait函数带wstatus参数的案例&#xff1a;当子进程正常运行完退出时 情况3&#xff1a; wait函数带wstatus参数…

Softing“物联网连接和OPC UA通信”系列研讨会

— 免费线上研讨会概览 — 您是否正在为车间应用寻找机器连接&#xff1f;您是否需要为创新的物联网解决方案制定架构决策&#xff1f;或者您是否已经选择了物联网平台&#xff0c;需要连接组件来访问自动化网络中的数据&#xff1f;在Softing线上研讨会中&#xff0c;我们将讨…

JavaScript 进阶 (三)

目录 编程思想 面向过程编程 面向对象编程 构造函数 原型 原型 constructor 属性 对象原型 原型继承 原型链 编程思想 面向过程编程 面向过程就是分析出解决问题所需要的步骤&#xff0c;然后用函数把这些步骤一步一步实现&#xff0c;使用的时候再一个一个的依次 调用…

Python中的封装、继承与多态

1. 前言 面向对象的三大特性&#xff0c;想必大家肯定是耳熟能详。就是今天要介绍的内容&#xff1a;封装、继承、多态。面向对象的思想都是一样的&#xff0c;这里我用Python进行实现&#xff01; 2. 封装 2.1 什么是封装呢&#xff1f; 封装就是&#xff1a;我写了一个类…

【T6/T3】根据畅捷通T6/T3账套备份文件判断软件的版本

【问题需求】 当畅捷通T6/T3软件只有正常的账套备份文件的时候&#xff0c; 通过账套备份文件判断软件版本&#xff0c; 进而安装软件恢复数据。 【解决方法】 【畅捷通T6】 找到T6的备份文件&#xff0c; 用记事本打开【UfErpAct.lst】文件。 查看【VersionbatchflagForUFDAT…

【linux】web基础与HTTP协议

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 web基础与HTTP协议 一、DNS/HTML1.域名的概述2.域名注册3.网页的概念4.HTML概述5.网页基本标签 二、web基础1.web概述2.静态网页3.静态网页特点4.动态网页5.动态网页的特点 三…

软件开发SOLID设计原则

前言&#xff1a;SOLID设计原则&#xff0c;不管是软件系统还是代码的实现&#xff0c;遵循SOLID设计原则&#xff0c;都能够有效的提高系统的灵活和可靠性&#xff0c;应对代码实现的需求变化也能更好的扩展和维护。因此提出了五大原则——SOLID。 我是通过老师讲解以及老师…

【深入浅出Spring Security(三)】默认登录认证的实现原理

Spring Security 默认登录认证的实现原理 一、默认配置登录认证过程二、流程分析登录页面的由来表单登录认证过程&#xff08;源码分析&#xff09; 三、UserDetailsServiceSpring Security 中 UserDetailsService 的实现默认的 UserDetailsService 配置&#xff08;源码分析&a…

【送书福利-第七期】《分布式中间件核心原理与RocketMQ最佳实践》

大家好&#xff0c;我是洲洲&#xff0c;欢迎关注&#xff0c;一个爱听周杰伦的程序员。关注公众号【程序员洲洲】即可获得10G学习资料、面试笔记、大厂独家学习体系路线等…还可以加入技术交流群欢迎大家在CSDN后台私信我&#xff01; 本文目录 一、前言二、内容介绍三、作者介…

孪生诱捕网络在欺骗防御领域的应用

随着以数字化、网络化和智能化为特征的信息化浪潮的蓬勃兴起&#xff0c;信息已经成为重要的战略资源与重要生产要素&#xff0c;在国家的发展和人们的生产生活中起到至关重要的作用。信息化在给人们带来便利的同时&#xff0c;网络信息安全问题也日益凸显。经过多年的网络安全…

【Linux】Linux 文件系统与设备文件

Ref: 《Linux设备驱动开发详解&#xff1a;基于最新的Linux4.0内核》中的第5章《Linux 文件系统与设备文件》 基于Linux 5.10 本文结合源码和实例分析了Linux 文件系统与设备文件&#xff0c;主要介绍文件系统的构成和发展&#xff0c;以及三种虚拟文件系统debugfs procfs sysf…