当前位置:   article > 正文

数据结构与算法面试知识点汇总(超全)_常用的算法和数据结构 面试

常用的算法和数据结构 面试


一、哈希函数和哈希表

01 哈希函数

哈希函数(散列函数):类似于函数调用一样,给一个字符串作为输入,返回一个哈希码。
哈希表(散列表):把哈希码映射到表中的一个位置来访问记录,存放记录的数组就叫散列表

  • 直接寻址法:hash(key) = a * key + b
  • 数字分析法
  • 平方取中法
  • 折叠法
  • 随机数法
  • 除留余数法

性质:
1)输入域是无穷大的
2)输出域是有穷的,输出域要比输入域小
3)哈希函数不是一个随机函数,只要输入一样,输出就一样
4)输入域这么大而输出域这么小,而由于特性3的关系,就会导致输入不一样也有可能得到输出一样的值(哈希碰撞)
5)虽然有哈希碰撞,如果输入够多,将在整个输出域上均匀地出现返回值,不一定完全平分,但输出域中的每个哈希码一定被差不多个输入域中的值对应
6)因为要做到均匀分布,于是哈希函数跟输入的规律是没有关系的,可以打乱输入规律

推广:
当输入域足够大,经过一个哈希函数计算完后保证输出域是均匀分布时,
对输出域的每一个哈希码模%上一个m,最终的域会缩减成0至m-1,也均匀分布

工程应用:
如何找到1k个相对独立的哈希函数,一个哈希函数的规律跟另一个哈希函数的规律是无关的
只要用1个哈希函数就能改出1k个来,如:哈希f1 + k * 哈希f2 = 哈希fn

02 哈希表

实现原理:
在这里插入图片描述

解决哈希冲突(key1和key2称为同义词)的方法:

  • 开放寻址法(包括线性探测再散列、二次探测再散列(改善堆积现象)、伪随机探测再散列、双散列法),容易造成堆积现象
  • 链地址法:在原地址新建一个空间,以链表结点的形式插入到该空间。链地址算法的基本思想是将所有哈希地址为 i 的元素构成一个称为同义词链的单链表,并将单链表的头指针存在哈希表的第i个单元中,因而查找、插入和删除主要在同义词链中进行。链地址法适用于经常进行插入和删除的情况。插入新数据项的时间随装载因子线性增长。

哈希表扩容:

如果发现某个位置上的链的长度到达k了,那么可以认为其它位置上链的长度也到达k了。此时,可能下一次加记录的话效率就不行了,这时就要经历哈希表的扩容

  • 首先要将原来的哈希域(如前面的17),扩展到104(或别的)。
  • 扩完之后如果要模104,则原来的记录都会失效。此时要将每一个记录都拿出来,用哈希函数计算完之后去模上104,再分配其在哪个位置。

那么,扩容代价不用计算吗?为什么能做到O(1)呢?
一方面样本量是N,若每次扩容都让哈希域长度增加k倍,则扩容平均代价是O(logkN)可很低
另一方面还可以离线扩容。
于是哈希表的增删改查是O(1)的。

Q:有一个大文件100T,每一行是一个字符串,是无序的,让你打印重复的字符串
A:如果单核单CPU单台机器去搞这个事情,那头都大了
Q:说得对,那你怎么办?
A:这是一个大数据问题,可以通过哈希来分流,你给我多少台机器呢?大文件存在哪呢?
Q:给你1000台机器干这个事情,大文件存在一个分布式文件系统上,按行读有很快的工具
A:

  • 1)首先对机器标号0-999,把每一行作为一个文本读出来,
    利用哈希函数去算一个哈希码,去模1000,
    当然也不一定要你提供的1k台机器,只要我能知道里面有m种字符串就好了,就模m
  • 2)然后将结果放到对应标号的机器中,用一个文件记录下来。
    这样就把这个大文件分到了m台机器上去,而且相同的重复文本一定会分配到同一台机器上(哈希函数的性质:相同输入相同输出,不同输入均匀分布)
  • 3)然后在单台机器上去统计有哪些重复的,最后就是1k台机器并发
    如果单台机器上的文件还是太大,再在这个机器上做相同的步骤,在单台机器上并行计算即可

二、布隆过滤器

主要用于解决爬虫去重问题以及黑名单问题,其短板是它有失误率。

这个失误是指:宁可错杀,绝不放过1个的类型。如果是坏人,一定报警。如果是好人,也可能报警,但几率较小

以黑名单问题来举例:

