- 使用 Spring Initializr 创建 Spring Boot 应用程序
- 在Spring Boot中配置Cassandra
- 在 Spring Boot 上配置 Tomcat 连接池
- 将Camel消息路由到嵌入WildFly的Artemis上
前言: 最近发现了一个很有趣的算法,那就是并查集,如果你不了解什么是并查集的话,我希望你能看看下面的文章,因为它真的是一个十分有趣且有用的算法结构!
情景引入:
假设有 a、b、c、d、e、f 六个样本,首先设这几个小样本在自己的集合里 {a}、{b}、{c}、{d}、{e}、{f},针对上述介绍有以下两个操作
boolean isSameSet(a, e)
: 查询 a 和 e 两个样本是否在一个集合void union(a, e)
: 将 a 所在的集合的全体和 e 所在的集合的全体变成一个集合而使用并查集的作用就是,假设有 N 个这样的样本,如果频繁使用上述两个操作能够均摊下来后让时间复杂度为 O(1)
并查集的做法:
下面用并查集的方式,实现了上述情景的两种操作,并且保证了均摊的时间复杂度为 O(1)。可以通过数组代替 HashMap 来提高效率,因为 HashMap 中的常数是大常数
import java.util.HashMap;
import java.util.List;
import java.util.Stack;
// 并查集的实现
public class UnionFind<V> {
// 节点
public static class Node<V> {
V value;
public Node(V value) {
this.value = value;
}
}
// 用来存储每个集合的样本和节点
public HashMap<V, Node<V>> nodes;
// 用来存储子节点和父节点的对应关系(前子后父)
public HashMap<Node<V>, Node<V>> parents;
// 用来存储每个样本所在的集合的样本个数
public HashMap<Node<V>, Integer> sizeMap;
// 初始化并查集(values 表示样本值)
public UnionFind(List<V> values) {
nodes = new HashMap<>();
parents = new HashMap<>();
sizeMap = new HashMap<>();
for (V cur : values) {
Node<V> node = new Node<>(cur);
nodes.put(cur, node);
parents.put(node, node);
sizeMap.put(node, 1);
}
}
// 向上找到代表节点,并且该方法中还做了路径压缩,让该节点及以上的节点直接指向代表节点
public Node<V> findFather(Node<V> cur) {
Stack<Node<V>> path = new Stack<>();
while (cur != parents.get(cur)) {
path.add(cur);
cur = parents.get(cur);
}
while (!path.isEmpty()) {
parents.put(path.pop(), cur);
}
return cur;
}
// 判断 a 和 b 样本是否在一个集合中
public boolean isSameSet(V a, V b) {
return findFather(nodes.get(a)) == findFather(nodes.get(b));
}
// 将 a 和 b 样本所在的集合合并
public void union(V a, V b) {
// 得到 a 和 b 样本的代表节点
Node<V> aHead = findFather(nodes.get(a));
Node<V> bHead = findFather(nodes.get(b));
if (aHead != bHead) {
// 得到这两个集合的样本个数
int aSize = sizeMap.get(aHead);
int bSize = sizeMap.get(bHead);
// 将得到的代表节点进行重定向
// max 表示样本个数多的代表节点
// min 表示样本个数少的代表节点
Node<V> big = aSize > bSize ? aHead : bHead;
Node<V> small = big == aHead ? bHead : aHead;
parents.put(small, big);
sizeMap.put(big, aSize + bSize);
sizeMap.remove(small);
}
}
}
方法: 本题运用并查集的思想,但与上述模板不同,这里将上述模板的 Map 改用数组实现,用数组实现其实效率会更高!
public static int findCircleNum(int[][] isConnected) {
int N = isConnected.length;
UnionFind unionFind = new UnionFind(N);
for (int i = 0; i < N; i++) {
for (int j = i + 1; j < N; j++) {
if (isConnected[i][j] == 1) {
unionFind.union(i, j);
}
}
}
return unionFind.sets();
}
public static class UnionFind {
// 表示父子节点关系 parents[i]=k i为子节点,k为父节点
public int[] parents;
// 表示含有当前节点的集合所有的节点数
public int[] size;
// 辅助节点,用于在压缩路径时充当栈
public int[] help;
// 集合的个数
public int sets;
public UnionFind(int N) {
parents = new int[N];
size = new int[N];
help = new int[N];
sets = N;
for (int i = 0; i < N; i++) {
parents[i] = i;
size[i] = 1;
}
}
public int findFather(int cur) {
int count = 0;
while (cur != parents[cur]) {
help[count++] = cur;
cur = parents[cur];
}
for (int i = 0; i < count; i++) {
parents[help[i]] = cur;
}
return cur;
}
public void union(int a, int b) {
int aHead = findFather(a);
int bHead = findFather(b);
if (aHead != bHead) {
int aSize = size[aHead];
int bSize = size[bHead];
int big = (aSize >= bSize) ? aHead : bHead;
int small = (big == aHead) ? bHead : aHead;
size[big] += size[small];
parents[small] = big;
sets--;
}
}
public int sets() {
return sets;
}
}
力扣链接:省份数量
方法一(递归): 可以从左往右从上往下依次遍历,如果该位置的值为0或2,我们就跳过;如果为1,则将该位置上下左右值为1的位置的值变成2,岛的数量加1,依次遍历完整个数组。重点点就是实现这个 inflect 感染方法来更改数组值为1位置的值
public static int numIslands(char[][] grid) {
int islands = 0;
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[0].length; j++) {
if (grid[i][j] == '1') {
islands++;
infect(grid, i, j);
}
}
}
return islands;
}
public static void infect(char[][] grip, int i, int j) {
if (i < 0 || i == grip.length || j < 0 || j == grip[0].length || grip[i][j] != '1') {
return;
}
grip[i][j] = '2';
infect(grip, i - 1, j);
infect(grip, i + 1, j);
infect(grip, i, j - 1);
infect(grip, i, j + 1);
}
方法二(并查集): 可以使用并查集的方法,我们只需要对每个数组位置进行左和上的 union,就可以求得岛的数量
public static int numIslands2(char[][] grid) {
UnionFind unionFind = new UnionFind(grid);
// 对第一行遍历(用来为后面遍历排除边界条件,注意 0 0 位置不需要遍历,因为它没有左和上的位置)
for(int i=1;i<grid[0].length;i++) {
if(grid[0][i]=='1'&&grid[0][i-1]=='1') {
unionFind.union(unionFind.index(0, i), unionFind.index(0, i-1));
}
}
// 对第一列遍历(用来为后面遍历排除边界条件,注意 0 0 位置不需要遍历,因为它没有左和上的位置)
for(int i=1;i<grid.length;i++) {
if(grid[i][0]=='1'&&grid[i-1][0]=='1') {
unionFind.union(unionFind.index(i, 0), unionFind.index(i-1, 0));
}
}
// 对右下角遍历
for(int i=1;i<grid.length;i++) {
for(int j=1;j<grid[0].length;j++) {
if(grid[i][j]=='1'&&grid[i][j-1]=='1') {
unionFind.union(unionFind.index(i, j), unionFind.index(i, j-1));
}
if((grid[i][j]=='1'&&grid[i-1][j]=='1')) {
unionFind.union(unionFind.index(i, j), unionFind.index(i-1, j));
}
}
}
return unionFind.sets();
}
public static class UnionFind{
// 子节点的父节点
public int[] parents;
// 含有该节点的集合的节点数
public int[] size;
// 辅助节点
public int[] help;
// 集合个数
public int sets;
// 数组列数
public int col;
public UnionFind(char[][] grid) {
col = grid[0].length;
int N = col*grid.length;
parents=new int[N];
size=new int[N];
help=new int[N];
sets=0;
for(int i=0;i<grid.length;i++) {
for(int j=0;j<col;j++) {
if(grid[i][j]=='1') {
int index=index(i,j);
parents[index]=index;
size[index]=1;
sets++;
}
}
}
}
public int findFather(int cur) {
int count=0;
while(cur!=parents[cur]) {
help[count++]=cur;
cur=parents[cur];
}
for(int i=0;i<count;i++) {
parents[help[i]]=cur;
}
return cur;
}
public int index(int i,int j) {
return i*col+j;
}
public void union(int i,int j) {
int iHead=findFather(i);
int jHead=findFather(j);
if(iHead!=jHead) {
int iSize=size[i];
int jSize=size[j];
int big= iSize>jSize?iHead:jHead;
int small=big==iHead?jHead:iHead;
parents[small]=big;
size[big]+=size[small];
sets--;
}
}
public int sets() {
return sets;
}
}
力扣链接:岛屿数量I
方法: 该题使用并查集,与题二不同的是,这题是将陆地给新增,然后求得新增岛屿的数量,岛屿的数量不是固定的。这里的做法和题二相仿,只不过增加了个动态的将新增的陆地进行岛屿数量确定的方法
public static List<Integer> numIslands(int m, int n, int[][] positions) {
UnionFind unionFind = new UnionFind(m, n);
List<Integer> ans = new LinkedList<>();
for (int[] position : positions) {
ans.add(unionFind.connect(position[0], position[1]));
}
return ans;
}
public static class UnionFind {
public int[] parents;
public int[] size;
public int[] help;
public int sets;
public int row;
public int col;
public UnionFind(int m, int n) {
row = m;
col = n;
sets = 0;
parents = new int[row * col];
size = new int[row * col];
help = new int[row * col];
}
public int sets() {
return sets;
}
public int index(int i, int j) {
return i * col + j;
}
public int findFather(int cur) {
int count = 0;
while (cur != parents[cur]) {
help[count++] = cur;
cur = parents[cur];
}
for (int i = 0; i < count; i++) {
parents[help[i]] = cur;
}
return cur;
}
public void union(int i1, int j1, int i2, int j2) {
if (i1 < 0 || i1 == row || j1 < 0 || j1 == col || i2 < 0 || i2 == row || j2 < 0 || j2 == col) {
return;
}
int index1 = index(i1, j1);
int index2 = index(i2, j2);
if (size[index1] == 0 || size[index2] == 0) {
return;
}
int f1 = findFather(index1);
int f2 = findFather(index2);
if (f1 != f2) {
int size1 = size[index1];
int size2 = size[index2];
int big = size1 > size2 ? f1 : f2;
int small = big == f1 ? f2 : f1;
parents[small] = big;
size[big] += size[small];
sets--;
}
}
public int connect(int i, int j) {
int index = index(i, j);
if (size[index] == 0) {
parents[index] = index;
size[index] = 1;
sets++;
union(i, j, i, j + 1);
union(i, j, i - 1, j);
union(i, j, i + 1, j);
union(i, j, i, j - 1);
}
return sets;
}
}
力扣链接:岛屿数量II
这里先简单的将一个矩形分一半来举例,示例图如下:
上述示例,1就表示岛屿,0表示海洋。如果不进行分割,那么岛屿的数量只有一个;如果进行分割,那么左边的岛屿数量是2,右边的岛屿数量也是2。
因此我们可以在分割后进行遍历后,将每个边界节点做一下记录,并给每个边界节点标好它自己的代表节点。当我们进行合并时,上述边界节点就分成了 a、b、c、d 四个集合,因此可以使用并查集,将这些集合进行合并,最终随着合并,岛屿的数量就变成了1个。
而对于 matrix 极大的情况,就可以进行多次分割,这时只要按照分割的方式,记录好边界点,并标好边界节点的代表节点,当合并时,使用并查集就可以依次合并!
我开始在 Ethereum blockchain 上了解如何开发智能合约以及如何写 web-script用于与智能合约交互(购买、销售、统计......)我得出了该怎么做的结论。我想知道我是否正确理解
我正在 UIView 中使用 CATransform3DMakeRotation,并且我正在尝试进行 45º,变换就像向后放置一样: 这是我拥有的“代码”,但显然没有这样做。 CATransform3
我目前正在测试 WebRTC 的功能,但我有一些脑逻辑问题。 WebRTC 究竟是什么? 我只读了“STUN”、“P2P”和其他...但是在技术方面什么是正确的 WebRTC(见下一个) 我需要什么
我在看 DelayedInit在 Scala in Depth ... 注释是我对代码的理解。 下面的 trait 接受一个非严格计算的参数(由于 => ),并返回 Unit .它的行为类似于构造函数
谁能给我指出一个用图片和简单的代码片段解释 WCF 的资源。我厌倦了谷歌搜索并在所有搜索结果中找到相同的“ABC”文章。 最佳答案 WCF 是一项非常复杂的技术,在我看来,它的文档记录非常少。启动和运
我期待以下 GetArgs.hs打印出传递给它的参数。 import System.Environment main = do args main 3 4 3 :39:1: Coul
private int vbo; private int ibo; vbo = glGenBuffers(); ibo = glGenBuffers(); glBindBuffer(GL_ARRAY_
我正在尝试一个 for 循环。我添加了一个 if 语句以在循环达到 30 时停止循环。 我见过i <= 10将运行 11 次,因为循环在达到 10 次时仍会运行。 如果有设置 i 的 if 语句,为什
我正在尝试了解 WSGI 的功能并需要一些帮助。 到目前为止,我知道它是一种服务器和应用程序之间的中间件,用于将不同的应用程序框架(位于服务器端)与应用程序连接,前提是相关框架具有 WSGI 适配器。
我是 Javascript 的新手,我正在尝试绕过 while 循环。我了解它们的目的,我想我了解它们的工作原理,但我在使用它们时遇到了麻烦。 我希望 while 值自身重复,直到两个随机数相互匹配。
我刚刚偶然发现Fabric并且文档并没有真正说明它是如何工作的。 我有根据的猜测是您需要在客户端和服务器端都安装它。 Python 代码存储在客户端,并在命令运行时通过 Fabric 的有线协议(pr
我想了解 ConditionalWeakTable .和有什么区别 class ClassA { static readonly ConditionalWeakTable OtherClass
关闭。这个问题需要更多focused .它目前不接受答案。 想改善这个问题吗?更新问题,使其仅关注一个问题 editing this post . 5年前关闭。 Improve this questi
我还没有成功找到任何可以引导我理解 UIPickerView 和 UIPickerView 模型的好例子。有什么建议吗? 最佳答案 为什么不使用默认的 Apple 文档示例?这是来自苹果文档的名为 U
我在看foldM为了获得关于如何使用它的直觉。 foldM :: Monad m => (a -> b -> m a) -> a -> [b] -> m a 在这个简单的例子中,我只返回 [Just
答案What are _mm_prefetch() locality hints?详细说明提示的含义。 我的问题是:我想要哪一个? 我正在处理一个被重复调用数十亿次的函数,其中包含一些 int 参数。
我一直在读这个article了解 gcroot 模板。我明白 gcroot provides handles into the garbage collected heap 然后 the handle
提供了一个用例: 流处理架构;事件进入 Kafka,然后由带有 MongoDB 接收器的作业进行处理。 数据库名称:myWebsite集合:用户 并且作业接收 users 集合中的 user 记录。
你好 我想更详细地了解 NFS 文件系统。我偶然发现了《NFS 图解》这本书,不幸的是它只能作为谷歌图书提供,所以有些页面丢失了。有人可能有另一个很好的资源,这将是在较低级别上了解 NFS 的良好开始
我无法理解这个问题,哪个更随机? rand() 或: rand() * rand() 我发现这是一个真正的脑筋急转弯,你能帮我吗? 编辑: 凭直觉,我知道数学答案是它们同样随机,但我忍不住认为,如果您
我是一名优秀的程序员,十分优秀!