【数据结构与算法】3.(单向、无向、带权)图,广度、深度优先搜索,贪心算法

news2024/11/28 6:40:25

文章目录

  • 1.图简介
  • 2.图的存储方式
    • 2.1.邻接矩阵存储方法
    • 2.2.邻接表存储方法
  • 3.有向、无向图和查询算法
    • 3.1.数据结构
    • 3.2.广度优先算法BFS
    • 3.3.深度优先算法DFS
      • 3.3.1.DFS查询单条路径
      • 3.3.2.DFS查询所有路径
  • 4.带权图和贪心算法
    • 4.1.贪心算法
    • 4.2.基于带权无向图使用贪心算法查询最优路径

1.图简介

      在数学中,图是描述于一组对象的结构,其中某些对象对在某种意义上是“相关的”。这些对象对应于称为顶点的数学抽象(也称为节点或点),并且每个相关的顶点对都称为边(也称为链接或线)。通常,图形以图解形式描绘为顶点的一组点或环,并通过边的线或曲线连接。 图形是离散数学的研究对象之一。
图的类型分为单向图和双向图,其中双向图中有个变种叫做加权(优先级)图:
- 单向图: 例如城市里的单行路里面只能走一个方向不能掉头往回走的马路。
- 无向图:例如城市里随处可见包含了正反两个方向的马路,这种路可以掉头。
- 带权图: 例如使用导航的时候,距离最短并不说明这条路线是最优路线,因为可能会堵车。这时候,带权图就可以帮助我们解决问题。

单向图如下图所示,箭头标识每个顶点之间的关系,如果需要关联双向关系的话,以A为例则需要添加C与A关联的箭头。
在这里插入图片描述
双向图如下图所示,顶点相邻的线没有箭头,表示是一个双向的关系。
在这里插入图片描述

带权图如下图所示,两个顶点之间连接线上的数值标识权重值,可以根据权重值升序或降序优先找最优路径。

在这里插入图片描述

2.图的存储方式

2.1.邻接矩阵存储方法

      图最直观的一种存储方法就是,邻接矩阵(Adjacency Matrix)邻接矩阵的底层依赖一个二维数组。对于无向图来说,如果顶点i与顶点j之间有边,我们就将A[i][j]A[j][i]标记为1;对于有向图来说,如果顶点i到顶点j之间,有一条箭头从顶点i指向顶点j的边,那我们就将A[i][j]标记为1。同理,如果有一条箭头从顶点j指向顶点i的边,我们就将A[j][i]标记为1。对于带权图,数组中就存储相应的权重。
在这里插入图片描述
      用邻接矩阵来表示一个图,虽然简单、直观,但是比较浪费存储空间。为什么这么说呢?
对于无向图来说,如果A[i][j]等于1,那A[j][i]也肯定等于1。实际上,我们只需要存储一个就可以了。也就是说,无向图的二维数组中,如果我们将其用对角线划分为上下两部分,那我们只需要利用上面或者下面这样一半的空间就足够了,另外一半白白浪费掉了。

2.2.邻接表存储方法

      针对2.1.邻接矩阵比较浪费内存空间的问题可以使用邻接表(Adjacency List)解决。邻接表有点像散列表,每个顶点对应一条链表,链表中存储的是与这个顶点相连接的其他顶点。下图是有向图的邻接表存储方式,每个顶点对应的链表里面,存储的是指向的顶点。对于无向图来说,也是类似的。
在这里插入图片描述

3.有向、无向图和查询算法

3.1.数据结构

本文以邻接表的方式实现图的存储,感兴趣的自己可以用邻接矩阵实现。

@Data
public class Graph {
    /**
     * 顶点的个数
     */
    private int size;
    /**
     * 邻接表
     */
    private LinkedList<Integer> tables[];
    /**
     * 深度优先找到路径标识
     */
    private boolean found = false;
    
    public Graph(int size) {
        this.size = size;
        tables = new LinkedList[size];
        for (int i = 0; i < size; i++) {
            tables[i] = new LinkedList<>();
        }
    }

    /**
     * 无向图一条边存两次
     *
     * @param v 顶点v
     * @param e 边集e
     */
    public void addEdge(int v, int e) {
        if (v > size) {
            return;
        }
        tables[v].add(e);
        //注释下面一行就是单向图
        tables[e].add(v);
    }
    /**
     * 递归打印s->t的路径
     */
    private void print(int[] prev, int s, int t) {
        if (prev[t] != -1 && t != s) {
            print(prev, s, prev[t]);
        }
        System.out.print(t + " ");
    }
 }   

