热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

HashTable,位图,BloomFilter分析(简单粗暴)

**HashTable:(散列表哈希表)**是根据关键字key而直接访问在内存存储位置的数据结构;它通过一个关键值的函数将所需的数据映射到表中的位置来访问数据,这个映射函数叫做

**

HashTable:(散列表/哈希表)

**
是根据关键字key而直接访问在内存存储位置的数据结构;它通过一个关键值的函数将所需的数据映射到表中的位置来访问数据,这个映射函数叫做散列函数,存放记录的数组叫做散列表;
构造哈希表的几种方法:
1)直接定址法:取关键字的某个线性函数为散列地址;(但数值之间相差较大,该方法不可用)
2)除留余数法:取关键值被某个不大于散列表长m的数p除后的所得的余数为散列地址;(会出现不同值会映射到相同的位置—哈希冲突)
处理哈西冲突的闭散列方法—–开放定址法
a)线性探测:一个位置有了就往后放,直到遇到空后放在空位置(且表示没有该值)
访问h+1 -> h+2 -> h+3…
b)二次探测:+平方
访问h+1^2 -> h+2^2 -> h+3^3 ->…
h = 插入的值%散列表长度(得到数组的下标)
负载因子=元素个数/散列表长度
对于“开放定址法”,负载因子决定着重要作用,当负载因子达到某一个固定值得时候就要扩增散列表的长度,这样才能缓解哈希冲突;
使用素数作为哈希表的容量可以减少哈希冲突;
处理哈希冲突的方法:开链法/拉链法(哈希桶)

3)平方取中法;
4).折叠法;
5).随机数法;
6).数学分析法;

代码示例:


#include
#include

//线性探测和二次方探测

//enum State
//{
// EXIST,DELETE,EMPTY
//};
//
//定义一个外部函数,创建一个素数表,每次扩容的大小从这里面选取
//当素数做除数的时候,产生的哈希冲突会减少
static size_t GetPrime(size_t cap)
{
    const int _PriListLen = 28;
    static const unsigned long _PriList[_PriListLen] = 
    {
        53ul,         97ul,         193ul,       389ul,       769ul,
        1543ul,       3079ul,       6151ul,      12289ul,     24593ul,
        49157ul,      98317ul,      196613ul,    393241ul,    786433ul,
        1572869ul,    3145739ul,    6291469ul,   12582917ul,  25165843ul,
        50331653ul,   100663319ul,  201326611ul, 402653189ul, 805306457ul,
        1610612741ul, 3221225473ul, 4294967291ul
    };
    for(size_t i = 0; i <_PriListLen; i++)
    {
        if(_PriList[i] > cap)
        {
            return _PriList[i];
        }
    }
    return _PriList[_PriListLen-1];
}

