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

2023-11-11

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.章节中带权图样例中最优路径是一致的。

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • DNG格式解析

    Author Maddock Date 2015 04 22 转载请注明出处 http www cnblogs com adong7639 p 4446828 html DNG格式基本概念 DNG格式是在TIFF的基础上扩展出来的 要了解D
  • 数据结构与算法学习总结(六)——字符串的模式匹配算法

    基本概念 字符串是一种特殊的线性表 即元素都是 字符 的线性表 字符是组成字符串的基本单位 字符的取值依赖于字符集 例如二进制的字符集为0 1 则取值只能为 0 1 再比如英语语言 则包括26个字母外加标点符号 例如 abcde 就是一个字
  • SDUT--OJ《数据结构与算法》实践能力专题训练6 图论

    A 数据结构实验之图论一 基于邻接矩阵的广度优先搜索遍历 Description 给定一个无向连通图 顶点编号从0到n 1 用广度优先搜索 BFS 遍历 输出从某个顶点出发的遍历序列 同一个结点的同层邻接点 节点编号小的优先遍历 Input
  • 手把手教你实现一个向量

    文章目录 什么是向量 向量提供哪些接口 实现 宏定义 定义类 成员变量 构造函数与析构函数 构造函数 析构函数 成员函数 size get r put r e expand insert r e remove lo hi remove r
  • 数据结构--图的遍历(广度优先遍历、深度优先遍历)

    目录 图的遍历 广度优先遍历 BFS 广度优先遍历的代码实现 编辑 广度优先遍历序列 编辑 遍历序列的可变性 编辑 BFS算法完整版 编辑 广度优先遍历复杂度分析 广度优先生成树 广度优先生成森林 回顾广度优先遍历 深度优先遍历 DFS 回
  • 【代码随想录】回溯算法刷题

    代码随想录 回溯算法 组合 组合总和 III 电话号码的字母组合 组合总和 I 组合总和 II 分隔回文串 复原 IP 地址 子集 I 子集 II 递增子序列 全排列 I 全排列 II 重新安排行程 hard N 皇后 hard 解数独 h
  • 数据结构——计算节点个数和二叉树高度(C语言版)

    摘自 数据结构 计算节点个数和二叉树高度 C语言版 作者 正弦定理 发布时间 2020 12 12 23 27 09 网址 https blog csdn net chinesekobe article details 111086664
  • 浮生六记

    浮生六记 目录 浮生六记卷一 闺房记乐 002 浮生六记卷二 闲情记趣 015 浮生六记卷三 坎坷记愁 022 浮生六记卷四 浪游记快 034 浮生六记 2 浮生六记卷一 闺房记乐 余生乾隆癸未冬十一月二十有二日 正值太平盛世 且在 衣冠之
  • 12.29练习总结

    第一题 迷宫 题目描述 给定一个N M 方格的迷宫 迷宫里有 T处障碍 障碍处不可通过 在迷宫中移动有上下左右四种方式 每次只能移动一个方格 数据保证起点上没有障碍 给定起点坐标和终点坐标 每个方格最多经过一次 问有多少种从起点坐标到终点坐
  • CRC校验(二)

    CRC校验 二 参考 https blog csdn net liyuanbhu article details 7882789 https www cnblogs com esestt archive 2007 08 09 848856
  • 用指针访问一维数组

    文章目录 顺序查找 数组方式实现 指针实现方式 对一位数组元素的访问有三种方式 指针变量的关系运算 引例 数组实现方式 主函数 指针实现方式 主函数 一维数组作为函数的参数 实际应用 顺序查找 要求用指针实现 在整数集合r中顺序查找与给定值
  • 数据结构与算法-列表(双向链表)设计及其排序算法

    0 概述 本文主要涵盖列表 双向链表 的设计及其排序算法的总结 列表是一种典型的动态存储结构 其中的数据 分散为一系列称作节点 node 的单位 节点之间通过指针相互索引和访问 为了引入新节点或删除原有节点 只需在局部调整少量相关节点之间的
  • 数理统计知识整理——回归分析与方差分析

    题记 时值我的北科研究生第一年下 选学 统计优化 课程 备考促学 成此笔记 以谨记 1 线性回归 1 1 原理分析 要研究最大积雪深度x与灌溉面积y之间的关系 测试得到近10年的数据如下表 使用线性回归的方法可以估计x与y之间的线性关系 线
  • 【LeetCode:1038. 从二叉搜索树到更大和树 | BST+DFS+中序遍历】

    算法题 算法刷题专栏 面试必备算法 面试高频算法 越难的东西 越要努力坚持 因为它具有很高的价值 算法就是这样 作者简介 硕风和炜 CSDN Java领域新星创作者 保研 国家奖学金 高中学习JAVA 大学完善JAVA开发技术栈 面试刷题
  • 力扣543. 二叉树的直径(java DFS解法)

    Problem 543 二叉树的直径 文章目录 题目描述 思路 解题方法 复杂度 Code 题目描述 给你一棵二叉树的根节点 返回该树的 直径 二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 这条路径可能经过也可能不经过根节点 r
  • Leetcode1094. 拼车

    Every day a Leetcode 题目来源 1094 拼车 解法1 差分数组 对于本题 设 a i 表示车行驶到位置 i 时车上的人数 我们需要判断是否所有 a i 都不超过 capacity trips i 相当于把 a 中下标从
  • 【数据结构入门精讲 | 第二篇】一文讲清算法复杂度

    上篇文章中我们引入了算法 数据结构 数据类型等概念 而要想衡量一个算法与数据结构是否为优质的 就需要一个衡量标准 这个衡量标准也是在我们实现一个好的算法时要遵循的原则 目录 基本概念 渐进性态 渐进性态数学表征 算法复杂度的运算 顺序搜索算
  • 【数据结构】双链表的定义和操作

    目录 1 双链表的定义 2 双链表的创建和初始化 3 双链表的插入节点操作 4 双链表的删除节点操作 5 双链表的查找节点操作 6 双链表的更新节点操作 7 完整代码 嗨 我是 Filotimo 很高兴与大家相识 希望我的博客能对你有所帮助
  • 冒泡排序/选择排序/插入排序/快速排序/归并排序/桶排序/堆排序/希尔排序/计数排序/基数排序/二分查找/广度优先搜索/深度优先搜索

    排序算法 冒泡排序 Bubble Sort 通过重复地比较相邻的元素并交换它们 使得最大 或最小 的元素逐渐移动到列表的一端 从而实现排序 选择排序 Selection Sort 在未排序的部分中 选择最小 或最大 的元素 并将其放置在已排
  • LeetCode 2397. 被列覆盖的最多行数,状态压缩优化回溯法

    一 题目 1 题目描述 给你一个下标从 0 开始 大小为 m x n 的二进制矩阵 matrix 另给你一个整数 numSelect 表示你必须从 matrix 中选择的 不同 列的数量 如果一行中所有的 1 都被你选中的列所覆盖 则认为这

随机推荐