在上面代码中把addEdge()函数中的 tables[e].add(v); 注释掉了话,无向图就成了单向图

在这里插入图片描述
以上图数据为例,把二维数组中数据转换成图中的代码 如下

    public static void main(String[] args) {
        Graph graph = new Graph(9);
        int[][] nums = new int[][]{{0, 1, 2}, {3, 4, 5}, {6, 7, 8}};
        int rowLen = nums.length;
        int colLen = nums[0].length;
        for (int i = 0; i < rowLen; i++) {
            for (int j = 0; j < colLen; j++) {
                if (j < colLen - 1) {
                	//将相下一列的数值加到边中
                    graph.addEdge(nums[i][j], nums[i][j + 1]);
                }
                if (i < rowLen - 1) {
                	//将相邻下一行的数值加到边中
                    graph.addEdge(nums[i][j], nums[i + 1][j]);
                }
            }
        }
        System.out.println(JSONObject.toJSONString(graph));

运行结果如下,可以看出每个顶点都把相邻的边数据存放到链表中了

{"found":false,"size":9,"tables":[[1,3],[0,2,4],[1,5],[0,4,6],[1,3,5,7],[2,4,8],[3,7],[4,6,8],[5,7]]}

把addEdge()函数中的 tables[e].add(v); 注释掉了测试返回结果如下

{"found":false,"size":9,"tables":[[1,3],[2,4],[5],[4,6],[5,7],[8],[7],[8],[]]}

3.2.广度优先算法BFS

      广度优先搜索(Breadth-First-Search),我们平常都把简称为BFS。直观地讲,它其实就是一种“地毯式”层层推进的搜索策略,即先查找离起始顶点最近的,然后是次近的,依次往外搜索。理解起来并不难,以下以顶0点到顶8点为例找寻最短路径过程。
在这里插入图片描述

上图先从 [1,3]开始查找,途径[2,4,6],[5,7]最后找到8,最优路径为下图,0->1->2->5->8 加起来的步数为16位最低,广度优先搜索算法有点类于动态规划算法都是获取全局最优解。
在这里插入图片描述
以3.1的数据结构为例实现广度优先搜索

    /**
     * 广度优先查询算法
     *
     * @param start 起点
     * @param end 终点
     */
    public void bfs(int start, int end) {
        if (start == end) return;
        boolean[] visited = new boolean[size];
        //是用来记录已经被访问的顶点,用来避免顶点被重复访问。如果顶点s被访问,那相应的visited[s]会被设置为true。
        visited[start] = true;
        /*
         用来存储已经被访问、但相连的顶点还没有被访问的顶点。因为广度优先搜索是逐层访问的,也就是说,我们只有把第k层的顶点都访问完成
         之后,才能访问第k+1层的顶点。当我们访问到第k层的顶点的时候,我们需要把第k层的顶点记录下来,稍后才能通过第k层的顶点来找第k+1层的顶点。所以,我
         们用这个队列来实现记录的功能。
         */
        Queue<Integer> queue = new LinkedList<>();
        queue.add(start);
        /*
        用来记录搜索路径。当我们从顶点s开始,广度优先搜索到顶点t后,prev数组中存储的就是搜索的路径。不过,这个路径是反向存储的。prev[w]存储的是,顶
        点w是从哪个前驱顶点遍历过来的。比如,我们通过顶点2的邻接表访问到顶点3,那prev[3]就等于2。为了正向打印出路径,我们需要递归地来打印,你可以看
        下print()函数的实现方式。
         */
        int[] prev = new int[size];
        for (int i = 0; i < size; ++i) {
            prev[i] = -1;
        }

        //循环停止条件 队列大小为0
        while (queue.size() != 0) {
            //从队列里取出一条数据
            int w = queue.poll();
            //遍历次数等于链表的长度
            for (int i = 0; i < tables[w].size(); ++i) {
                //从链表中取出相邻边值
                int q = tables[w].get(i);
                //如果没有被访问
                if (!visited[q]) {
                    //记录搜索路径的值
                    prev[q] = w;
                    //找到了值打印数据并结束循环
                    if (q == end) {
                        print(prev, start, end);
                        return;
                    }
                    //设置当前节点q为访问过
                    visited[q] = true;
                    //设置当前边值到队列中
                    queue.add(q);
                }
            }
        }
    }

测试广度优先搜索

    public static void main(String[] args) {
        Graph graph = new Graph(9);
        int[][] nums = new int[][]{{0, 1, 2}, {3, 4, 5}, {6, 7, 8}};
        int rowLen = nums.length;
        int colLen = nums[0].length;
        for (int i = 0; i < rowLen; i++) {
            for (int j = 0; j < colLen; j++) {
                if (j < colLen - 1) {
                    graph.addEdge(nums[i][j], nums[i][j + 1]);
                }
                if (i < rowLen - 1) {
                    graph.addEdge(nums[i][j], nums[i + 1][j]);
                }
            }
        }
        //广度优先搜索
        graph.bfs(0, 8);

在这里插入图片描述

3.3.深度优先算法DFS

深度优先搜索(Depth-First-Search),简称DFS。最直观的例子就是“走迷宫”。假设你站在迷宫的某个岔路口,然后想找到出口。你随意选择一个岔路口来走,走着走着发现走不通的时候,你就回退到上一个岔路口,重新选择一条路继续走,直到最终找到出口。这种走法就是一种深度优先搜索策略。走迷宫的例子很容易能看懂,我们现在再来看下,如何在图中应用深度优先搜索,来找某个顶点到另一个顶点的路径,以顶点0到右下角顶点8为例,从0->1->2->5->4->3->6->7->8

在这里插入图片描述

3.3.1.DFS查询单条路径

以3.1的数据结构为例实现深度优先搜索

    /**
     * 深度优先查询算法
     * @param start 起点
     * @param end 终点
     */
    public void dfs(int start, int end) {
        found = false;
        //访问过的标识
        boolean[] visited = new boolean[size];
        //记录搜索过的路径
        int[] prev = new int[size];
        for (int i = 0; i < size; ++i) {
            prev[i] = -1;
        }
        recurDfs(start, end, visited, prev);
        print(prev, start, end);
        found = false;
    }

    /**
     * 递归搜索
     *
     * @param start     起点
     * @param end       终点
     * @param visited 已访问标识
     * @param prev    搜索路径
     */
    private void recurDfs(int start, int end, boolean[] visited, int[] prev) {
        if (found == true) return;
        //设置节点访问过
        visited[start] = true;
        //如果出发和结束地点一样则停止循环
        if (start == end) {
            found = true;
            return;
        }
        for(Integer nextNode: tables[start]){
            //如果当前节点未被访问,则尝试使用
            if (!visited[nextNode]) {
                prev[nextNode] = start;
                //以找到的相邻点的位置作为递归的下一项
                recurDfs(nextNode, end, visited, prev);
            }
        }
    }

3.2章节测试广度优先的代码里把调用graph.bfs(0, 8)改成graph.dfs(0, 8) 后运行结果如下
在这里插入图片描述

3.3.2.DFS查询所有路径

以3.1的数据结构为例实现深度优先搜索

    /**
     * 查找
     *
     * @param start 起点
     * @param end   终点
     * @return 所有路径
     */
    public List<List<Integer>> dfsAll(int start, int end) {
        List<List<Integer>> prevList = new ArrayList<>();
        //访问过的标识
        boolean[] visited = new boolean[size];
        List<Integer> prev = new ArrayList<>();
        //添加起点
        prev.add(start);
        recurDfs(start, end, visited, prev, prevList);
        return prevList;
    }

    /**
     * @param start    起点
     * @param end      终点
     * @param visited  已访问标识
     * @param prev     路径
     * @param prevList 存放所有匹配路径
     */
    private void recurDfs(int start, int end, boolean[] visited, List<Integer> prev, List<List<Integer>> prevList) {
        //设置当前节点已访问
        visited[start] = true;
        for (int nextNode : tables[start]) {
            //已访问的节点则不进行出路
            if (!visited[nextNode]) {
                //把节点添加到路径中
                prev.add(nextNode);
                if (nextNode == end) {
                    //到达终点后把当前的路径添加到所有路径中
                    prevList.add(new ArrayList<>(prev));
                } else {
                    //没找到则递归往深处查找
                    recurDfs(nextNode, end, visited, prev, prevList);
                    //递归查找完设置当前节点为未访问
                    visited[nextNode] = false;
                }
                //移除最后一位元素
                prev.remove(prev.size() - 1);
            }
        }
    }

3.2章节测试广度优先的代码里把调用graph.bfs(0, 8)以下两行代码,运行结果如下图

 		List<List<Integer>> all = graph.dfsAll(0, 8);
        System.out.println(JSONObject.toJSONString(all));

在这里插入图片描述
在这里插入图片描述

总结:

  • 广度优先搜索深度优先搜索是图上的两种最常用、最基本的搜索算法,比起其他高级的搜索算法,比如A*、IDA*等,要简单粗暴,没有什么优化,所以,也被叫作暴力搜索算法。所以,这两种搜索算法仅适用于状态空间不大,也就是说图不大的搜索。
  • 广度优先搜索: 地毯式层层推进,从起始顶点开始,依次往外遍历。广度优先搜索需要借助队列来实现,遍历得到的路径就是,起始顶点到终止顶点的最短路径。
  • 深度优先搜索: 回溯思想,非常适合用递归实现。换种说法,深度优先搜索是借助栈来实现的。
  • 在执行效率方面,深度优先广度优先搜索的时间复杂度都是O(E边数),空间复杂度是O(V顶点数)。

4.带权图和贪心算法

4.1.贪心算法

贪心算法(greedy algorithm 又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,算法得到的是在某种意义上的局部最优解。

在这里插入图片描述
      用贪心算法解决问题的思路,并不总能给出最优解。以上图一个带权图中,我们从顶点S开始,找一条到顶点T的最短路径(路径中边的权值和最小)。贪心算法的解决思路是,每次都选择一条跟当前顶点相连的权最小的边,直到找到顶点T。按照这种思路,我们求出的最短路径是S->A->E->T,路径长度是1+4+4=9。
      这种贪心的选择方式,最终求的路径并不是最短路径(动态规划算法);因为路径S->B->D->T才是最短路径(路径的长度是2+2+2=6)。贪心算法会前面的选择,会影响后面的选择。

4.2.基于带权无向图使用贪心算法查询最优路径

下图以Java代码实现了带权无向图

public class WeightedGraph {
    /**
     * 顶点总数
     */
    private final int size;
    /**
     * 边的总数
     */
    private int count;
    /**
     * 邻接表
     */
    private Queue<Edge>[] queues;

    /**
     * 创建一个含有size个顶点的空加权无向图
     *
     * @param size
     */
    public WeightedGraph(int size) {
        //初始化顶点数量
        this.size = size;
        //初始化边的数量
        this.count = 0;
        //初始化邻接表
        this.queues = new Queue[size];
        for (int i = 0; i < queues.length; i++) {
            queues[i] = new ArrayDeque<>();
        }
    }


    /**
     * 向加权无向图中添加一条边e
     *
     * @param e
     */
    public void addEdge(Edge e) {
        //需要让边e同时出现在e这个边的两个顶点的邻接表中
        int v = e.v;
        int w = e.other(v);
        queues[v].add(e);
        queues[w].add(e);
        //边的数量+1
        count++;
    }

    /**
     * 获取和顶点v关联的所有边
     *
     * @param index
     * @return
     */
    public Queue<Edge> get(int index) {
        return queues[index];
    }

    public static class Edge implements Comparable<Edge> {
        /**
         * 顶点v
         */
        private final int v;
        /**
         * 顶点w
         */
        private final int w;
        /**
         * 当前边的权重
         */
        private final int weight;

        /**
         * 通过顶点v和w,以及权重weight值构造一个边对象
         *
         * @param v
         * @param w
         * @param weight 权重值
         */
        public Edge(int v, int w, int weight) {
            this.v = v;
            this.w = w;
            this.weight = weight;
        }

        /**
         * 获取边的权重值
         *
         * @return
         */
        public int weight() {
            return weight;
        }

        /**
         * 获取边上除了顶点vertex外的另外一个顶点
         *
         * @return
         */
        public int other(int vertex) {
            if (vertex == v) {
                return w;
            } else {
                return v;
            }
        }

        @Override
        public int compareTo(Edge that) {
            //使用一个遍历记录比较的结果
            int cmp;
            if (this.weight() > that.weight()) {
                //如果当前边的权重值大,则让cmp=1;
                cmp = 1;
            } else if (this.weight() < that.weight()) {
                //如果当前边的权重值小,则让cmp=-1;
                cmp = -1;
            } else {
                //如果当前边的权重值和that边的权重值一样大,则让cmp=0
                cmp = 0;
            }
            return cmp;
        }
    }
}

基于上面邻接表结构的带权图使用贪心算法查找最优路径

    /**
     * 根据权重数值低的查询两点之间的最优路径
     * @param start 起点
     * @param end  终点
     * @return
     */
    public List<Integer> searchByWeight(int start, int end) {
        if (start >= count || end >= count) {
            return null;
        }
        //记录途径滤镜
        List<Integer> pathList = new ArrayList<>();
        pathList.add(start);
        //获取起点的所有邻边
        Queue<Edge> edges = queues[start];
        Edge edge;
        //是用来记录已经被访问的顶点,用来避免顶点被重复访问。如果顶点q被访问,那相应的visited[last]会被设置为true。
        boolean[] visited = new boolean[size];
        int last = start;
        while (edges != null) {
            final int finalLast = last;
            //过滤掉已经访问过的节点过滤掉并且不能往回走然后获取权重值最小的路径
            edge = edges.stream()
                    .filter(e -> !visited[e.other(finalLast)] && e.other(finalLast) > finalLast)
                    .min(Edge::compareTo).get();
            visited[last] = true;
            last = edge.other(last);
            pathList.add(last);
            //找到路径则推出循环
            if (last == end) {
                break;
            }
            //继续往下查找
            edges = queues[last];
        }
        return pathList;
    }

上图最难理解的代码段为下面这段,其实分段看就很简单

  • 先从所有边里面通过filter函数过滤掉已访问过的节点和比上一节点值更小(避免走回头路)
  • 然后通过min函数找到权重值最小的一条边
       edge = edges.stream()
                    .filter(e -> !visited[e.other(finalLast)] && e.other(finalLast) > finalLast)
                    .min(Edge::compareTo).get();

如果是把addEdge函数第四行注释掉把无向图改成单向图则不需要上面过滤代码了

       edge = edges.stream().min(Edge::compareTo).get();

测试带权图

  • 使用BiMap(双休map)给每个顶点取一个对应的下标值用于在图中存储对应边
  • 初始化顶点和边的映射信息和权重值
  • 通过贪心算法实现的查询函数查找最优路径的数值后通过BiMap获取数值的顶点名称并打印

BiMap需要引入第三方依赖,我使用的是hutool工具包,也可以使用guava工具包下的BiMap

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-core</artifactId>
            <version>5.8.9</version>
        </dependency>
    public static void main(String[] args) {
        //S=0 A=1 B=2 C=3  D=4  E=5 F=6  T=7
        BiMap<String, Integer> biMap = new BiMap<>(new HashMap<>());
        biMap.put("S", 0);
        biMap.put("A", 1);
        biMap.put("B", 2);
        biMap.put("C", 3);
        biMap.put("D", 4);
        biMap.put("E", 5);
        biMap.put("F", 6);
        biMap.put("T", 7);

        WeightedGraph weightedGraph = new WeightedGraph(8);
        //添加顶点S到顶点 A,B,C的映射和权重
        weightedGraph.addEdge(new WeightedGraph.Edge(0, 1, 1));
        weightedGraph.addEdge(new WeightedGraph.Edge(0, 2, 2));
        weightedGraph.addEdge(new WeightedGraph.Edge(0, 3, 3));

        //添加顶点A到E,F的映射和权重
        weightedGraph.addEdge(new WeightedGraph.Edge(1, 5, 4));
        weightedGraph.addEdge(new WeightedGraph.Edge(1, 6, 5));

        //添加顶点B到D,f的映射和权重
        weightedGraph.addEdge(new WeightedGraph.Edge(2, 4, 2));
        weightedGraph.addEdge(new WeightedGraph.Edge(2, 6, 6));

        //添加顶点B到D,f的映射和权重
        weightedGraph.addEdge(new WeightedGraph.Edge(3, 4, 3));
        weightedGraph.addEdge(new WeightedGraph.Edge(3, 5, 2));

        weightedGraph.addEdge(new WeightedGraph.Edge(4, 7, 2));
        weightedGraph.addEdge(new WeightedGraph.Edge(5, 7, 4));
        weightedGraph.addEdge(new WeightedGraph.Edge(6, 7, 1));

        List<Integer> pathList = weightedGraph.searchByWeight(0, 7);
        StringBuilder sb = new StringBuilder();
        for (Integer path : pathList) {
            sb.append(biMap.getKey(path)).append("->");
        }
        sb.deleteCharAt(sb.length() - 1);
        sb.deleteCharAt(sb.length() - 1);
        System.out.println(sb);
    }

运行结果如下,可以看到上4.1.章节中带权图样例中最优路径是一致的。

在这里插入图片描述

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

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

相关文章

混合精度训练,FP16加速训练,降低内存消耗

计算机中的浮点数表示&#xff0c;按照IEEE754可以分为三种&#xff0c;分别是半精度浮点数、单精度浮点数和双精度浮点数。三种格式的浮点数因占用的存储位数不同&#xff0c;能够表示的数据精度也不同。 Signed bit用于控制浮点数的正负&#xff0c;0表示正数&#xff0c;1表…

MAC地址IP地址 端口

网络结构&#xff1a; 服务器-客户机&#xff08;C/S&#xff09;Client-Server结构&#xff0c;如QQ,LOL都拥有客户端 优点&#xff1a;响应速度快&#xff0c;形式多样&#xff0c;安全新较高缺点&#xff1a;安装软件和维护&#xff0c;不能跨平台LINUX/windows/MAC浏览器-…

C语言——柔性数组

目录0. 前言1. 思维导图2. 柔性数组的特点3. 柔性数组的使用4. 柔性数组的优势5. 结语0. 前言 柔性数组是在C99标准时引入&#xff1a; 结构中的最后一个元素允许是未知大小的数组&#xff0c;这就叫柔性数组成员。 代码示例&#xff1a; typedef struct flexible_arr {int a…

leetcode 1011. Capacity To Ship Packages Within D Days(D天内运送包裹的容量)

数组的每个元素代表每个货物的重量&#xff0c;注意这个货物是有先后顺序的&#xff0c;先来的要先运输&#xff0c;所以不能改变这些元素的顺序。 要days天内把这些货物全部运输出去&#xff0c;问所需船的最小载重量。 思路&#xff1a; 数组内数字顺序不能变&#xff0c;就…

【Storm】【一】简介

介绍 1.1 简介 Storm 是 Apache 旗下免费开源的分布式实时计算框架。Storm可以轻松、可靠地处理无限数据流&#xff0c;对实时分析、在线机器学习、连续计算、分布式RPC&#xff0c;ETL等提供高效、可靠的支持。 1.2 什么是分布式计算 分布式计算&#xff0c;将一个任务分解…

云解析专家解密《狂飙》剧中京海市人民政府域名访问真相

这段时间&#xff0c;最火的国产剧当属《狂飙》无疑。尽管不久前迎来了大结局&#xff0c;但这部剧的热度依然不减&#xff0c;成为大家茶余饭后热议的话题。 出于对这部剧的喜爱&#xff0c;小编开启了二刷模式&#xff0c;在重温剧情的同时&#xff0c;对于其中的一些细节也…

Windows 10注册表损坏怎么办?

注册表是一个复杂的数据库&#xff0c;如果不进行维护&#xff0c;它就会填充损坏的和孤立的注册表项。尤其是对Windows进行升级时&#xff0c;损坏或丢失的注册表项也会不断累积&#xff0c;从而影响您的系统性能。如果您的Windows 10系统正在经历注册表损坏的问题&#xff0c…

SpringBoot(powernode)

SpringBoot&#xff08;powernode&#xff09; 目录SpringBoot&#xff08;powernode&#xff09;一、第一个SpringBoot项目二、打jar包启动测试三、个性化banner四、常用注解4.1回顾spring和java的注解4.1.1 spring标注类的注解&#xff1a;4.1.2 spring标注方法的注解&#x…

linux的文件权限介绍

文件权限 在linux终端输入 ls -lh 出现下面界面 介绍 基本信息 其中的开头代表着文件类型和权限 而 root 和kali 则分别代表用户名和用户组名用户名顾名思义就是这个文件属于哪一个用户用户组是说自己在写好一个文件后&#xff0c;这个文件是属于该用户所有&#xff0c;…

剑指 Offer 63. 股票的最大利润

剑指 Offer 63. 股票的最大利润 难度&#xff1a;middle\color{orange}{middle}middle 题目描述 假设把某股票的价格按照时间先后顺序存储在数组中&#xff0c;请问买卖该股票一次可能获得的最大利润是多少&#xff1f; 示例 1: 输入: [7,1,5,3,6,4] 输出: 5 解释: 在第 2 …

尚硅谷《Redis7》(小白篇)

尚硅谷《Redis7 》&#xff08;小白篇&#xff09; 02 redis 是什么 官方网站&#xff1a; https://redis.io/ 作者 Git Hub https://github.com/antirez 03 04 05 能做什么 06 去哪下 Download https://redis.io/download/ redis中文文档 https://www.redis.com.cn/docu…

详解量子计算:相位反冲与相位反转

前言 本文需要对量子计算有一定的了解。需要的请翻阅我的量子专栏&#xff0c;这里不再涉及基础知识的科普。 量子相位反冲是什么&#xff1f; 相位反转&#xff08;phase kickback&#xff09;是量子计算中的一种现象&#xff0c;通常在量子算法中使用&#xff0c;例如量子…

亲测实现PopupWindow显示FlowLayout流式布局带固定文本/按钮(位置可改)

实现&#xff1a;动态绘制并带固定文本/按钮&#xff0c;固定文本/按钮固定在最后一行的右边且垂直居中&#xff0c;若最后一行放不下&#xff0c;则新开一行放到新行的右边且垂直居中&#xff08;新行的行高跟前面的一样&#xff09;&#xff0c;可单选、多选、重置。 注&…

SQL零基础入门学习(六)

SQL零基础入门学习&#xff08;六&#xff09; SQL零基础入门学习&#xff08;五&#xff09; SQL 通配符 通配符可用于替代字符串中的任何其他字符。 SQL 通配符用于搜索表中的数据。 在 SQL 中&#xff0c;可使用以下通配符&#xff1a; 演示数据库 在本教程中&#xff…

robotframework自动化测试环境搭建

环境说明 win10 python版本&#xff1a;3.8.3rc1 安装清单 安装配置 selenium安装 首先检查pip命令是否安装&#xff1a; C:\Users\name>pipUsage:pip <command> [options]Commands:install Install packages.download Do…

掌握这10个测试方法,软件测试已登堂入室

当然还有很多测试方法&#xff0c;这些要根据实际不同应用场景而变化&#xff0c;这里就以键盘为例子进行测试方法的讲解。 1.需求测试 需求这种大家都知道这种主要是就是甲方或者项目经理写的&#xff0c;或者某些人需要什么我们就给什么&#xff0c;一般来讲一个东西给到…

API 接口应该如何设计?如何保证安全?如何签名?如何防重?

说明&#xff1a;在实际的业务中&#xff0c;难免会跟第三方系统进行数据的交互与传递&#xff0c;那么如何保证数据在传输过程中的安全呢&#xff08;防窃取&#xff09;&#xff1f;除了https的协议之外&#xff0c;能不能加上通用的一套算法以及规范来保证传输的安全性呢&am…

训练一个ChatGPT需要多少数据?

“风很大”的ChatGPT正在席卷全球。作为OpenAI在去年底才刚刚推出的机器人对话模型&#xff0c;ChatGPT在内容创作、客服机器人、游戏、社交等领域的落地应用正在被广泛看好。这也为与之相关的算力、数据标注、自然语言处理等技术开发带来了新的动力。自OpenAI发布ChatGPT以来&…

好用的SQL工具盘点:从学习到工作总有一款适合你

标题一.入坑阶段&#xff08;学习入门&#xff09;&#xff1a; 这个阶段一般就是小白&#xff0c;想学习SQL语言&#xff0c;然后到处找软件&#xff0c;找免费破解版找半天&#xff0c;找到了半天安装不下来&#xff0c;还可能把自己电脑搞中毒。 其实对于小白来说&#xf…

Shiro常用的Filter过滤器

Shiro常用的Filter过滤器 核心过滤器&#xff1a;DefaultFilter&#xff0c;配置了的相应路径的相应的拦截器进行处理 常用过滤器 authc&#xff1a;org.apache.shiro.web.filter.authc.FormAuthenticationFilter 需要认证登录才能访问 user&#xff1a;org.apache.shiro.w…