//
//template 
//struct HashNode
//{
// pair _kv;
// State _state;
//
// HashNode(const K& key,const V& value)
// :_kv(key,value),_state(EMPTY)
// {}
// HashNode()
// :_state(EMPTY)
// {}
//};
//
//template 
//struct _GetFunc
//{
// size_t operator()(const K& key)
// {
// return key;
// }
//};
//
////如果是字符创作为键值的话应该重新定义
//template 
//struct _GetFuncString
//{
// static size_t _getfunc(const char* key)
// {
// signed int seek = 131;//31 131 1313 13131 131313 
// signed int hash = 0;
// while(*key)
// {
// hash = hash*seek + (*key++);
// }
// //类似于整数“1234”的保存方式是1*10^3+2*10^2+3*10^1+4*10^0这样的把字符拆开一个一个算总和存储
// return (hash & 0x7FFFFFFF);
// }
// size_t operator()(const string& key)
// {
// return _getfunc(key.c_str());
// }
//};
//
//
//template >
//class HashTable
//{
//public: 
// typedef HashNode Node;
//protected:
// vector> _table;
// size_t _size;//表示实际元素的个数
//public:
// HashTable()
// :_size(0)
// {}
// 
// //获取key键值,求出映射的下标
// size_t GetFunc(const K& kv)
// {
// _GetFunc hash;
// return hash(kv) % _table.size();
// }
// //插入一个值
// //当节点状态是“空/删除”状态时是可以插入节点的
// bool Insert(pair& kv)
// {
// _checkCapacity();//检查容量
// size_t index = GetFunc(kv.first);
// size_t i = 1;
// while(_table[index]._state == EXIST)
// {
// if(_table[index]._kv.first == kv.first)
// {
// return false;//不允许出现相同的键值
// }
// //二次方探测
// index += i * i;
// index = index % _table.size();
// ++i;
// //线性探测
// /*++index;
// if(index == _table.size())
// {
// index = 0;
// }*/
// }
// _table[index]._kv = kv;
// _table[index]._state = EXIST;
// ++_size;//哈希表中的元素个数增加
// return true;
// }
// //查找某个节点
// //只有当节点状态是存在时我们的查找才有效
// Node* Find(const K& key)
// {
// size_t index = GetFunc(key);
// //如果遇到空位/删除还是没有找到,则说明该节点不存在
// while(_table[index]._state == EXIST)
// {
// if(_table[index]._kv.first == key)
// {
// return &_table[index];
// }
// ++index;
// if(index == _table.size())
// {
// index = 0;
// }
// }
// return NULL;
// }
// //删除一个节点,对于数组来讲删除一个节点话费太大,
// //不能真的删除,我们只需要将节点的状态置为DELETE,_table的大小--即可,下次就可以插入新节点
// bool Remove(const K& key)
// {
// if(_size == 0)
// {
// return false;
// }
// Node* cur = Find(key);
// if(cur)
// {
// cur->_state = DELETE;
// --_size;
// return true;
// }
// return false;
// /*size_t index = GetFunc(key);
// while(_table[index]._state == EXIST)
// {
// if(_table[index]._kv.first == key)
// {
// _table[index]._state = DELETE;
// --_size;
// return true;
// }
// ++index;
// if(index == _table.size())
// {
// index = 0;
// }
// }*/
// }
//
//private:
// void _checkCapacity()
// {
// if(_table.empty())
// {
// _table.resize(GetPrime(0));
// return;
// }
// //检查是否需要扩容
// if(((_size*10)/_table.size()) >= 7)//负载因子设定为0.7
// {
// //需要开始扩容
// size_t newSize = GetPrime(_table.size());
// HashTable newHash;
// newHash._table.resize(newSize);
// for(size_t i = 0; i <_table.size(); i++)
// {
// if(_table[i]._state == EXIST)
// {
// newHash.Insert(_table[i]._kv);
// }
// }
// _table.swap(newHash._table);
//
// }
// }
//};
//




//开链法(哈希桶)

template <class K,class V>
struct HashNode
{
    pair _kv;
    HashNode* _next;//指向下一个节点

    HashNode(const pair& kv)
        :_kv(kv),_next(NULL)
    {}
};


template<class K>
struct _GetFunc
{
    size_t operator()(const K& key)
    {
        return key;
    }
};


struct _GetFuncString
{
    size_t _GetFunc(const char* key)
    {
        signed int seek = 131;
        signed int hash = 0;
        while(*key)
        {
            hash = hash*seek + (*key++);
        }
        return (hash & 0x7FFFFFFF);
    }

    size_t operator()(const string key)
    {
        return _GetFunc(key.c_str());
    }
};

template<class K, class V, class GetFunc>
class HashTable;//先声明一下

template <class K,class V,class GetFunc=_GetFunc>
struct HashTableIterator
{
    typedef HashNode Node;
    typedef HashTableIterator Self;
    typedef HashTable HashTable;
protected:
    Node* _node;
    HashTable* _hash;

    size_t getFunc(const K& key)
    {
        GetFunc hash;
        return hash(key) % (_hash->_table.size());
    }

public:
    HashTableIterator(Node* node,HashTable* hash)
        :_node(node),_hash(hash)
    {}
    Self& operator=(const Self& h)
    {
        if(this != &h)
        {
            this->_node = h._node;
            this->_hash = h._hash;
        }
        return *this;
    }

    pair& operator*()
    {
        return _node->_kv;
    }
    pair* operator->()
    {
        return &_node->_kv;
    }
    Self& operator++()//前置++
    {
        if(_node->_next)
        {
            _node = _node->_next;
        }
        else//可能是某个位置的最后一个结点了
        {
            size_t index = getFunc(_node->_kv.first);
            _node = NULL;
            for(size_t i = index+1; i <(_hash->_table.size()); i++)
            {
                if(_hash->_table[i])
                {
                    _node = _hash->_table[i];
                    break;
                }
            }
        }
        return *this;
    }
    Self operator++(int)
    {
        Self cur = *this;
        ++(*this);
        return cur;
    }
    bool operator!=(const Self& h) const 
    {
        return _node != h._node;
    }

};

