• 【算法训练营】 - ⑩ 并查集与图


    并查集

    • 有若干个样本a、b、c、d…类型假设是V。
    • 在并查集中一开始认为每个样本都在单独的集合里。
    • 用户可以在任何时候调用如下两个方法:
      1. boolean isSameSet(V x, V y) : 查询样本x和样本y是否属于一个集合。
      2. void union(V x, V y) : 把x和y各自所在集合的所有样本合并成一个。
    • isSameSet和union方法的代价越低越好。(时间复杂度O(1))。

    并查集特征

    1)每个节点都有一条往上指的指针。
    2)节点a往上找到的头节点,叫做a所在集合的代表节点。
    3)查询x和y是否属于同一个集合,就是看看找到的代表节点是不是一个。
    4)把x和y各自所在集合的所有点合并成一个集合,只需要小集合的代表点挂在大集合的代表点的下方即可。

    并查集的优化

    1)节点往上找代表点的过程,把沿途的链变成扁平的。
    2)小集合挂在大集合的下面。
    3)如果方法调用很频繁,那么单次调用的代价为O(1),两个方法都如此。

    /**
     * 并查集
     * 提供两个方法:
     * 1、查询两个元素是否在同一个集合中  isSameSet
     * 2、把两个元素所在的集合合并成一个  union
     *
     * 假定用户一次直接给出所有集合
     */
    public class UnionSearch<V> {
        // 节点位置表
        Map<V,Node> nodes;
        // 查找当前节点对应的父节点表
        Map<Node,Node> parents;
        // 作为代表元素表 k代表元素 v 集合中有几个元素
        Map<Node,Integer> size;
        public UnionSearch(List<V> list){
            nodes = new HashMap<>();
            parents = new HashMap<>();
            size = new HashMap<>();
            for(V v : list){
                // 出事时每个元素单独作为一个结合
                Node node = new Node(v);
                nodes.put(v,node);
                parents.put(node,node);
                size.put(node,1);
            }
        }
    
    
        public  boolean isSameSet(V v1,V v2){
            if(!nodes.containsKey(v1)
                    || !nodes.containsKey(v2)){
                return false;
            }
            Node node1 = findFather(v1);
            Node node2 = findFather(v2);
            if(node1 == node2){
                return true;
            }
    
            return false;
        }
    
    
        public void union(V v1,V v2){
            if(!nodes.containsKey(v1)
                    || !nodes.containsKey(v2)){
                return;
            }
            Node node1 = findFather(v1);
            Node node2 = findFather(v2);
            // 两个元素不在一个集合中才合并
            if(node1 != node2){
               // 看谁的元素多
                Node big = size.get(node1) > size.get(node2) ? node1 : node2;
                Node small = size.get(node1) <= size.get(node2) ? node1 : node2;
                // 小的挂在大的上面
                parents.put(small,big);
                size.put(big,size.get(big) + size.get(small));
                size.remove(small);
            }
    
        }
    
        // 调用此方法是一定保证集合中有此元素
        private Node findFather(V node){
            // 做一个扁平化优化,把从当前节点网上找的节点都加入容器中
            Stack<Node> stack = new Stack<>();
            Node cur = nodes.get(node);
            while (parents.get(cur) != cur){
                // 路径上经过的节点加入容器
                stack.push(cur);
                // 只要此时节点不等于父节点,说明不是代表节点就一直往上找
                cur = parents.get(cur);
            }
    
            // 把所有经过节点直接和代表节点相连,下次查找是O(1)
            while (!stack.isEmpty()){
                parents.put(stack.pop(),cur);
            }
            //跳出来说明已经找到了
            return cur;
        }
    
        public static void main(String[] args) {
            List<String> list = new ArrayList();
    
            list.add("a");
            list.add("b");
            list.add("c");
            list.add("d");
            list.add("e");
            list.add("f");
            list.add("g");
            UnionSearch<String> unionSearch = new UnionSearch<>(list);
            unionSearch.union("a","b");
            unionSearch.union("c","b");
            System.out.println(unionSearch.isSameSet("a", "g"));
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101

    1. 由点的集合和边的集合构成。
    2. 虽然存在有向图和无向图的概念,但实际上都可以用有向图来表达。
    3. 边上可能带有权值。

    图结构的表达

    1. 邻接表法
    2. 邻接矩阵法
    3. 除此之外还有其他众多的方式

    图的面试题如何搞定?

    图的算法都不算难,只不过coding的代价比较高

    1. 先用自己最熟练的方式,实现图结构的表达
    2. 在自己熟悉的结构上,实现所有常用的图算法作为模板
    3. 把面试题提供的图结构转化为自己熟悉的图结构,再调用模板或改写即可

    图的数据结构

    // 点
    public class Node {
        // 结点值
        public int value;
        // 入度
        public int in;
        // 出度
        public int out;
        // 邻居
        List<Node> nexts;
        // 存一份边
        List<Edge> edges;
    
        public Node(int value){
            this.value = value;
            // 新结点入度为0
            this.in = 0;
            // 新结点出度为0
            this.out = 0;
            // 新结点邻居为空
            this.nexts = new ArrayList<>();
            this.edges = new ArrayList<>();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    // 边
    public class Edge {
        // 权重
        public int weight;
        // 起点边
        public Node from;
        // 终点边
        public Node to;
        public Edge(Node from,Node to,int weight){
            this.from = from;
            this.to = to;
            this.weight = weight;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    // 图
    public class Graph {
        // 点的集合
        HashMap<Integer,Node> nodes;
        // 边的集合
        HashSet<Edge> edges;
        public Graph(){
            this.nodes = new HashMap<>();
            this.edges = new HashSet<>();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    生成图

    public class GraphGenerator {
        // matrix 所有的边
        // N*3 的矩阵
        // [weight, from节点上面的值,to节点上面的值]
        public static Graph graphGenerator(Integer[][] 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 edge = new Edge(fromNode,toNode,weight);
                // fromNode 几点出度加一,邻居加一
                fromNode.out ++;
                fromNode.nexts.add(toNode);
                fromNode.edges.add(edge);
                // toNode入度加一
                toNode.in ++;
                // 图中边集加一
                graph.edges.add(edge);
            }
            return graph;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    图算法

    广度优先遍历

    1. 利用队列实现
    2. 从源节点开始依次按照宽度进队列,然后弹出
    3. 每弹出一个点,把该节点所有没有进过队列的邻接点放入队列
    4. 直到队列变空

    注意要点,不能重复遍历,遍历过的点应该排除。

    package class16;
    
    import java.util.HashSet;
    import java.util.LinkedList;
    import java.util.Queue;
    
    public class Code01_BFS {
    
    	// 从node出发,进行宽度优先遍历
    	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)) {
    					set.add(next);
    					queue.add(next);
    				}
    			}
    		}
    	}
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    深度优先遍历

    1. 利用栈实现
    2. 从源节点开始把节点按照深度放入栈,然后弹出
    3. 每弹出一个点,把该节点下一个没有进过栈的邻接点放入栈
    4. 直到栈变空
    package class16;
    
    import java.util.HashSet;
    import java.util.Stack;
    
    public class Code02_DFS {
    
    	public static void dfs(Node node) {
    		if (node == null) {
    			return;
    		}
    		Stack<Node> stack = new Stack<>();
    		HashSet<Node> set = new HashSet<>();
    		stack.add(node);
    		set.add(node);
    		System.out.println(node.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;
    				}
    			}
    		}
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    图的拓扑排序算法

    1. 在图中找到所有入度为0的点输出
    2. 把所有入度为0的点在图中删掉,继续找入度为0的点输出,周而复始
    3. 图的所有点都被删除后,依次输出的顺序就是拓扑排序

    要求:有向图且其中没有环
    应用:事件安排、编译顺序

    package class16;
    
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.LinkedList;
    import java.util.List;
    import java.util.Queue;
    
    public class Code03_TopologySort {
    
    	// directed graph and no loop
    	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.add(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.add(next);
    				}
    			}
    		}
    		return result;
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    最小生成树K算法

    /**
     * 最小生成树算法之Kruskal
     * 1)总是从权值最小的边开始考虑,依次考察权值依次变大的边(优先级队列)
     * 2)当前的边要么进入最小生成树的集合,要么丢弃
     * 3)如果当前的边进入最小生成树的集合中不会形成环,就要当前边
     * 4)如果当前的边进入最小生成树的集合中会形成环,就不要当前边
     * 5)考察完所有边之后,最小生成树的集合也得到了
     */
    public class Kruskal {
        public static Set<Edge> kruskal(Graph graph){
            if(graph == null){
                return null;
            }
            Set<Edge> res = new HashSet<>();
            // 先把所有点都加入并查集
            List<Node> nodeList = (List<Node>)graph.nodes.values();
            UnionSearch<Node> unionSearch = new UnionSearch<>(nodeList);
            // 获取所有边
            PriorityQueue<Edge> queue = new PriorityQueue<>(new MyComparator());
            for(Edge item : graph.edges){
                queue.offer(item);
            }
            while (!queue.isEmpty()){
                Edge cur = queue.poll();
                Node from = cur.from;
                Node to = cur.to;
                if(!unionSearch.isSameSet(from,to)){
                   res.add(cur);
                    // 加入并查集
                    unionSearch.union(from,to);
                }
            }
            
            return res;
    
        }
        
        private void makeUnion(List<Node> list,UnionSearch<Node> unionSearch){
            for(Node item : list){
                
            }
        }
    
        static class MyComparator implements Comparator<Edge>{
    
            @Override
            public int compare(Edge o1, Edge o2) {
                return o1.weight - o2.weight;
            }
        }
    
    
         static class UnionSearch<V> {
            // 节点位置表
            Map<V,Node2> nodes;
            // 查找当前节点对应的父节点表
            Map<Node2,Node2> parents;
            // 作为代表元素表 k代表元素 v 集合中有几个元素
            Map<Node2,Integer> size;
            public UnionSearch(List<V> list){
                nodes = new HashMap<>();
                parents = new HashMap<>();
                size = new HashMap<>();
                for(V v : list){
                    // 出事时每个元素单独作为一个结合
                    Node2 node = new Node2(v);
                    nodes.put(v,node);
                    parents.put(node,node);
                    size.put(node,1);
                }
            }
    
    
            public  boolean isSameSet(V v1,V v2){
                if(!nodes.containsKey(v1)
                        || !nodes.containsKey(v2)){
                    return false;
                }
                Node2 node1 = findFather(v1);
                Node2 node2 = findFather(v2);
                if(node1 == node2){
                    return true;
                }
    
                return false;
            }
    
    
            public void union(V v1,V v2){
                if(!nodes.containsKey(v1)
                        || !nodes.containsKey(v2)){
                    return;
                }
                Node2 node1 = findFather(v1);
                Node2 node2 = findFather(v2);
                // 两个元素不在一个集合中才合并
                if(node1 != node2){
                    // 看谁的元素多
                    Node2 big = size.get(node1) > size.get(node2) ? node1 : node2;
                    Node2 small = size.get(node1) <= size.get(node2) ? node1 : node2;
                    // 小的挂在大的上面
                    parents.put(small,big);
                    size.put(big,size.get(big) + size.get(small));
                    size.remove(small);
                }
    
            }
    
            // 调用此方法是一定保证集合中有此元素
            private Node2 findFather(V node){
                // 做一个扁平化优化,把从当前节点网上找的节点都加入容器中
                Stack<Node2> stack = new Stack<>();
                Node2 cur = nodes.get(node);
                while (parents.get(cur) != cur){
                    // 路径上经过的节点加入容器
                    stack.push(cur);
                    // 只要此时节点不等于父节点,说明不是代表节点就一直往上找
                    cur = parents.get(cur);
                }
    
                // 把所有经过节点直接和代表节点相连,下次查找是O(1)
                while (!stack.isEmpty()){
                    parents.put(stack.pop(),cur);
                }
                //跳出来说明已经找到了
                return cur;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129

    最小生成树p算法

    /**
     * 最小生成树,p算法
     * 1)可以从任意节点出发来寻找最小生成树
     * 2)某个点加入到被选取的点中后,解锁这个点出发的所有新的边
     * 3)在所有解锁的边中选最小的边,然后看看这个边会不会形成环
     * 4)如果会,不要当前边,继续考察剩下解锁的边中最小的边,重复3)
     * 5)如果不会,要当前边,将该边的指向点加入到被选取的点中,重复2)
     * 6)当所有点都被选取,最小生成树就得到了
     */
    public class Prim {
        public static Set<Edge> prim(Graph graph){
            if(graph == null){
                return null;
            }
            // 生成优先级队列
            PriorityQueue<Edge> queue = new PriorityQueue<>(new MyComparator());
            Set<Edge> edgeSet = new HashSet<>();
            Set<Node> nodeSet = new HashSet<>();
            Set<Edge> res = new HashSet<>();
            List<Node> nodeList = (List<Node>)graph.nodes.values();
            for(Node node : nodeList){ // 防止森林,如果明确没有森林可不要此循环。
                if(!nodeSet.contains(node)){
                    // 解锁点的所有边
                    nodeSet.add(node);
                    for(Edge temp : node.edges){
                        if(!edgeSet.contains(temp)){
                            queue.offer(temp);
                            edgeSet.add(temp);
                        }
                    }
                    while (!queue.isEmpty()){
                        // 权重最小的边
                        Edge cur = queue.poll();
                        // 此时from节点已经在nodeSet结合中解锁出来了,只需判断to节点是否解锁了
                        if(!nodeSet.contains(cur.to)){
                            // 要这条边
                            res.add(cur);
                            // 加入点的集合
                            nodeSet.add(cur.to);
                            // 解锁cur.to的所有边
                            for (Edge e : cur.to.edges){
                                if(!edgeSet.contains(e)){
                                    queue.offer(e);
                                    edgeSet.add(e);
                                }
                            }
                        }
                    }
                }
            }
            return res;
        }
       static class MyComparator implements Comparator<Edge>{
           @Override
           public int compare(Edge o1, Edge o2) {
               return o1.weight - o2.weight;
           }
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    Dijkstra算法

    1. Dijkstra算法必须指定一个源点
    2. 生成一个源点到各个点的最小距离表,一开始只有一条记录,即原点到自己的最小距离为0,源点到其他所有点的最小距离都为正无穷大
    3. 从距离表中拿出没拿过记录里的最小记录,通过这个点发出的边,更新源点到各个点的最小距离表,不断重复这一步
    4. 源点到所有的点记录如果都被拿过一遍,过程停止,最小距离表得到了
    • 优化点:在distanceMap中查找起点到其他点的最短距离是用遍历的方式时间复杂度是O(N),如果用小根堆,可以让时间复杂度降到O(logn),但是因为会更新加入到小根堆中的距离数据,一般的小根堆不能自动调整,所以要自己手动改写小根堆。
    /**
     * 1)Dijkstra算法必须指定一个源点
     * 2)生成一个源点到各个点的最小距离表,一开始只有一条记录,即原点到自己的最小距离为0,源点到其他所有点的最小距离都为正无穷大
     * 3)从距离表中拿出没拿过记录里的最小记录,通过这个点发出的边,更新源点到各个点的最小距离表,不断重复这一步
     * 4)源点到所有的点记录如果都被拿过一遍,过程停止,最小距离表得到了
     */
    public class Dijkstra {
        
        public static Map<Node2,Integer> dijkstra1(Node2 node){
            if(node == null){
                return null;
            }
            // 距离表,一开始只有出发点到出发点的距离为0
            Map<Node2,Integer> distanceMap = new HashMap<>();
            distanceMap.put(node,0);
            // 去重黑名单,每次选举最小记录时排除此表中的数据
            Set<Node2> noSelect = new HashSet<>();
            // 获取最小记录点(出发点到某一点的最短距离)
            Node2 minDistance = getMinAndNoSelect(distanceMap,noSelect);
            while (minDistance != null){ // 把表中数据都遍历完
                for (Edge temp : minDistance.edges){
                    // 这个点往能出去的所有边能不能更新表中的距离
                    Node2 to = temp.to;
                    if(!distanceMap.containsKey(to)){
                        // 如果表中不包含这个点,说明还没找到过重from点到这个距离,直接加入表中
                        distanceMap.put(to,distanceMap.get(minDistance) + temp.weight);
                    }else{
                        distanceMap.put(to,Math.min(distanceMap.get(to),distanceMap.get(minDistance) + temp.weight));
                    }
                }
                minDistance = getMinAndNoSelect(distanceMap,noSelect);
                
            }
            
            return distanceMap;
        }
        
        
        private static Node2 getMinAndNoSelect(Map<Node2,Integer> distanceMap,Set<Node2> noSelcet){
            // 由于Dijkstra算法中边都是非负的用0就可以了
            int min = 0;
            Node2 res = null;
            // 遍历距离表
            for (Map.Entry<Node2, Integer> item : distanceMap.entrySet()){
                Node2 key = item.getKey();
                if(noSelcet.contains(key)){
                    continue;
                }
                // 比较大小
                if(item.getValue() < min){
                    min = item.getValue();
                    res = item.getKey();
                }
            }
            noSelcet.add(res);
            return res;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
  • 相关阅读:
    数据和埋点的通俗解释
    学习Vue3 第十章(实操组件和认识less sass 和 scoped)
    ImportError: /lib/libgdal.so.26: undefined symbol: sqlite3_column_table_name
    oracle 临时表 在sql 里面用完要删除吗
    从业务开发中学习和理解架构设计
    从不同的正态分布中抽取随机数randn()函数
    ViewModel 源码设计思路分析
    Typora收费后我换了个Markdown编辑器(Marktext)
    在Linux上安装Oracle相关产品,常用文件的默认路径
    Hashing to elliptic curve算法改进
  • 原文地址:https://blog.csdn.net/weixin_42274953/article/details/126184983