算法-并查集-岛屿问题

这道题是LeetCode原题
https://leetcode.com/problems/number-of-islands/

题意:给定一个二维数组表,只可能会有0和1的位置,求上下左右都为1的一个整块数量。像下图就为2个岛数量
在这里插入图片描述

递归方式

public static int numIslands(char[][] board) {
   int islands = 0;
   for (int i = 0; i < board.length; i++) {
      for (int j = 0; j < board[0].length; j++) {
         if (board[i][j] == '1') {
            islands++;
            infect(board, i, j);
         }
      }
   }
   return islands;
}

// 从(i,j)这个位置出发,把所有练成一片的'1'字符,变成0
public static void infect(char[][] board, int i, int j) {
   if (i < 0 || i == board.length || j < 0 || j == board[0].length || board[i][j] != '1') {
      return;
   }
   board[i][j] = 0;
   // 列举所有可能性
   infect(board, i - 1, j);
   infect(board, i + 1, j);
   infect(board, i, j - 1);
   infect(board, i, j + 1);
}

并查集方式实现

定义:

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

	public static int numIslands(char[][] board) {
		int row = board.length;
		int col = board[0].length;
		UnionFind2 uf = new UnionFind2(board);
		// 只有左的  这样可以不用处理一些边界条件   这样虽然多了for循环,但其实和一个for循环里面有多个判断是一样的  这样也不需要判断的是否越界
		for (int j = 1; j < col; j++) {
			// (0-j) (0,0)跳过了  (0,1)(0,2)(0,3)
			if (board[0][j - 1] == '1' && board[0][j] == '1') {
				uf.union(0, j - 1, 0, j);
			}
		}
		// 只有上的
		for (int i = 1; i < row; i++) {
			// 如果我自己是1和上面也是1
			if (board[i - 1][0] == '1' && board[i][0] == '1') {
				uf.union(i - 1, 0, i, 0);
			}
		}
		// 既有左又有上的
		for (int i = 1; i < row; i++) {
			for (int j = 1; j < col; j++) {
				if (board[i][j] == '1') {
					if (board[i][j - 1] == '1') {
						uf.union(i, j - 1, i, j);
					}
					if (board[i - 1][j] == '1') {
						uf.union(i - 1, j, i, j);
					}
				}
			}
		}
		return uf.sets();
	}

	public static class UnionFind2 {
		private int[] parent;
		private int[] size;
		private int[] help;
		// (i,j) i*列数+j
		private int col;
		private int sets;

		public UnionFind2(char[][] board) {
			col = board[0].length;
			sets = 0;
			int row = board.length;
			int len = row * col;
			parent = new int[len];
			size = new int[len];
			help = new int[len];
			for (int r = 0; r < row; r++) {
				for (int c = 0; c < col; c++) {
					if (board[r][c] == '1') {
						int i = index(r, c);
						parent[i] = i;
						size[i] = 1;
						sets++;
					}
				}
			}
		}

		// (r,c) -> i  对应的下标给算出来
		private int index(int r, int c) {
			return r * col + c;
		}

		// 原始位置 -> 下标
		private int find(int i) {
			int hi = 0;
			while (i != parent[i]) {
				help[hi++] = i;
				i = parent[i];
			}
			for (hi--; hi >= 0; hi--) {
				parent[help[hi]] = i;
			}
			return i;
		}

		public void union(int r1, int c1, int r2, int c2) {
			int i1 = index(r1, c1);
			int i2 = index(r2, c2);
			int f1 = find(i1);
			int f2 = find(i2);
			if (f1 != f2) {
				if (size[f1] >= size[f2]) {
					size[f1] += size[f2];
					parent[f2] = f1;
				} else {
					size[f2] += size[f1];
					parent[f1] = f2;
				}
				sets--;
			}
		}

		public int sets() {
			return sets;
		}

	}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值