template <class K,class V,class GetFunc=_GetFuncString>
class HashTable
{
public:
    typedef HashNode Node;
    typedef HashTableIterator Iterator;
    friend struct HashTableIterator;//因为_table是保护成员,外部对象不可以直接访问
protected:
    vector*> _table;//数组中放的应该是节点
    size_t _size;//有效数据个数
public:
    HashTable()
        :_size(0)
    {}
    ~HashTable()
    {
        for(size_t i = 0; i <_table.size(); i++)
        {
            Node* del = _table[i];
            while(del)
            {
                Node* next = del->_next;
                delete del;
                del = next;
            }
            _table[i] = NULL;
        }
        /*_size = 0;*/
    }
    //获取下标
    size_t getFunc(const K& key)
    {
        GetFunc hash;
        return hash(key) % _table.size();
    }
    //插入数据
    bool Insert(const pair& kv)
    {
        //先检查容量
        _checkCapacity();
        //求下标
        size_t index = getFunc(kv.first);
        //头插,先判断插入的结点是否有效
        Node* cur = _table[index];
        while(cur)
        {
            if(cur->_kv.first == kv.first)
            {
                return false;
            }
            cur = cur->_next;
        }
        cur = new Node(kv);
        cur->_next = _table[index];
        _table[index] = cur;
        ++_size;
        return true;
    }
    pairbool> InsertPair(const pair& kv)
    {
        //先检查容量
        _checkCapacity();
        //求下标
        size_t index = getFunc(kv.first);
        //头插,先判断插入的结点是否有效
        Node* cur = _table[index];
        while(cur)
        {
            if(cur->_kv.first == kv.first)
            {
                return make_pair(Iterator(cur,this),false);
            }
            cur = cur->_next;
        }
        cur = new Node(kv);
        cur->_next = _table[index];
        _table[index] = cur;
        ++_size;
        return make_pair(Iterator(cur,this),true);
    }

    V& operator[](const K& key)
    {
        pairbool> tmp = this->InsertPair(make_pair(key,V()));
        //如果没有就插入,如果有就直接返回并且迭代器指向它
        return tmp.first->second;
    }
    //找到一个结点
    Node* Find(const K& key)
    {
        if(_size == 0)
        {
            return NULL;
        }
        size_t index = getFunc(key);
        Node* cur = _table[index];
        while(cur)
        {
            if(cur->_kv.first == key)
            {
                return cur;
            }
            cur = cur->_next;
        }
        return NULL;
    }
    //删除一个结点
    bool Remove(const K& key)
    {
        if(_size == 0)
        {
            return false;
        }
        size_t index = getFunc(key);
        Node* prev = NULL;
        Node* del = _table[index];
        while(del)
        {
            if(del->_kv.first == key)
            {
                if(prev == NULL)
                {
                    _table[index] = del->_next;
                }
                else
                {
                    prev->_next = del->_next;
                }
                delete del;
                --_size;
                return true;
            }
            prev = del;
            del = del->_next;
        }
        return false;
    }

    void Resize(size_t size)
    {
        _table.resize(GetPrime(size));
    }

    Iterator Begin()
    {
        if(_size == 0)
        {
            return Iterator(NULL,this);
        }
        Node* cur = NULL;
        for(size_t i = 0; i <_table.size(); i++)
        {
            if(_table[i])
            {
                cur = _table[i];
                break;
            }
        }
        return Iterator(cur,this);
    }

    Iterator End()
    {
        return Iterator(NULL,this);
    }

private:
    void _checkCapacity()
    {
        if(_table.size() == _size)
        {
            size_t newsize = GetPrime(_size);
            //扩容的时候理应重新排一下位置
            vector newtable;
            newtable.resize(newsize);
            for(size_t i = 0; i <_size; i++)
            {
                Node* cur = _table[i];
                while(cur)
                {
                    Node* next = cur->_next;//保存下一个节点
                    size_t index = getFunc(cur->_kv.first);//重新计算下标
                    cur->_next = newtable[index];
                    newtable[index] = cur;
                    cur = next;
                }
                //这里为什么不用直接插入的方法?因为插入需要重新new新节点,这样一来花费太大
                //为什么线性探测需要用插入呢?因为线性探测数组中每一个元素就放一个数据,
                //不需要new节点,直接赋值运算,所有这其实是一个重复的动作,所以调用插入函数
                _table[i] = NULL;
            }
            _table.swap(newtable);
        }
    }

};