有这么个需求,100亿个URL是黑名单,假设每个URL是64字节,
当用户想搜索某个URL时,如果它是黑名单里的URL,就要对它进行过滤

解决方案:
1)首先它可以通过哈希表实现,但是需要多少个空间呢?

  • 由于这里面不用存value,只用存key,于是它是一个hashSet,key是URL字符串
  • 这里起码需要6400亿个字节才能把这些URL装下,还不算指针、索引等所占的空间,实际上是比6400亿字节(640G)大的, 如果做成哈希表全放内存中,代价是很高的,而且服务器还要用分布式内存
  • 这个设计就耗费了巨大的代价。即便用哈希函数分流到多个机器上,这浪费了机器

2)那怎么能仅适用一台机器就能提供服务呢?——布隆过滤器

  • 在哈希来哈希去的经典方法说了以后,应该问允许有很低的失误率吗?——允许。这个时候就可以使用布隆过滤器。它是一个bit类型的map。

实现方法:
第一步:
准备一个数组,从0到m-1位置,每个位置上不是整数也不是串,是一个bit,只有两种状态0/1
int *arr = new int[1000]; // 分配4*1000字节的连续内存,共4*1000*8个bit
在这里插入图片描述
这样,就用基础类型int做出了一个0到m-1长度为m的bit类型的数组。如果想省空间,就用long类型,8个字节64位。此外还能做成矩阵的形式。
如果想让第300000个bit位置置1,此时int index = 300000,

  • ① 定位这个bit来自于哪个桶 int intIndex = index / 32;
  • ② 定位这个位置来自于这个桶的哪个bit位 int bitIndex = index % 32;
  • arr[intIndex] = (arr[intIndex] | (1 << bitIndex));

第二步:
有了这个数组以后,

  • ① 将黑名单内的所有URL,输入k个相互独立的哈希函数中得到k个哈希码分别取模上m,算出来的对应数组中的那个位置1。若产生哈希碰撞,不管,还是置1。这个过程结束以后,URL进入布隆过滤器。注意:这个数组应当长一些,如果太短,则很容易每个位置都变成1,这样用户输入的URL就全都是黑名单了。
  • ② 查URL的过程:将用户提供的URL,也做步骤①,如果算出来的k个位置都已经置1,则说这个URL在黑名单中。如果仅有一个甚至几个没被置1,说明这个URL不在黑名单中(哈希函数特性:相同输入一定有相同输出)。失误的时候就出现在数组开的比较小。
  • 通过调整数组的大小m和哈希函数的个数k,可以得到不同的失误率。
    在这里插入图片描述

三、一致性哈希

经典服务器抗压结构:
比如提供一个根据用户QQ号(key)返回其网龄(value)的服务。如何使其负载均衡呢?

  • 首先会有一个前端(Nginx),用于接受请求,请求可以发送到任意一个前端上,提供的服务相同
  • 然后有一个后端集群组,假设有3台机器m1,m2,m3
  • 首先信息会发送到前端上,经前端的同样一份哈希函数算出哈希码,模上3得到对应的机器,将这个信息存到对应的机器中。如果数据足够多,数据是均匀地存在三台机器中的,负载均衡:每台机器处理的东西是差不多的,CPU占用差不多,内存使用差不多,相关系统性能指标差不多。
  • 在查的时候,依然是经过上面的步骤,由于相同输入一定导致相同输出,就会到相应的机器上去拿出这个记录,返回给前端,然后前端返回给用户。

上面提到的经典服务器有什么问题呢?——当增加机器或减少机器时,它就炸了

原因:
这和哈希表扩容是一样的,如果加机器加到100,原来模的是3,现在要模100,所有的数据归属全变了。所以必须要把原来的记录都拿出来再算一次,然后迁移到相应的机器上。代价很高

于是有了一致性哈希结构,可实现把原来全部数据迁移的代价变得很低,并负载均衡

具体是这样的:
在这里插入图片描述
那么如何实现它呢?

  • 对于后端——还是那三台机器
  • 对于前端——还是无差别的负载的服务器

然后,对三台机器对应的哈希值排好序之后做成一个数组存到前端中去
这样,不管请求发送到前端的哪一个机器上,由于每个机器上都有这么一个数组,二分的方式找到第一个大于等于算出来的哈希值的那个位置,就知道这个请求应当由哪个机器处理了。
即便你有10亿台机器,由于采用二分的方法,log以后复杂度也是很低的。

那为什么说它克服了传统服务器结构中加减机器的问题呢?

比如加了一台m4,同

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/620191
推荐阅读
相关标签
  

闽ICP备14008679号