岛问题
- 题目要求
-
- 这道题的解法有很多种
- 我采用的是多路递归的方式具体来说就是时间复杂度为O(n)的方式。将已经确定的变成2,看代码
-
package LiKou.Graph; public class IsLand { public static int getLand(int[][] arr) { int sum = 0; for (int i = 0; i < arr.length; i++) { for (int j = 0; j < arr[i].length; j++) { if (arr[i][j] == 2 || arr[i][j] == 0) { continue; } sum++; dfs(i, j, arr); } } return sum; } private static void dfs(int i, int j, int[][] arr) { //如果越界,直接return if (i < 0 || i > arr.length - 1 || j < 0 || j > arr[0].length - 1) { return; } //如果不越界,但是你=0; if (arr[i][j] == 0 || arr[i][j] == 2) { return; } //下面是不等于0的状态,且不越界 arr[i][j] = 2; dfs(i + 1, j, arr); dfs(i - 1, j, arr); dfs(i, j + 1, arr); dfs(i, j - 1, arr); } public static void main(String[] args) { int[][] arr = new int[4][4]; arr[0][0] = 0; arr[0][1] = 1; arr[0][2] = 0; arr[0][3] = 0; arr[1][0] = 1; arr[1][1] = 0; arr[1][2] = 1; arr[1][3] = 1; arr[2][0] = 0; arr[2][1] = 0; arr[2][2] = 1; arr[2][3] = 0; arr[3][0] = 0; arr[3][1] = 1; arr[3][2] = 0; arr[3][3] = 1; int i = IsLand.getLand(arr); System.out.println(i); } }
-
问题来了:我现在有一个非常大的二维数组,我想设计一个并行程序(也就是多个cpu),让运行时间成倍的往下降。
引出并查集
- 并查集是一个支持集合合并,又非常快速的一个数据结构。(需要提前给定一组数据为初始化)
- 并查集的三个方法:(注意:三个方法的时间复杂度都是O(1),参考文献,算法导论23章,证明问什么是O(1)的时间复杂度,先辈用了25年,用的时候知道时间复杂度是O(1)即可)。
- isSame(T t1,T t2):判断两个元素是否属于同一个集合
- getFather(T t):寻找该节点的最祖宗节点
- 存在一个优化过程,当寻找父节点的同时,将途径节点押入栈中,当找到最祖宗节点,将途径节点的父节点变为最祖宗节点
- 优化的目的在于;将下一次寻找父节点的时间复杂度变为O(1);
- 当getFather的运行次数越趋紧于N,时间复杂度就越趋近于O(1);
- 单独各自空间,因此空间的尺寸不用改变
- union(T t1, T t2):将两个集合合并,合并的过程也是O(1);
- 需要将两个集合合并,因此空间需要累加。
- 关键节点需要进行父子串联
- 看代码
-
package LiKou.UninoAndFind; import java.util.HashMap; import java.util.List; import java.util.Stack; public class Code01_UnionFind { /** * 静态内部类:用于存放节点 * @param <V> */ public static class Node<V> { //确定类型后,给一个value V value; public Node(V v) { value = v; } } //并查集类 public static class UnionSet<V> { public HashMap<V, Node<V>> nodes; //用于存放值类型,节点类型的集合 public HashMap<Node<V>, Node<V>> parents; //用于存放节点对应节点的父节点 public HashMap<Node<V>, Integer> sizeMap; //用于判断一个集合的大小 //一个类的初始化,给定一串数据 public UnionSet(List<V> values) { //进行初始化动作 nodes = new HashMap<>(); parents = new HashMap<>(); sizeMap = new HashMap<>(); //初始化赋值 for (V cur : values) { //根据value创建node Node<V> node = new Node<>(cur); //进行node的初始化(key为值,value为node) nodes.put(cur, node); //刚开始当前节点的祖宗节点就是当前节点 parents.put(node, node); //存放node节点的大小,只有1个,所以就是1 sizeMap.put(node, 1); } } // 寻找头节点 public Node<V> findFather(Node<V> cur) { //创建一个栈 Stack<Node<V>> path = new Stack<>(); //当本节点的父节点不等与当前节点时,直至变为其父节点 while (cur != parents.get(cur)) { //压栈 path.push(cur); //然后将当前节点变为其父节点 cur = parents.get(cur); } //如果栈不为空 while (!path.isEmpty()) { //变换其与祖宗节点的父子关系 parents.put(path.pop(), cur); } //返回祖宗节点 return cur; } //判断是同一个集合里 public boolean isSameSet(V a, V b) { //判断是否在大集合里 if (!nodes.containsKey(a) || !nodes.containsKey(b)) { return false; } //最终判断是否在同一个爹里 return findFather(nodes.get(a)) == findFather(nodes.get(b)); } //合并 public void union(V a, V b) { //前提是两个节点必须放在node里 if (!nodes.containsKey(a) || !nodes.containsKey(b)) { return; } //找到a的父节点 Node<V> aHead = findFather(nodes.get(a)); //找到b的父节点 Node<V> bHead = findFather(nodes.get(b)); //如果两个节点不一样,开始合并 if (aHead != bHead) { int aSetSize = sizeMap.get(aHead); int bSetSize = sizeMap.get(bHead); //找到大的那个 Node<V> big = aSetSize >= bSetSize ? aHead : bHead; //大的是一个,小的就是另一个 Node<V> small = big == aHead ? bHead : aHead; //小的放到big里 parents.put(small, big); //大的集合大小刷新 sizeMap.put(big, aSetSize + bSetSize); //在size里删除small sizeMap.remove(small); } } } }
-
-
根据并查集,由此引出咋们上面提到的用多个cpu 处理一个非常大的岛问题
- 核心思想就是分割加+并查集的合并套路+记录感染点:(自己手写了一下,就不上电子版了,有疑惑的留言)
-
- 好吧,今天先到这~