散列(hash table)

概念

        理想中散列就是一个包含一些项的具有固定大小的数组。只不过数组中各元素的下标是通过对元素的关键字进行计算得到的(计算下标的函数叫散列函数)。

        与栈,队列,数组等数据结构相比,散列可以以常数平均时间进行插入、删除、查找。因为通过散列函数,可以很简单地计算出要操作的元素的下标,从而可直接进行插入、删除、查找。

        但散列不能很好地支持需要排序信息的操作。如findMin,findMax以及有序地输出散列中的所有元素等。因为各个元素的下标是通过散列函数计算得的,这意味着散列中是无序的。

        hash:可以翻译为散列,也可以直译为哈希。哈希也就是散列。就是把任意长度的输入,通过散列算法,变换成固定长度的输出,该输出就是散列值。

散列函数

        由于需要通过散列函数计算出元素的下标,所以散列函数计算起来必须简单,并且保证任何两个不同的关键字映射到不同的下标,同时应该能均匀地分布各个元素。比如常用的String类中的hashCode()方法,这就是一个散列函数:

public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

        该算法的主要思路是:利用字符串中不同位的字符(可以转为int)乘以31的不同次方,这样不同的string得到的hashcode必然不同。

        该算法涉及到关键字中的所有字符,并且一般也可以分布的很好。注意这个算法允许溢出——有可能使int类型的h超出int的最大值——所以在使用这个hash值时需要进行转换,一般如下:

int hash = String#hashCode();
hash = hash % size;//size指数组的大小
if(hash < 0){
  hash += size;
}



### C++ 中 Hash 散列的实现与用法 #### 1. 哈希表的基本概念 哈希表是一种基于键值对的数据结构,其核心思想是通过哈希函数将键(Key)映射到特定的索引位置。这种设计使得插入、删除和查找操作的时间复杂度接近 O(1)[^1]。 #### 2. 自定义哈希函数 对于基本数据类型(如 `int` 或 `float`),可以直接使用内置的哈希函数。然而,当处理更复杂的类型(如 `std::string` 或自定义对象)时,则需要提供一个合适的哈希函数[^3]。例如: ```cpp struct HashFuncString { size_t operator()(const std::string& key) const { size_t hash = 0; for (char c : key) { hash += static_cast<size_t>(c); hash *= 31; // 使用乘法增加随机性 } return hash; } }; ``` 此代码片段展示了如何为字符串类型创建一个高效的哈希函数。 #### 3. 开放寻址法 vs 链式法 ##### (1)链式法 链式法是最常见的解决哈希冲突的方法之一。它通过维护一个指针数组 `_table` 来存储每个桶中的节点列表。如果发生冲突,则新节点会被追加到对应桶的链表中[^2]。 以下是链式法的一个简单实现示例: ```cpp template<class K, class V> struct HashNode { std::pair<K, V> _kv; HashNode* next; HashNode(K k, V v) : _kv(k, v), next(nullptr) {} }; template<class K, class V> class HashTable { public: using Node = HashNode<K, V>; HashTable(size_t capacity = 10) : _capacity(capacity), _size(0) { _buckets.resize(_capacity, nullptr); } private: std::vector<Node*> _buckets; size_t _capacity; size_t _size; size_t getBucketIndex(const K& key) const { return HashFunction()(key) % _capacity; } struct DefaultHashFunction { size_t operator()(const K& key) const { return std::hash<K>()(key); } }; DefaultHashFunction HashFunction; }; ``` ##### (2)开放寻址法 开放寻址法则是在遇到冲突时,在同一数组内寻找其他可用槽位。这种方法通常会引入额外的探测策略,比如线性探测或二次探测[^5]。 下面是一个采用双重散列技术的例子: ```cpp template<typename KeyType> class OpenAddressingHashTable { public: bool insert(const KeyType& key); private: size_t h1(const KeyType& key) const { return std::hash<KeyType>()(key) % tableSize; } size_t h2(const KeyType& key) const { return prime - (std::hash<KeyType>()(key) % prime); } enum EntryState { EMPTY, OCCUPIED }; struct TableEntry { KeyType key; EntryState state; }; std::vector<TableEntry> table; size_t tableSize; size_t prime; }; ``` 在此例子中,`h1` 和 `h2` 分别代表两个不同的哈希函数,它们共同决定了最终的存储位置[^5]。 #### 4. 查找节点逻辑 无论采取哪种方式构建哈希表,都需要考虑如何高效地定位目标元素。一般流程如下:先验证当前状态是否允许继续执行;接着利用指定的哈希算法得出初始索引;最后沿着路径逐一比较直至匹配成功或者确认不存在该项[^4]。 具体而言,假设我们要在一个已有的哈希表实例里检索某个给定关键字是否存在的话,可以参照这段伪代码描述的过程来进行实际编码工作。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值