//make_pair的模板函数
//template 
//pair make_pair(const K& key,const V& value)
//{
// return pair(key,value);
//}

应用:
**

位图:

**
是一个数组,数组中的每个数据的每个二进制位都表示一个数据,数据存在表示0,数据不存在表示1;
下标表示除的结果,二进制的位置表示除的余数;

面试题:存储一个40亿的无符号整数,没有排序
思路:
1G大约等于10亿个byte(字节)
1byte = 8 bit(8个比特位)(每一个比特位可以标志一个数)
存放40亿个数需要40亿个位置
1G大概可以存放80亿个数,所以存放40亿个数需要500MB左右;
**

布隆过滤器:

**
哈希+位图
当一个元素被加入数组时,通过 K 个 HashFunc 函数将这个元素映射成一个位阵列中的 K 个点,把它们置为 1(和位图思想一样,只是它要占用多个二进制位)。检索时,我们只要看看这些点是不是都是 1 就(大约)知道集合中有没有它了:
如果这些点有任何一个 0,则被检索元素一定不在;
如果都是 1,则被检索元素很可能在。(记住是可能)
优点是空间效率很高,布隆过滤器存储空间和插入 / 查询时间都是常数O(k)。
缺点
误算率是其中之一。随着存入的元素数量增加,误算率随之增加。但是如果元素数量太少,则使用散列表足矣。误判补救方法是:再建立一个小的白名单,存储那些可能被误判的信息。
注:要保证安全地删除元素并不简单。首先我们必须保证删除的元素的确在布隆过滤器里面, 这一点单凭过滤器是无法保证的,而且计数器回绕也会造成问题。

位图和布隆过滤器的区别:
位图——>利用哈希直接定址法;优点是节省空间,快;缺点是只能运用于整形;
布隆过滤器——->哈希+位图;优点是节省空间,快,可以标记任意类型;缺点是会产生误判;

代码示例:

//BitMap位图
//利用哈希的直接定址法,用来表示一个整形存在或者不存在的数组

#include
#include

typedef size_t MyType;

