c++ 一个好的向量散列函数

iaqfqrcu  于 2022-12-15  发布在  其他
关注(0)|答案(4)|浏览(215)

我有一些整数向量,我想在c++11中高效地存储在一个unordered_map中我的问题是:
如何最好地存储这些数据并针对.find查询进行优化?
我想出了以下的hasher:

class uint32_vector_hasher {
public:
  std::size_t operator()(std::vector<uint32_t> const& vec) const {
    std::size_t ret = 0;
    for(auto& i : vec) {
      ret ^= std::hash<uint32_t>()(i);
    }
    return ret;
  }
};

然后将对象存储在unordered_map中,但是我有几个问题
1.多久计算一次散列,仅一次,某个随机数或次数?
1.使用==和散列函数创建一个 Package 器对象,以便记住散列并避免多次计算它,这样做有意义吗?
在分析时,我注意到我的cpu时间有相当大的一部分花在了无序Map的查找上,这并不是最优的:(

qnzebej0

qnzebej01#

因此,当不想使用boost时,Michael Blurr的评论导致了下面的散列函数实现:

std::size_t operator()(std::vector<uint32_t> const& vec) const {
  std::size_t seed = vec.size();
  for(auto& i : vec) {
    seed ^= i + 0x9e3779b9 + (seed << 6) + (seed >> 2);
  }
  return seed;
}

好像有用。
编辑:see's answer稍微慢一点,但确实产生了更好的散列分布。

q3aa0525

q3aa05252#

HolKann当前最高投票答案中的散列函数导致大量向量的高冲突率,这些向量都包含来自小连续分布的元素。
为了解决这个问题,每个元素的位被均匀地分布(算法取自Thomas Mueller's answer)。

std::size_t operator()(std::vector<uint32_t> const& vec) const {
  std::size_t seed = vec.size();
  for(auto x : vec) {
    x = ((x >> 16) ^ x) * 0x45d9f3b;
    x = ((x >> 16) ^ x) * 0x45d9f3b;
    x = (x >> 16) ^ x;
    seed ^= x + 0x9e3779b9 + (seed << 6) + (seed >> 2);
  }
  return seed;
}
6xfqseft

6xfqseft3#

boost::hash_combine足够好,但不是特别好
HolKann的答案已经足够好了,但我建议对每个条目使用一个好的哈希值,然后将它们组合起来,问题是std::hash不是一个好的哈希值,boost::hash_combine也不够强大,无法弥补这一点。

template<typename T>
T xorshift(const T& n,int i){
  return n^(n>>i);
}

uint32_t hash(const uint32_t& v) {
  uint32_t p = 0x55555555ul; // pattern of alternating 0 and 1
  uint32_t c = 3423571495ul; // random uneven integer constant; 
  return c*xorshift(p*xorshift(n,16),16);
}

// if c++20 rotl is not available:
template <typename T,typename S>
typename std::enable_if<std::is_unsigned<T>::value,T>::type
constexpr rotl(const T n, const S i){
  const T m = (std::numeric_limits<T>::digits-1);
  const T c = i&m;
  return (n<<c)|(n>>((T(0)-c)&m)); // this is usually recognized by the compiler to mean rotation, also c++20 now gives us rotl directly
}

class uint32_vector_hasher {
public:
  std::size_t operator()(std::vector<uint32_t> const& vec) const {
    std::size_t ret = 0;
    for(auto& i : vec) {
      ret = rotl(ret,11)^hash(i);
    }
    return ret;
  }
};
kqlmhetl

kqlmhetl4#

我尝试用see's answer来解决leet代码问题。但是对于某些输入,函数会溢出int。所以,我又回到了你的方法。但是,如果你有如下元素,你的函数会引起很多冲突:{0}, {0, 0}, {0, 0, 0}等,因为int的hash是数字本身,所有这些hash都是0。
我稍微调整了一下,加入了减少碰撞率的索引:

struct hash {
    std::size_t operator()(std::vector<int> const& vec) const {
        std::hash<uint32_t> h;
        std::size_t ret = vec.size();
        for(auto& i : vec) {
            ret ^= h(i) | i;
        }
        return ret;
    }
};

我只是用索引对哈希进行排序,这样{0}, {0, 0}, {0, 0, 0}就会产生不同的哈希。这是一个非常糟糕的哈希函数,但它对我的目的有效:P

相关问题