一:问题描叙:
在日常生活当中我们经常要判断一个元素在不在一个集合当中,在字处理软件当中,判断一个单词是否拼写正确;在网络爬虫当中,一个网址是否被访问过,最直接的方法将元素全部存到计算机中,遇到一个新元素,将它与集合中的元素进行比较就可以。我们知道集合可以用哈希表来存储,好处是快速准确,缺点是浪费空间,无论是闭散列方法还是开链法,当负载因子超过0.7会导致查找的效率变慢,因此每次都要开一个更大的空间,增加了查找的开销、这时我们想到布隆过滤器。
二:基本概念:
如果判断一个元素是不是在一个集合里,一般想到的是将所有元素保存起来,然后通过比较确定,但是随着集合元素的增加,需要的存储的空间越来越大,检索的速度聚会越来越慢,这时我我们会想到散列表(哈希表hashtable)的数据结构。通过一个hash函数将一个元素映射到一个位阵列中的一个点,这样只要需要看这个映射的这个位置是不是1,就可以确定这个元素在没在这个集合中了,这就是布隆过滤器的思想;
三:布隆过滤器:
实际上由一个很长的二进制向量和一系列的随机映射函数组成,布隆过滤器可以检索一个元素是否在一个集合当中;
优点:
(1)空间效率和查找效率远超于一般算法,布隆过滤器存储空间和插入、查询时间都是常数。O(1)
(2)另外,hash函数相互之间没有关系,方便由硬件实现,布隆过滤器不需要存储元素本身。
缺点:
(1)可能会存在误判(如”sort”和”srot”可能经过字符串哈希算法,映射到相同的位置,经过检测显示存在,但实际上不是我们所希望得到的“sort”;)随着存入数量的增加,会导致误判率;
(2)删除困难
解决删除困难的方法;每插入一个元素相应的计数器加1,这样删除元素时,只要把计数器删除就好了。
四:布隆过滤器的实现
1:字符串哈希算法,将字符串变成整数。
2:将数字通过处留余数法映射到所存储的集合上。
3:判断映射的位置是否都存在,都存在表明这个元素在这个集合里面。否则不再这个集合里。
#include<iostream>
#include<string>
#include<vector>
using namespace std;
//布隆过滤器用于检索一个元素是不是在一个集合里面
template<class K>
struct _HashFunc1
{
size_t BKDRHash(const char *str)
{
register size_t hash = 0;
while (size_t ch = (size_t)*str++)
{
hash = hash * 131 + ch; // 也可以乘以31、131、1313、13131、131313..
}
return hash;
}
size_t operator()(const string &s)
{
return BKDRHash(s.c_str());
}
};
template<class K>
struct _HashFunc2
{
size_t SDBMHash(const char *str)
{
register size_t hash = 0;
while (size_t ch = (size_t)*str++)
{
hash = 65599 * hash + ch;
//hash = (size_t)ch + (hash << 6) + (hash << 16) - hash;
}
return hash;
}
size_t operator()(const string &s)
{
return SDBMHash(s.c_str());
}
};
template<class K>
struct _HashFunc3
{
size_t RSHash(const char *str)
{
if (!*str) // 这是由本人添加,以保证空字符串返回哈希值0
return 0;
register size_t hash = 1315423911;
while (size_t ch = (size_t)*str++)
{
hash ^= ((hash << 5) + ch + (hash >> 2));
}
return hash;
}
size_t operator()(const string &s)
{
return RSHash(s.c_str());
}
};
template<class K>
struct _HashFunc4
{
size_t RSHash(const char *str)
{
register size_t hash = 0;
size_t magic = 63689;
while (size_t ch = (size_t)*str++)
{
hash = hash * magic + ch;
magic *= 378551;
}
return hash;
}
size_t operator()(const string&s)
{
return RSHash(s.c_str());
}
};
template<class K>
struct _Hashfunc5
{
size_t RSHash(const char *str)
{
register size_t hash = 0;
size_t ch;
for (long i = 0; ch = (size_t)*str++; i++)
{
if ((i & 1) == 0)
{
hash ^= ((hash << 7) ^ ch ^ (hash >> 3));
}
else
{
hash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));
}
}
return hash;
}
size_t operator()(const string &s)
{
return RSHash(s.c_str());
}
};
template<class K =string
, typename HashFunc1 = _HashFunc1<K>
, typename HashFunc2 = _HashFunc2<K>
, typename HashFunc3 = _HashFunc3<K>
, typename HashFunc4 = _HashFunc4<K>
, typename HashFunc5 = _Hashfunc5<K>>
class BloomFiler
{
public:
//构造函数
BloomFiler(size_t n)
:_bs(n * 5 * 2)
, _range(n * 5 * 2)
{}
void Set(const K&key)
{
size_t hash1 = HashFunc1()(key);
size_t hash2 = HashFunc2()(key);
size_t hash3 = HashFunc3()(key);
size_t hash4 = HashFunc4()(key);
size_t hash5 = HashFunc5()(key);
//再用除留余数法映射到范围上
_bs.Set(hash1%_range);
_bs.Set(hash2%_range);
_bs.Set(hash3%_range);
_bs.Set(hash4%_range);
_bs.Set(hash5%_range);
}
bool Test(const K&key)
{
size_t hash1 = HashFunc1()(key);
if (_bs.Test(hash1%_range) == false)
return false;
size_t hash2 = HashFunc2()(key);
if (_bs.Test(hash2%_range) == false)
return false;
size_t hash3 = HashFunc3()(key);
if (_bs.Test(hash3%_range) == false)
return false;
size_t hash4 = HashFunc4()(key);
if (_bs.Test(hash4%_range) == false)
return false;
size_t hash5 = HashFunc5()(key);
if (_bs.Test(hash1%_range) == false)
return false;
return true;//5个都存在,可能存在误判
}
protected:
BitSet _bs;
size_t _range;
};
void TestBloomFiler()
{
BloomFiler<>bf(1000);
string ur1 = "insert";
string ur2 = "sort";
string ur3 = "find";
string ur4 = "string";
bf.Set(ur1);
bf.Set(ur2);
bf.Set(ur3);
bf.Set(ur4);
bf.Set("left");
bf.Set("dict");
cout << "ur1->"<<bf.Test(ur1)<< endl;
cout << "ur2->"<< bf.Test(ur2)<< endl;
cout << "ur3->"<< bf.Test(ur3)<< endl;
cout << "ur4->"<< bf.Test(ur4)<< endl;
cout << "left->"<< bf.Test("lfet") << endl;
cout << "dict->"<< bf.Test("dict")<< endl;
}
2:带引用计数的布隆删除
用一个数组存放每一个位置上的引用计数,每插入一个元素,引用计数加1,当删除时,只要把计数器删除就好了。
template<class K>
struct __HashFunc1
{
size_t BKDRHash(const char *str)
{
register size_t hash = 0;
while (size_t ch = (size_t)*str++)
{
hash = hash * 131 + ch; // 也可以乘以31、131、1313、13131、131313..
}
return hash;
}
size_t operator()(const string &s)
{
return BKDRHash(s.c_str());
}
};
template<class K>
struct __HashFunc2
{
size_t SDBMHash(const char *str)
{
register size_t hash = 0;
while (size_t ch = (size_t)*str++)
{
hash = 65599 * hash + ch;
//hash = (size_t)ch + (hash << 6) + (hash << 16) - hash;
}
return hash;
}
size_t operator()(const string &s)
{
return SDBMHash(s.c_str());
}
};
template<class K>
struct __HashFunc3
{
size_t RSHash(const char *str)
{
if (!*str) // 这是由本人添加,以保证空字符串返回哈希值0
return 0;
register size_t hash = 1315423911;
while(size_t ch = (size_t)*str++)
{
hash ^= ((hash << 5) + ch + (hash >> 2));
}
return hash;
}
size_t operator()(const string &s)
{
return RSHash(s.c_str());
}
};
template<class K>
struct __HashFunc4
{
size_t RSHash(const char *str)
{
register size_t hash = 0;
size_t magic = 63689;
while (size_t ch = (size_t)*str++)
{
hash = hash * magic + ch;
magic *= 378551;
}
return hash;
}
size_t operator()(const string&s)
{
return RSHash(s.c_str());
}
};
template<class K>
struct __HashFunc5
{
size_t RSHash(const char *str)
{
register size_t hash = 0;
size_t ch;
for (long i = 0; ch = (size_t)*str++; i++)
{
if ((i & 1) == 0)
{
hash ^= ((hash << 7) ^ ch ^ (hash >> 3));
}
else
{
hash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));
}
}
return hash;
}
size_t operator()(const string &s)
{
return RSHash(s.c_str());
}
};
template<class K =string
, typename HashFun1 = __HashFunc1<K >
, typename HashFun2 = __HashFunc2<K >
, typename HashFun3 = __HashFunc3<K >
, typename HashFun4 = __HashFunc4<K >
, typename HashFun5 = __HashFunc5<K>>
class BloomFiler
{
public:
BloomFiler(size_t N)
{
_bs.resize(N * 5 * 2);
_range=N*5*2;
}
//每次映射到相同的位置,引用计数加1
void Set(const K&key)
{
size_t Hash1 = HashFun1()(key) % _range;
_bs[Hash1]++;
size_t Hash2 = HashFun2()(key) % _range;
_bs[Hash2]++;
size_t Hash3 = HashFun3()(key) % _range;
_bs[Hash3]++;
size_t Hash4 = HashFun4()(key) % _range;
_bs[Hash4]++;
size_t Hash5 = HashFun5()(key) % _range;
_bs[Hash5]++;
}
bool Test(const K&key)
{
size_t Hash1 = HashFun1()(key) % _range;
if (_bs[Hash1] == false)
return false;
size_t Hash2 = HashFun2()(key) % _range;
if (_bs[Hash2] == false)
return false;
size_t Hash3 = HashFun3()(key) % _range;
if (_bs[Hash3] == false)
return false;
size_t Hash4 = HashFun4()(key) % _range;
if (_bs[Hash4] == false)
return false;
size_t Hash5 = HashFun5()(key) % _range;
if (_bs[Hash5] == false)
return false;
return true;//可能会存在误判;
}
//删除
void ReSet(const K&key)
{
size_t Hash1 = HashFun1()(key) % _range;
if (_bs[Hash1] > 0)
{
_bs[Hash1]--;
}
size_t Hash2 = HashFun2()(key) % _range;
if (_bs[Hash2] > 0)
{
_bs[Hash2]--;
}
size_t Hash3 = HashFun3()(key) % _range;
if (_bs[Hash3] > 0)
{
_bs[Hash3]--;
}
size_t Hash4 = HashFun4()(key) % _range;
if (_bs[Hash4] > 0)
{
_bs[Hash4]--;
}
size_t Hash5 = HashFun5()(key) % _range;
if (_bs[Hash5] > 0)
{
_bs[Hash5]--;
}
}
protected:
vector<size_t>_bs;//节省空间
size_t _range;
};
//数组存放引用计数
void TestBloonFilerCount()
{
BloomFiler<> bf1(1000);
string ur1 = "find";
string ur2 = "insert";
string ur3 = "sort";
bf1.Set(ur1);
bf1.Set(ur2);
bf1.Set(ur3);
cout << "ur1->" << bf1.Test(ur1) << endl;
cout << "ur2->" << bf1.Test(ur2) << endl;
cout << "ur3->" << bf1.Test(ur3) << endl;
bf1.ReSet(ur1);
cout << "ur1->" << bf1.Test(ur1) << endl;
cout << "ur2->" << bf1.Test(ur2) << endl;
cout << "ur3->" << bf1.Test(ur3) << endl;
}
关于位图可参考前面:
http://blog.csdn.net/f2016913/article/details/70553164
五:倒排索引:
倒叙索引的原理和应用:
1:问题:
平时我们都喜欢用搜索引擎,当我们输入一个关键字很快就会找到与这个关键字相关的网页消息,那它是怎么实现的呢?
一个很重要的原因采用的倒叙索引计数;如果不采用倒叙索引计数,每次进行搜索是,都必须遍历一个网页,然后再找出网页中的关键字,这个查找的效率很低的。原因:
(1):网页的基数十分庞大,每天还在不断产生新的网页
(2):在每个网页中的检索是否有所需要的关键字需要遍历整个网页的字符;
2:倒排索引的基本概念:
文档:一般搜索引擎的处理对象是互联网网页,而文档这个概念更宽泛写,代表是以文本的形式存在的存储对象;
单词字典:搜索引擎索引的基本单位是单词,单词字典是文档集合中所有出现单词构成的字符串的集合,
倒排列表:倒排列表记载了出现过某个单词的所有文档的文档列表及单词在该文档中出现的位置信息,每条记录称为一个倒排项。根据倒排列表,即可获知哪些文档包含某个单词。
倒排文件:所有单词的倒排列表往往顺序地存储在磁盘的某个文件里,这个文件即被称之为倒排文件,倒排文件是存储倒排索引的物理文件。
3:倒叙索引的如何工作呢?
简单的说是从这个关键字找到对应的源,而不是从这个源找可能出现的关键字;
例:检索关键字A,首先从单词字典,找到关键字A,然后找单词字典的对应的文档,最后找文档对应的倒排列表。这样效率就会很快。
4:应用:搜索引擎