海量数据处理

  针对海量数据的处理,可以使用的方法非常多,常见的方法有hash法、Bit-map法、Bloom filter法、数据库优化法、倒排索引法、外排序法、Trie树、堆、双层桶法以及MapReduce法。

 

1、hash法

  hash法也成为散列法,它是一种映射关系,即给定一个元素,关键字是key,按照一个确定的散列函数计算出hash(key),把hash(key)作为关键字key对应的元素的存储地址,再进行数据元素的插入和检索操作。

  散列表是具有固定大小的数组,表长应该是质数,散列函数是用于关键字和存储地址之间的一种映射关系,但是,不能保证每个元素的关键字与函数值是一一对应的,因为可能会冲突(多个关键字对应同一个存储地址)。

  常用的散列函数的构造方法有:

  (1)直接寻址法

  取关键字或关键字的某个线性函数值为散列地址,即h(key) = key或h(key)=a*key+b,其中a和b都是整型常数,这种散列函数叫做自身函数。这种函数的时间复杂度是O(1),但是空间复杂度是O(n),其中n指的是关键字的个数。

  直接寻址法不会导致哈希冲突,但是没有压缩,所以在关键值集合较大的时候,使用这种hash函数不能实现地址编码的散列。

  

  (2)取模法

  选择一个合适的正整数p,令hash(key)=key mod p,p如果选择的是比较大的素数,则效果比较好,一般p取的是散列表的长度。

  

  (3)数字分析法

  设关键字是d位的以r为基的数,且共有n个关键字,则关键字的每个位可能有r个不同字符出现,但这r个字符出现的频率不固定,可能在某些位上是俊宇的,即每个字符出现的次数接近于r/n,而在另外的一些位上分布不均匀。因此可以选取其中分布比较均匀的那些位,重新组合为新的数,用其作为散列地址。

  这种方法比较简洁,但是需要预知每个关键字的情况,这样就限制了使用。

  

  (4)折叠法 

  将关键字分成位数为t的几个部分(最后一部分的位数可能小于t),然后把各部分按位对其进行相加,将所得的和舍弃进位,留下t位作为散列地址。当关键字位数很多,而且关键字中每位上数字分布比较均匀时,采用折叠法比较合适。

  

  (5)平方取中法

  这是一种常见的方法,将关键字进行平方运算,然后从结果的中间取出若干位(位数与散列地址的位数相同),将其作为散列地址。

 

  (6)除留取余法

  这是一种比较常见的散列方法,其主要原理是取关键字除以某个数p(p不大于散列表的长度)的余数作为散列地址,即:

 hash(key) = key%p

     使用除留取余法时,选取合适的p值很重要,一般p<=TableSize,p一般取质数,也可以是不包含小于20质因子的合数。

  

  (7)随机数法

  选择一个随机函数,然后用关键字key的随机函数值作为散列地址,即

  hash(key) = random(key)

   其中,random()是随机函数。当关键字的长度不等的时候,采用这种方式比较合适。

  

  无论采用什么样的方法,冲突都是不可能避免的,所以解决冲突是一个必不可少的过程。解决冲突的主要途径是当一个关键字映射到散列表中的某一个地址,且该地址上已有关键字的时候,再为该关键字寻找新的存储地址。

  常用的解决冲突的方法有以下几种:

  1.开放定址法

  开放定址法的基本思想是当发生地址冲突的时候,在散列表中再按照某种方法继续探测其他的存储地址,直到找到空闲的地址为止。这个过程可以描述为:

  Hi = (H(key) + di) mod m(i = 1,2,...,k (k <= m-1) )

  其中:

    H(key):关键字key的直接散列地址;

    m:散列表的长度;

    di:每次再探测时的地址增量。

  采用这种方法时,首先计算出关键字的直接散列地址,即H(key),若该直接散列地址上已经有其他关键字,则继续查看地址为H(key) + di 的存储地址,判断是否为空。如此反复,知道找到空闲的存储地址为止,然后将关键字key存放在该地址。

  增量di有不同的取法,常用的有以下3种:

  (1)di = 1,2,3,...,m-1,称为线性探测再散列;

  (2)di = 1*1, -1*1, 2*2, -2*2,...,k*k, -k*k,称为二次探测再散列;

  (3)di = 伪随机数,称为伪随机再散列。

  

  2.链地址法(拉链法)

  若散列表空间为[0,m-1],则设置一个由m个指针组成的一维数组CH[m],然后在寻找关键字散列地址的过程中,所有散列地址为i的数据元素都插入到头指针为CH[i]的链表中。

  这种方法适合于冲突比较严重的情况。

  对于关键字集合{12,67,56,16,25,37, 22,29,15,47,48,34},我们用前面同样的12为除数,进行除留余数法:

  

  

