布隆过滤器,一致性哈希以及并查集

今天我对之前学到几种较为复杂的数据结构和算法进行一下总结。分别是布隆过滤器,一致性哈希和并查集。

目录

 

1.布隆过滤器

2.一致性哈希

3.并查集


1.布隆过滤器

布隆过滤器解决的问题:

许多网站都维护有一个黑名单防止有些人的恶意访问,当我们在访问一个网站的时候,网站的检测系统会将我们的ip和黑名单中进行比对,如果发现比对成功,那么访问失败。

这个问题看起来非常简单,只需要将这些ip维护在一个哈希表中,每当有用户进行访问和这个哈希表比对就可以了。但是如果ip的数量非常庞大,那么这个哈希表将占用巨额的内存,这显然会降低效率和增加服务器的负载。

布隆过滤器的思路:

布隆过滤器是一个比特类型的哈希表。我们可以想象一个数组,这个数组的类型不是int或者string这些我们常用的,而是比特类型,也就是0或者1的比特。一个整形类型是4个字节,一个字节对应8个比特。那么一个整型类型对应的就是32个字节。

int[] arr = new int[1000]//这个数组代表了32000的比特位

那么如何将其中的一个bit位涂黑(也就是置为1)呢。

int index = 30000;//需要涂黑的比特位

int intIndex = index /32;//这个是对应int数组中的第几个int的位置

int bitIndex = index %32;//这个对应了在int位置的第几个bit位置。


arr[intIndex] = arr[intIndex]|(1<<(32-bitIndex))

这个思路就是将第30000个bit位涂黑。在布隆过滤器中,需要声明多个哈希函数,将黑名单中每一个ip算出多个hash值(每个hash函数对应一个),将hash值对32000进行取模然后参照上面的算法求出应该涂黑的比特位。之后在对访问的ip采用同样的办法,如果发现ip的每一个涂黑的位置和黑名单对应的布隆过滤器中每一个涂黑的位置相同,说明这个ip为黑名单。

这个思路大大节约了内存空间,但是容易出现误判也就是将正常的ip归为黑名单中。这个时候对于arr数组的长度选取还有hash函数的个数选取是十分重要的。

2.一致性哈希

应用领域:负载均衡

通常一个服务需要部署到多台服务器上来应对用户大量的访问需求,这里面就涉及到了负载均衡。假如我有3台服务器来处理用户的请求,那么我可以在设置将请求以轮询的方式来访问这三台服务器。但是如果涉及到了服务器数量变化,这个请求的分配方法就需要改变,需要对请求的每一条信息进行重新计算并安排新的服务器处理请求。这个开销非常庞大,一致性哈希就是用来解决这个问题,降低服务器开销的同时实现负载均衡。

一致性哈希问题又称为哈希环问题,目的是将所有的负载均分到每一台服务器上。这个环上所有的点对应的是请求的hash值。将每一台服务器的hash值进行计算并进行排序。将这个数组维护在每一台负载服务器上,在处理请求时计算请求的hash值处在哪两个机器的hash值的区间(可以采用二分法进行查找)。最后将请求归属到区间的左端点服务器上。这样处理实际上还是存在问题,如果机器数量很少那么hash值并不能将整个环均分,也就不能是负载均衡。解决的办法是为每一台服务器分配若干的虚拟节点,通过虚拟节点的hash值来进行服务器分配,算法和上面的相同。

3.并查集

并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。

思路:通过维护两个HashMap来实现这个数据结构,一个集合用来存放节点和该节点对应的代表节点或者父节点。一个用来记录以该节点的父节点为根节点的集合的大小。

        public HashMap<Node, Node> fatherMap;
        public HashMap<Node, Integer> sizeMap;

在初始化的过程中,需要传入一个list,这个链表包含了所有的节点,在初始化将每一个结点当做一个新的集合。

        public UnionFindSet() {
			fatherMap = new HashMap<Node, Node>();
			sizeMap = new HashMap<Node, Integer>();
		}

		public void makeSets(List<Node> nodes) {
			fatherMap.clear();
			sizeMap.clear();
			for (Node node : nodes) {
				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;
		}

 如果需要查找两个元素是否在同一个集合中,只需要查找head结点即可。

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);
				if (aSetSize <= bSetSize) {
					fatherMap.put(aHead, bHead);
					sizeMap.put(bHead, aSetSize + bSetSize);
				} else {
					fatherMap.put(bHead, aHead);
					sizeMap.put(aHead, aSetSize + bSetSize);
				}
			}
		}

这个合并实际上只涉及到了头结点的代表节点的变化,这样在下一次调用findHead方法的时候,每一个结点的代表节点就会更新为新的集合对应的代表节点。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值