• 使用两次Hash的Hash表——Twice_Hash_Map


    先回顾一下hash表的相关内容,STL里面的unordered_map和map。

    使用unordered_map,通过hash函数,将key映射到一个位置,如果这个位置原本没有值,那么就可以将这个key作为唯一的下标来访问这个位置。
    但是如果这个位置已经被占了,那么就需要比较这个key和已经存在的下标是否一样。所以unordered_map需要提供key的hash和等于函数。

    由于是使用hash得到的值作为下标,所以会出现不同的key得到相同的下标值,称为冲突。处理冲突的方法,一种是使用一个链表,保存这个位置的key。
    每次hash到这个位置,就逐次比较链表里的key,以确定这个key是否已经存在。另一种方法是使用一个有序二叉树,可以使得查找的效率更高。当然需要提供比较函数。

    STL中的map就是一个用来查找的有序二叉树,查找所需要的比较次数是O(log2(N)),N为已经存在的节点数。而unordered_map,时间复杂度是一次hash的时间加上与冲突项的比较次数,O(1).

    需要注意的是
    1, 实际的时间效率。
    一般情况下,会认为unordered_map比map快。在hash函数较好时,冲突数比较少,unordered_map会快。当需要处理的key很多,hash函数也不是那么好的时候,冲突次数增加,会导致unordered_map效率降低。所以当需要处理大规模的自定义数据,发现unordered_map比较慢的时候,可以试试map。
    2, map所需要的小于比较
    map在插入或查找key的时候,使用的是小于比较。例如插入key,与root节点比较,如果小于root节点,那么就向左查找,如果大于,就向右查找。否则就是等于。所以map只需要小于比较。
    3. unordered_map的自动扩容,
    unordered_map基于数组,当冲突数太大的时候,可以将数组扩大,将hash值对一个更大的质数取余得到下标,可以减少一部分冲突。所以自己提供hash函数的时候,不需要自己使用质数将hash值取余。
    4. 对于自定义数据的hash
    当需要自己写hash函数的时候,就会为hash的效率担忧。网上有各种对字符串和整数的hash方法,如何结合这些方法来实现自定义数据的hash,还是需要好好设计的。一种比较简单的方法是,将自定义数据转化为字符串,比如u16string,u32string。STL提供了这些字符串的hash,等于,小于操作。所以这是非常简单的方法。但是如果使用map,或者类似地,需要转化得到的字符串唯一。另一种方法是使用两次或多次hash。STL的hash表只要一个hash函数,得到一个下标。如果自己使用两次hash,例如
    hash = hash_0 * PRIME + hash_1, 把这个组合的hash值传给unordered_map,然后所使用的PRIME与系统使用的冲突,效果就会很糟糕。那么最好就是自己实现一个类似于unordered_map的hash表,使用这个组合的hash值作为下标。

    一个自定义的使用两个hash函数,并且使用有序二叉树处理冲突的hash表的实现及简单测试如下,

    hashmap.h

    /*
     * hashmap.h
     *
     *  Created on: Dec 26, 2012
     *      Author: chjd
     */
    
    #ifndef HASHMAP_H_
    #define HASHMAP_H_
    
    #include <vector>
    #include <map>
    #include <utility>
    #include <random>
    using std::vector;
    using std::map;
    using std::pair;
    
    #include <algorithm>
    using std::max;
    
    #include <cassert>
    
    template<typename _KeyT,typename _ValueT,typename _HashFn,typename _HashFn2,typename _EqualFn,typename _LessFn>
    class HashMap
    {
    public:
        typedef _KeyT KeyT;
        typedef _ValueT ValueT;
        typedef _HashFn HashFn;
        typedef _HashFn2 HashFn2;
        typedef _EqualFn EqualFn;
        typedef _LessFn LessFn;
    
        typedef map<KeyT,ValueT,LessFn> CellT;
        typedef map<size_t,CellT> DenseDataT;
        typedef vector<CellT> ArrayDataT;
    
    //    typedef pair<const KeyT,ValueT> KeyValuePairT;
        typedef typename CellT::value_type KeyValuePairT;
        typedef ArrayDataT DataT;
    
    private:
        static const unsigned int littlePrimeSize = 7;
        static const unsigned int bigPrimeSize = 13;
        static const size_t maxBit = 24;    // 24 for 32bit memory space and 27bit for 64bit memory space
        static const size_t maxTotal = 13-2;
        static const size_t maxCellSize = 253;
    
        size_t ROW_PRIME;
        size_t COL_PRIME;
        size_t row_pindex;
        size_t col_pindex;
        size_t entrySize;    // ROW_PRIME * COL_PRIME
        size_t entryLoad;    // not empty entry
        size_t cellLoad;    // size of cell
    
        size_t cnt;
    
        DataT data;
    
    // for rehash prime_index
        typedef std::uniform_int_distribution<> Dis;
        typedef std::mt19937 Gen;
        Gen gen_pindex;
        Dis dis_pindex;
    
        size_t littlePrimeTable[littlePrimeSize] = {
                5, 11, 23, 47, 97, 193, 397
        };
    
        size_t bigPrimeTable[bigPrimeSize] = {
                1021,            /* 10 bit */
                2027,            /* 11 bit */
                4013,            /* 12 bit */
                8191,            /* 13 bit */
                16127,            /* 14 bit */
                31321,            /* 15 bit */
                66047,            /* 16 bit */
                131071,            /* 17 bit */
                263167,            /* 18 bit */
                524287,            /* 19 bit */
                1046527,        /* 20 bit */
                2097593,        /* 21 bit */
                3626149,        /* 22 bit */
        };
    //            8388587,        /* 23 bit */
    //            16769023,        /* 24 bit */
    //            33554347,        /* 25 bit */
    //            67108721,        /* 26 bit */
    //            134217613,        /* 27 bit */
    //            253450711,        /* 28 bit */
    //            433494437,        /* 29 bit */
    //            1073676287,        /* 30 bit */
    //            2147483647,        /* 31 bit */
    //            2971215073,        /* 32 bit */
    //    };
    
    
        size_t FinalHash(const KeyT& key)
        {
            size_t hash,hash2;
            HashFn hashFn;
            HashFn2 hashFn2;
            hash = hashFn(key)%COL_PRIME;
            hash2 = hashFn2(key)%ROW_PRIME;
            return (hash2 * COL_PRIME + hash);
        }
    
        void Init(size_t level=0)
        {
            row_pindex = 2+level;
            col_pindex = 4+level;
            dis_pindex = Dis(2, littlePrimeSize-2);
            ReInit();
            data = DataT(entrySize);
        }
    
        void ReInit()
        {
            ROW_PRIME = littlePrimeTable[row_pindex];
            COL_PRIME = bigPrimeTable[col_pindex];
            entrySize = ROW_PRIME*COL_PRIME;
            entryLoad = 0;
            cellLoad = 0;
            cnt = 0;
        }
    
        void WEnLarge(size_t& total)
        {
            if(entryLoad*1.0/entrySize>0.95 && total<maxTotal)
            {
                total += 1;
            }
        }
    
        void UpdatePrimeIndex(size_t total)
        {
            row_pindex = (row_pindex + dis_pindex(gen_pindex))%littlePrimeSize;
            col_pindex = total - row_pindex;
        }
    
        bool WReHash()
        {
            cnt++;
            if(cnt<maxCellSize)
                return false;
            cnt = 0;
            if(cellLoad>maxCellSize)
            {
                size_t total = row_pindex+col_pindex;
                WEnLarge(total);
                UpdatePrimeIndex(total);
                ReInit();
                return true;
            }
            else
                return false;
        }
    
        void ReHash()
        {
            DataT t_data = DataT(entrySize);
            for(auto e_it=data.begin(),e_et=data.end();e_it!=e_et;e_it++)
            {
                if(e_it->empty())
                    continue;
                for(auto c_it=e_it->begin(),c_et=e_it->end();c_it!=c_et;c_it++)
                {
                    auto it = insert_key(t_data,*c_it);
                    assert(it.second==true);
                }
            }
            data.clear();
            data.swap(t_data);
        }
    
    
        pair<KeyValuePairT,bool> insert_key(DataT& t_data,KeyValuePairT pairValue)
        {
            size_t pos = FinalHash(pairValue.first);
            CellT& entry = t_data[pos];
            if(entry.empty())
                entryLoad++;
            auto it = entry.insert(pairValue);
            cellLoad = max(entry.size(),cellLoad);
            return make_pair(make_pair(it.first->first,it.first->second),it.second);
        }
    
    public:
        HashMap(size_t level=0)
        {
            Init();
        }
    
        bool find(const KeyT& key,ValueT& value)
        {
            size_t pos = FinalHash(key);
            CellT& entry = data[pos];
            if(entry.empty())
                return false;
            auto cell_it = entry.find(key);
            if(cell_it==entry.end())
                return false;
    
            value = cell_it->second;
            return true;
        }
    
        ValueT& operator [] (KeyT& key)
        {
            size_t pos = FinalHash(key);
            CellT& entry = data[pos];
            return entry[key];
        }
    
        pair<KeyValuePairT,bool> insert(KeyValuePairT pairValue)
        {
            if(WReHash())
                ReHash();
            return insert_key(data,pairValue);
        }
    
        pair<double,double> LoadFactor()
        {
            double entry =  entryLoad * 1.0 / entrySize;
            double cell = cellLoad * 1.0 / maxCellSize;
            return pair<double,double>(entry,cell);
        }
    };
    
    
    /*
     * any to string, when use string as key of map
     *
     * int, u32string
     * address, size_t, 32bit or 64bit, u16string
     * N = sizeof(addr)>>2;
     * str = u16string(N,char16_t())
     * mask = size_t(0xFFFF);
     * tmp_addr = (size_t)addr;
     * for(i=0;i<N;i++)
     *         short x = short(tmp_addr & mask);
     *        str[i] = char16_t(x);
     *        tmp_addr = tmp_addr >> 16;
     *
     */
    
    #endif /* HASHMAP_H_ */

    测试相关的文件test.h和test.cpp,

    View Code
    /*
     * test.h
     *
     *  Created on: Dec 27, 2012
     *      Author: chjd
     */
    
    #ifndef TEST_H_
    #define TEST_H_
    
    
    #include <string>
    using std::string;
    
    #include <unordered_map>
    using std::unordered_map;
    
    #include <iostream>
    using std::cout;
    using std::endl;
    
    #include <random>
    
    #include "hashmap.h"
    
    #include <functional>
    using std::hash;
    
    #include <ctime>
    using std::clock;
    using std::clock_t;
    
    #include <sparsehash/dense_hash_map>
    using google::dense_hash_map;
    
    class JsHash
    {
    public:
        size_t operator()(const char* str) const
        {
            size_t hash = 1315423911;
            while (*str)
            {
                hash ^= ((hash << 5) + (*str++) + (hash >> 2));
            }
            return (hash & 0x7FFFFFFF);
        }
    };
    
    class HashStr
    {
    public:
        size_t operator()(const string& s) const
        {
            hash<string> hashFn;
            return hashFn(s);
        }
    };
    
    class HashStr2
    {
    public:
        size_t operator()(const string& s) const
        {
            JsHash jsHash;
            return jsHash(s.c_str());
        }
    };
    
    class EqualStr
    {
    public:
        bool operator()(const string& a, const string& b) const
        {
            return a == b;
        }
    };
    
    class LessStr
    {
    public:
        bool operator()(const string& a, const string& b) const
        {
            return a < b;
        }
    };
    
    typedef HashMap<string, size_t, HashStr, HashStr2, EqualStr, LessStr> SHashMap;
    typedef unordered_map<string, size_t> SUMap;
    typedef map<string,size_t> SMap;
    typedef dense_hash_map<string, size_t, HashStr,EqualStr> DMap;
    
    typedef std::uniform_int_distribution<> DisUniInt;
    typedef std::mt19937 GenInt;
    
    
    
    #endif /* TEST_H_ */
    View Code
    /*
     * test.cpp
     *
     *  Created on: Dec 27, 2012
     *      Author: chjd
     */
    
    #include "test.h"
    
    void GenString(DisUniInt& disl, DisUniInt& disa, GenInt& gen, string& s)
    {
        int length = disl(gen);
        s = string(length, char(0));
        for (int i = length - 1; i >= 0; i--)
            s[i] = char(disa(gen));
    }
    
    int TestSHashMap(size_t N)
    {
        SHashMap table;
        DisUniInt disl(5, 32);
        DisUniInt disa(int('a'), int('z'));
        GenInt gen;
        size_t cnt = 0;
        clock_t t0 = clock();
        for (size_t i = 0; i < N; i++)
        {
            string s;
            GenString(disl, disa, gen, s);
    //        cout << s << endl;
            auto ok = table.insert(make_pair(s, i));
            if (ok.second)
                cnt += 1;
    /*        else
            {
                cout << ok.first.first << " -> " << ok.first.second << endl;
            }
    */    }
        clock_t t1 = clock();
        double t2 = (t1 - t0) * 1.0 / CLOCKS_PER_SEC;
        cout << "time:" << t2 << endl;
        pair<double,double> factor = table.LoadFactor();
        cout << "entry:" << factor.first << "\t cell:" << factor.second << endl;
        cout << "cnt:" << cnt << endl;
        return cnt;
    }
    
    int TestUMap(size_t N)
    {
        SUMap table;
        DisUniInt disl(5, 32);
        DisUniInt disa(0, 255);
        GenInt gen;
        size_t cnt = 0;
        clock_t t0 = clock();
        for (size_t i = 0; i < N; i++)
        {
            string s;
            GenString(disl, disa, gen, s);
            auto ok = table.insert(make_pair(s, i));
            if (ok.second)
                cnt++;
        }
        clock_t t1 = clock();
        double t2 = (t1 - t0) * 1.0 / CLOCKS_PER_SEC;
        cout << "time:" << t2 << endl;
        cout << "cnt:" << cnt << endl;
        return cnt;
    }
    
    int TestSMap(size_t N)
    {
        SMap table;
        DisUniInt disl(5, 32);
        DisUniInt disa(0, 255);
        GenInt gen;
        size_t cnt = 0;
        clock_t t0 = clock();
        for (size_t i = 0; i < N; i++)
        {
            string s;
            GenString(disl, disa, gen, s);
            auto ok = table.insert(make_pair(s, i));
            if (ok.second)
                cnt++;
        }
        clock_t t1 = clock();
        double t2 = (t1 - t0) * 1.0 / CLOCKS_PER_SEC;
        cout << "time:" << t2 << endl;
        cout << "cnt:" << cnt << endl;
        return cnt;
    }
    
    int TestDMap(size_t N)
    {
        DMap table;
        DisUniInt disl(5, 32);
        DisUniInt disa(0, 255);
        GenInt gen;
        size_t cnt = 0;
        clock_t t0 = clock();
        table.set_empty_key(string());
        for (size_t i = 0; i < N; i++)
        {
            string s;
            GenString(disl, disa, gen, s);
            auto ok = table.insert(make_pair(s, i));
            if (ok.second)
                cnt++;
    
        }
        clock_t t1 = clock();
        double t2 = (t1 - t0) * 1.0 / CLOCKS_PER_SEC;
        cout << "time:" << t2 << endl;
        cout << "cnt:" << cnt << endl;
        return cnt;
    }
    
    bool TestCorrect()
    {
        size_t N = 10000000;
        SUMap table_0;
        SHashMap table_1;
        DisUniInt disl(5, 32);
        DisUniInt disa(0, 255);
        GenInt gen;
        size_t cnt_0 = 0, cnt_1=0;
        for (size_t i = 0; i < N; i++)
        {
            string s;
            GenString(disl, disa, gen, s);
            auto ok = table_0.insert(make_pair(s, i));
            if (ok.second)
                cnt_0++;
            auto ok_1 = table_1.insert(make_pair(s,i));
            if(ok_1.second)
                cnt_1++;
        }
        cout << "cnt:" << cnt_0 << "\t" << cnt_1 << endl;
        return cnt_0==cnt_1;
    }
    
    int main()
    {
    //    size_t N = 100000;
        TestCorrect();
    /*
        TestSHashMap(N);
        TestUMap(N);
        TestSMap(N);
        TestDMap(N);
    */
        return 0;
    }

    测试比较了自己实现的HashMap,STL的unordered_map,map,和google dense_hash_map。在数据比较小的时候,测试结果还好,当数据比较多的时候,会比unordered_map慢一些。应该是扩容的部分没写好吧。

    目前就先这样了。

  • 相关阅读:
    Git commit 信息标准和丢弃必须要的commit
    Markdown list状态下插入代码
    INIT: vesion 2.88 booting
    I.MX6 support eMMC 5.0
    GitLab non-standard SSH port
    PenMount Touch显示鼠标指针驱动安装
    Android WebView remote debugging
    Android预安装可卸载程序
    jmeter(六)元件的作用域与执行顺序
    jmeter(五)JDBC Request
  • 原文地址:https://www.cnblogs.com/Frandy/p/twice_hash_map_map.html
Copyright © 2020-2023  润新知