拉链法的优势与缺点

与开放定址法相比,拉链法有如下几个优点:

  1. 拉链法处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短;
  2. 由于拉链法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况;
  3. 开放定址法为减少冲突,要求装填因子α较小,故当结点规模较大时会浪费很多空间。而拉链法中可取α≥1,且结点较大时,拉链法中增加的指针域可忽略不计,因此节省空间;
  4. 在用拉链法构造的散列表中,删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。而对开放地址法构造的散列表,删除结点不能简单地将被删结 点的空间置为空,否则将截断在它之后填人散列表的同义词结点的查找路径。这是因为各种开放地址法中,空地址单元(即开放地址)都是查找失败的条件。因此在用开放地址法处理冲突的散列表上执行删除操作,只能在被删结点上做删除标记,而不能真正删除结点。

拉链法的缺点:指针需要额外的空间,故当结点规模较小时,开放定址法较为节省空间,而若将节省的指针空间用来扩大散列表的规模,可使装填因子变小,这又减少了开放定址法中的冲突,从而提高平均查找速度。

  

  (3)再散列法(再哈希法)

  当发生冲突的时候,使用第二个、第三个散列函数计算地址,直到没有冲突为止,但这种方法可能导致计算时间的大幅增加。

  

  (4)建立一个公共溢出区

  假设散列函数的值域为[0,m - 1],则设向量Hashtable [0...m-1]为基本表,另外设立存储空间向量OverTable[0...v]用以存储发生冲突的记录。

  hash主要用来进行“快速存取”,在O(1)的时间复杂度里就可以查找到目标元素,或者判断其是否存在。hash数据结构中的数据对外是杂乱无章的,因此其具体的存储位置以及各个存储元素位置之间的相互关系是无法得知的,但是却可以在常数时间里判断元素位置及存在与否。

 

2、Bit-map法

  位图法的基本原理是使用位数组来表示某些元素是否存在,如从8位电话中查找重复号码。

  为突发的结果是生成一个N位长的串,每位上以“0”或“1”表示需要排序的组合(简称“集合”)中的数,例如集合为{2,7,4,9,1,10},则生成一个10位的串,将会在第2、7、4、9、1、10位置设置为“1”,其余位置为“0”,当把串中所有位都置完后,排序也自动完成了(因为字符串的下标是有序的)。上面的数据排序后的结果为1101001011。

  如果要排序0-15以内的元素序列{5,8,1,12,6,2},那么首先开辟了2Byte的空间,也就是16位,分别对应0-15这16个数,并将这16个位置设置为“0”。遍历序列,在出现的数字的对应位置上置为“1”,也就是将每个元素对应到了位图的相应位置。再遍历这16位,就完成了对元素的排序。

  

  使用位图法存储数据【5,1,7,15,0,4,6,10】,如下图所示:

  位图法排序的时间复杂度是O(n),比一般的排序快,但它是以时间换空间(需要一个N位的串)的,而且有一些限制,即数据状态不是很多,例如排序前集合大小最好已知,而且集合中元素的最大重复次数必须已知,最好数据比较集中。

  

  常常会遇到判断集合中是否存在重复的问题,数据量比较小的时候,对时间复杂度要求不高,担当集合中数据量比较大的时候,则希望能够少进行几次扫描,此时如果还采用双重循环的话,效率很低,此时使用位图法很合适,首先找到最大元素,然后按照集合中最大元素max创建一个长度为max+1的新数组,接着再次扫描原数组,每次遇到一个元素,就将新数组中下标为元素值的位置1,例如,如果遇到元素5,就将新数组中第6个位置置为1,当再次遇到5的时候,发现已经是1,所以重复。该算法的运算次数最坏的情况为2N,但如果知道最大元素,速度可以提升1倍。

  

