zoukankan      html  css  js  c++  java
  • 变参模板之万用hash函数

    首先向侯捷大佬致敬。

    本文记录一个自定义的通用hash函数的写法,涉及了变参模板的用法

    template<typename T>
    void hash_combine(size_t& seed, const T& val)
    {
        /*使得seed足够乱序而已*/
        seed ^= std::hash<T>()(val)+0x9e3779b9 + (seed << 6) + (seed >> 2);
    }
    
    template<typename T>
    void hash_val(size_t& seed, const T& val){
        hash_combine(seed, val);
    }
    
    template<typename T,typename... Types>
    void hash_val(size_t& seed, const T& val, const Types&... args){
        hash_combine(seed, val);
        hash_val(seed, args...);
    }
    
    template<typename... Types>
    size_t hash_val(const Types... args){
        size_t seed = 0;
        hash_val(seed, args...);
        return seed;
    }
    /*自定义类*/
    class Customer
    {
    public:
        Customer(string f, string l, long num):fname(f),lname(l),no(num){}
    bool operator==(const Customer& tmp)const { return this->no == tmp.no; } string fname; string lname; long no; }; /*hash仿函数*/ class CustomerHash_func { public: std::size_t operator()(const Customer& c)const { return hash_val(c.fname, c.lname, c.no); } };

    测程序如下:

    /*
            *unordered_set需要四个模板参数
            *一般使用只需要填第一个,它表示容器中元素的类型
            *当容器存放的类型是自定义类型,第二个参数就被填入,一个求hash值的hash函数
            *第三个参数表示判断元素相等的准则,默认使用的是std::equal_to,里边就是==符号比较
            *所以自定义类型需要重载这个符号
            *最后一个参数是分配器,这里就不讨论了
        */
        unordered_set<Customer, CustomerHash_func> m_hashset;
        m_hashset.insert(Customer("c++", "program", 996L));
    
        int bucket_cout = m_hashset.bucket_count();
    
        /*预判Customer("c++", "program", 996L)将位于哪一个位置*/
        CustomerHash_func test_hf;
        cout << "now bucket_cout is:" << bucket_cout << endl;;
        cout << "Customer("c++", "program", 996L) will be in bucket #" << 
            test_hf(Customer("c++", "program", 996L)) % bucket_cout << endl;
        
        /*打印容器此时内部存储情况*/
        for (unsigned int i = 0; i < bucket_cout; ++i)
        {
            cout << "bucket #" << i << " is:";
            for (auto it = m_hashset.begin(i); it != m_hashset.end(i); ++it)
            {
                cout << "fname:" << it->fname << " " << "lname:" << it->lname << " " << " no:" << it->no;
            }
            cout << "
    ";
        }

    控制台显示:

    可以看出计算出来的hash值是5,bucket_cout方法可得出当前哈希表(unordered_set的内部容器)的桶长度,插一句嘴:哈希表又叫散列表,其结构没记错应该是vector加list,这个vector常常称为桶;也就是用vector的每个节点去挂上一条链表,链表就是hash冲突形成的,此时如果再次插入一个Customer且其算出来的hash值等于5,那它就会继续被挂在“五号桶”上,和之前挂的Customer("c++","program",996L)形成一条两个节点的链表;当这个链表太长也就是所谓的hash冲突过多就会导致rehash,rehash就是分两步,先扩充vector,然后将当前容器里所以的节点重新算hash值,然后挂到该挂的桶;我感觉自己已经说的挺直白了哈哈哈;

    更多关于hash表的知识可参阅stl源码剖析,关于这本书网上有一些言论说太老不值得看,怎么说呢还是看人,技术大佬们肯定另说啦;当前时间为2020/11/19,算了一下18年有余,哈哈哈,刚刚了解到有此书时也稍受言论影响,过了一段时间后还是遵从本心去买来看看(技术较菜,时间也有,且不是很贵哈哈),一个原因也是当前市面上暂未发现有此类书籍,个人感觉对于学习理解stl有较大用处。

    打印:

    补充另外一种方法:

    通过偏特化hash来实现,实现方法于上一种略有不同,熟悉偏特化的同学应该挺清楚的,效果与前边一致,有兴趣的同学可以自己调试下

    template<>
    struct hash < Customer > {
    	size_t operator()(const Customer& c)const
    	{
    		return hash_val(c.fname, c.lname, c.no);
    	}
    };
    
    	/*
    		*不需要自定义仿函数,还是使用默认的hash参数,但是通过偏特化自定义类型来起作用
    		*通过vs里f12跳进去看看原型就一目了然了
    		*效果和上边使用仿函数的方法一模一样
    	*/
    	unordered_set<Customer> m_hashset_ex;
    	m_hashset_ex.insert(Customer("c++", "program", 996L));
    

      

  • 相关阅读:
    HBase 使用与原理总结
    Java8 的一些使用总结
    对Redis的一些理解
    BERT模型源码解析 -- 转载
    BERT原理解析 -- 转载
    SQL 多表查询之 where和INNER JOIN
    Spark 数据处理相关代码
    Sql实战 1.单表复用进行比较排名
    LeetCode 47.括号生成 DFS递归
    LeetCode 46.机器人的运动范围
  • 原文地址:https://www.cnblogs.com/Cxiangyang/p/14005815.html
Copyright © 2011-2022 走看看