class BitMap
{
protected:
    vector<char> _bitMap;
public:
    BitMap(MyType range)
    {
        //给数组开空间
        _bitMap.resize(range/8+1,0);

    }
    //将数据置入数组中
    void Set(size_t value)
    {
        size_t index = value/8;//找到数组下标
        size_t pos = value%8;//找到元素中的二进制的位置
        _bitMap[index] |= (1<//将数据从数组中删除
    void ReSet(size_t value)
    {
        size_t index = value>>3;
        size_t pos = value%8;
        _bitMap[index] &= (~(1<//判断某个数在不在
    bool Test(size_t value)
    {
        size_t index = value>>3;
        size_t pos = value%8;
        return (_bitMap[index] & (1<void TestBitMap()
{
    //BitMap bm(1024*1024*1024*4);
    BitMap bm(-1);
    bm.Set(1101);
    bm.Set(1001);
    bm.Set(1001111);
    bm.Set(1002221);

    cout<1101)<cout<1102)<cout<1001)<cout<1002221)<//布隆过滤器-------->哈希+位图(可以标记任意类型)

struct _GetFunc1
{
    size_t GetFunc(const char* value)
    {
        size_t seek = 131;
        register size_t hash = 0;
        while(*value)
        {
            hash = hash*seek + (*value++);
        }
        return hash;
    }
    size_t operator()(const string& key)
    {
        return GetFunc(key.c_str());
    }
};

struct _GetFunc2
{
    size_t GetFunc(const char* value)
    {
        register size_t hash = 0;
        size_t seek = 63689;
        while(*value)
        {
            hash = hash*seek + (*value++);
            seek *= 378551;
        }
        return hash;
    }
    size_t operator()(const string& key)
    {
        return GetFunc(key.c_str());
    }
};

struct _GetFunc3
{
    size_t GetFunc(const char* value)
    {
        if(!*value)    
            return 0;  
        register size_t hash = 1315423911;  
        while (size_t ch = (size_t)*value++)  
        {  
            hash ^= ((hash <<5) + ch + (hash >> 2));  
        }  
        return hash;  
    }
    size_t operator()(const string& key)
    {
        return GetFunc(key.c_str());
    }
};

template <class K=string,class HashFunc1=_GetFunc1,
          class HashFunc2=_GetFunc2,class HashFunc3=_GetFunc3>
class BloomFilter
{
protected:
    BitMap _BM;
    size_t _range;
public:
    BloomFilter(size_t range)
        :_range(range),_BM(range)
    {
    }
    void Set(const K key)
    {
        //将传进来的参数转化成size_t,但是转化后的大小还不能超过范围
        size_t hash1 = HashFunc1()(key) % _range;
        //这里为什么要%_range,如果这个数据转化成size_t类型变得超级大了,
        //那么就无法正确定位到有效的下标
        size_t hash2 = HashFunc2()(key) % _range;
        size_t hash3 = HashFunc3()(key) % _range;
        _BM.Set(hash1);
        _BM.Set(hash2);
        _BM.Set(hash3);
    }
    bool Test(const K key)
    {
        size_t hash1 = HashFunc1()(key) % _range;
        if(_BM.Test(hash1) == false)
        {
            return false;
        }
        size_t hash2 = HashFunc2()(key) % _range;
        if(_BM.Test(hash2) == false)
        {
            return false;
        }
        size_t hash3 = HashFunc3()(key) % _range;
        if(_BM.Test(hash3) == false)
        {
            return false;
        }
        return true;
    }
    //这里为什么不写删除?因为布隆过滤器任意删除一个数据都可能会影响到其它的数据,比较复杂,可以自己研究

};



推荐阅读
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文介绍了如何使用python从列表中删除所有的零,并将结果以列表形式输出,同时提供了示例格式。 ... [详细]
  • ALTERTABLE通过更改、添加、除去列和约束,或者通过启用或禁用约束和触发器来更改表的定义。语法ALTERTABLEtable{[ALTERCOLUMNcolu ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • MyBatis多表查询与动态SQL使用
    本文介绍了MyBatis多表查询与动态SQL的使用方法,包括一对一查询和一对多查询。同时还介绍了动态SQL的使用,包括if标签、trim标签、where标签、set标签和foreach标签的用法。文章还提供了相关的配置信息和示例代码。 ... [详细]
  • 本文介绍了游标的使用方法,并以一个水果供应商数据库为例进行了说明。首先创建了一个名为fruits的表,包含了水果的id、供应商id、名称和价格等字段。然后使用游标查询了水果的名称和价格,并将结果输出。最后对游标进行了关闭操作。通过本文可以了解到游标在数据库操作中的应用。 ... [详细]
  • 本文详细介绍了Spring的JdbcTemplate的使用方法,包括执行存储过程、存储函数的call()方法,执行任何SQL语句的execute()方法,单个更新和批量更新的update()和batchUpdate()方法,以及单查和列表查询的query()和queryForXXX()方法。提供了经过测试的API供使用。 ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 本文介绍了最长上升子序列问题的一个变种解法,通过记录拐点的位置,将问题拆分为左右两个LIS问题。详细讲解了算法的实现过程,并给出了相应的代码。 ... [详细]
  • 浏览器中的异常检测算法及其在深度学习中的应用
    本文介绍了在浏览器中进行异常检测的算法,包括统计学方法和机器学习方法,并探讨了异常检测在深度学习中的应用。异常检测在金融领域的信用卡欺诈、企业安全领域的非法入侵、IT运维中的设备维护时间点预测等方面具有广泛的应用。通过使用TensorFlow.js进行异常检测,可以实现对单变量和多变量异常的检测。统计学方法通过估计数据的分布概率来计算数据点的异常概率,而机器学习方法则通过训练数据来建立异常检测模型。 ... [详细]
  • Redis底层数据结构之压缩列表的介绍及实现原理
    本文介绍了Redis底层数据结构之压缩列表的概念、实现原理以及使用场景。压缩列表是Redis为了节约内存而开发的一种顺序数据结构,由特殊编码的连续内存块组成。文章详细解释了压缩列表的构成和各个属性的含义,以及如何通过指针来计算表尾节点的地址。压缩列表适用于列表键和哈希键中只包含少量小整数值和短字符串的情况。通过使用压缩列表,可以有效减少内存占用,提升Redis的性能。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
author-avatar
伊人憔悴儡
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有