3.Bloom Filter法

  如需要判断一个元素是不是在一个集合中,我们通常做法是把所有元素保存下来,然后通过比较知道它是不是在集合内,链表、树都是基于这种思路,当集合内元素个数的变大,我们需要的空间和时间都线性变大,检索速度也越来越慢。 Bloom filter 采用的是哈希函数的方法,将一个元素映射到一个 m 长度的阵列上的一个点,当这个点是 1 时,那么这个元素在集合内,反之则不在集合内。这个方法的缺点就是当检测的元素很多的时候可能有冲突,解决方法就是使用 k 个哈希 函数对应 k 个点,如果所有点都是 1 的话,那么元素在集合内,如果有 0 的话,元素则不在集合内。

  核心思想是:位数组和hash函数的联合使用。

   

(1)位数组:

        假设Bloom Filter使用一个m比特的数组来保存信息,初始状态时,Bloom Filter是一个包含m位的位数组,每一位都置为0,即BF整个数组的元素都设置为0。

 

(2)添加元素,k个独立hash函数

       为了表达S={x1, x2,…,xn}这样一个n个元素的集合,Bloom Filter使用k个相互独立的哈希函数(Hash Function),它们分别将集合中的每个元素映射到{1,…,m}的范围中。

         当我们往Bloom Filter中增加任意一个元素x时候,我们使用k个哈希函数得到k个哈希值,然后将数组中对应的比特位设置为1。即第i个哈希函数映射的位置hashi(x)就会被置为1(1≤i≤k)。

 注意,如果一个位置多次被置为1,那么只有第一次会起作用,后面几次将没有任何效果。在下图中,k=3,且有两个哈希函数选中同一个位置(从左边数第五位,即第二个“1“处)。   

 

(3)判断元素是否存在集合

    在判断y是否属于这个集合时,我们只需要对y使用k个哈希函数得到k个哈希值,如果所有hashi(y)的位置都是1(1≤i≤k),即k个位置都被设置为1了,那么我们就认为y是集合中的元素,否则就认为y不是集合中的元素。下图中y1就不是集合中的元素(因为y1有一处指向了“0”位)。y2或者属于这个集合,或者刚好是一个false positive。

Bloom Filter的缺点:

       1)Bloom Filter无法从Bloom Filter集合中删除一个元素。因为该元素对应的位会牵动到其他的元素。所以一个简单的改进就是 counting Bloom filter,用一个counter数组代替位数组,就可以支持删除了。 此外,Bloom Filter的hash函数选择会影响算法的效果。

       2)还有一个比较重要的问题,如何根据输入元素个数n,确定位数组m的大小及hash函数个数,即hash函数选择会影响算法的效果。当hash函数个数k=(ln2)*(m/n)时错误率最小。在错误率不大于E的情况 下,m至少要等于n*lg(1/E) 才能表示任意n个元素的集合。但m还应该更大些,因为还要保证bit数组里至少一半为0,则m应 该>=nlg(1/E)*lge ,大概就是nlg(1/E)1.44倍(lg表示以2为底的对数)。 

举个例子我们假设错误率为0.01,则此时m应大概是n的13倍。这样k大概是8个。 

 

4.数据库优化法

  这种方法不细致说,因为不是直接的算法,而是通过优化数据库(优化数据库其实也是用的算法)的方式。

 

5.倒排索引法

  

6.外排序法

  当待排序的对象数目特别多的时候,在内存中不能被一次性处理,必须把它们以文件形式存放在外存中,排序的时候再把它们一部分一部分的调入内存进行管理,这种方式就是外排序法。

 

7.Trie树

  Trie树又被称为字典树或者键树,它是一种用于快速字符串检索的多叉树结构,其原理是利用字符串的公共前缀来减少时空开销,即以空间换时间,从而达到提高程序效率的目的。Trie树的典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎用于文本词频统计。优点是可以最大限度的减少无畏的字符串比较,查询效率比散列表高。

  Trie树一般具有3个基本特性:

  (1)根节点不包含字符,除根节点之外的每一个节点都只包含一个字符;

  (2)从根节点到某一节点,路径上所经过的字符连接起来,为该节点对应的字符串;

  (3)每个节点的所有子节点包含的字符都不同。

 

转载于:https://www.cnblogs.com/Mr24/p/6685863.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值