算法学习05:哈希&并查集

认识哈希函数和哈希表

经典哈希函数:
输入域无穷大的,输出域有穷尽的,
输入参数固定时,返回值一定是一样的
输入值不同时,有可能得到相同的返回值,(哈希碰撞)
哈希函数的离散性:给我很多不同的书的话,我将在整个s域上均匀返回其输出值

认识布隆过滤器

  • 布隆过滤器: 用于封杀url,这种方法有一定的失误率:有可能会误封url.
  • 做法:
    1. 创建一个bit数组,每个bit代表一个 哈希结果 的true或false.
    2. 将每个黑名单url经过多个独立的哈希函数得到的哈希值对应的bit位描黑
    3. 要对比url是否存在时,只需查看将待对比的url经过几个哈希函数的哈希结果节点.若所有哈希结果对应的点均被描黑,则认为此url属于url
  • 公式:(设n为样本量,p为目标失误率,k为哈希函数个数)
    1. 数组位数 m = − n ∗ ln ⁡ p l n 2 2 m = -\frac{n*\ln{p}}{ln^2{2}} m=ln22nlnp
    2. 哈希函数个数 k = ln ⁡ 2 ∗ m n k = \ln{2}*\frac{m}{n} k=ln2nm
    3. 实际失误率 p ′ = ( 1 − e − ( m ∗ k n ) ) k p' = {(1-e^{-(\frac{m*k}{n})})}^k p=(1e(nmk))k

认识一致性哈希

在这里插入图片描述
对于分布式系统,需要将哈希结果 限制在[0-232]以内,然后将哈希结果区间平均分配给几个服务器.

并查集

并查集主要解决两个问题:

  1. 判断某元素是否存在于集合中
  2. 集合的合并

并查集的设计

  1. 并查集的结构: 每个节点都有一个父指针指向自己的父节点,父子节点属于同一个集合,根节点作为一个集合的代表节点.
  2. 并查集的构造: 初始时每一个点自成一个集合,每一个节点的父指针都指向自己,每一个集合的代表节点为集合元素自身.
  3. 集合的合并: 是把一个集合的代表节点的父节点指向另一个节点的代表节点.
  4. 并查集的路径压缩: 查找根节点时顺便做路径压缩操作: 将沿途节点的父指针设为所查询到的根节点.

并查集的实现

并查集是用map实现的,某节点的fatherMap值表示该节点的父指针

public class UnionFind {

	// 节点类
	public static class Node {
		// whatever you like
	}

	// 并查集类
	public static class UnionFindSet {
		public HashMap<Node, Node> fatherMap;	// 存储某节点的父节点
		public HashMap<Node, Integer> sizeMap;	// 存储某集合的元素个数

		// 初始化并查集
		public UnionFindSet() {
			fatherMap = new HashMap<Node, Node>();
			sizeMap = new HashMap<Node, Integer>();
		}

		// 初始化并查集(使用Node列表)
		public void UnionFindSet(List<Node> nodes) {
			UnionFindSet()
			fatherMap.clear();
			sizeMap.clear();
			for (Node node : nodes) {
			// 初始时每个节点自成一个并查集
			// 	每个集合的代表节点为其自身,每个并查集的size为1
				fatherMap.put(node, node);
				sizeMap.put(node, 1);
			}
		}

		// 找到并查集代表节点(路径压缩)
		private Node findHead(Node node) {
			Node father = fatherMap.get(node);
			if (father != node) {
				father = findHead(father);
			}
			fatherMap.put(node, father);
			return father;
		}
	
		// 判断是否是同一个并查集
		public boolean isSameSet(Node a, Node b) {
			return findHead(a) == findHead(b);
		}

		// 合并并查集
		public void union(Node a, Node b) {
			if (a == null || b == null) {
				return;
			}
			Node aHead = findHead(a);
			Node bHead = findHead(b);
			if (aHead != bHead) {
				int aSetSize = sizeMap.get(aHead);
				int bSetSize = sizeMap.get(bHead);
				// 将size小并查集的代表节点加到size大并查集中
				if (aSetSize <= bSetSize) {
					fatherMap.put(aHead, bHead);
					sizeMap.put(bHead, aSetSize + bSetSize);
				} else {
					fatherMap.put(bHead, aHead);
					sizeMap.put(aHead, aSetSize + bSetSize);
				}
			}
		}
	}
}

当节点数量足够多时,并查集单次查询和合并的效率是O(1),与数据规模无关.

并查集应用举例:岛问题

一个矩阵中只有0和1两种值,每个位置都可以和自己的上、下、左、右四个位置相连,如果有一片1连在一起,这个部分叫做一个岛,求一个矩阵中有多少个岛?
举例:

0 0 1 0 1 0
1 1 1 0 1 0
1 0 0 1 0 0
0 0 0 0 0 0
这个矩阵中有三个岛。

解法

  1. 作为经典问题,比较好做:一顿遍历,若遍历到1,就进入感染函数,将与其相连的部分感染成2,岛屿数量加一.
    public static void infect(char[][] grid, int y, int x) {
    	int yLen = grid.length;
    	int xLen = grid[0].length;
    	if (y >= 0 && y < yLen && x >= 0 && x < xLen) {
    		if (grid[y][x] == '1') {
    			grid[y][x] = '2';
    			infect(grid, y + 1, x);
    			infect(grid, y - 1, x);
    			infect(grid, y, x + 1);
    			infect(grid, y, x - 1);
    		}
    	}
    }
    
  2. 若数据量很大,需要并行运算求解,就不太好做了,难点在于多CPU计算结果的合并.
    1. 合并时要考虑将边界有重合的岛合并,但问题在于我们不知道多个重合边是否属于同一个岛.
    2. 考虑如下情况:有一个大C字形岛屿,被一刀切成三块了,那么在合并的时候遇到两条边,但我们不知道到底有几个岛(要合并几次).
    3. 应用并查集结构,将一个岛屿构造成一个并查集,遇到相同边的时候合并进同一个并查集中,这样避免了重复合并的问题.
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值