字符串/超大数据的哈希(hash)高效实现

本文介绍了在C++中如何高效地实现哈希操作,特别是针对字符串的哈希。通过使用特定的哈希函数和双向循环链表解决哈希冲突,实现了哈希表的存储和查找功能。详细讲解了哈希值的计算方法,以及如何通过自定义结构存储哈希冲突的节点。此外,还展示了如何封装成模板类以提高代码复用性。
摘要由CSDN通过智能技术生成

C++代码中有unordered_map<>()可实现hash操作,但是在追求效率的代码中,使用会严重影响代码的执行效率。

特别当需要hash字符串的时候,可能有人会这么操作:

  • unordered_map<string, int> hash_map;

上述操作确实没有错误,但是当你在C++中追求高效率的时候,这种写法可能会比较耗时。这里推荐一个利用数组和双向循环链表实现哈希的方法。

通常的hash操作有字符串哈希和较大数据的哈希。

  • 字符串哈希,通常利用移位操作。hval = (hval << 5) + (str[i] - 'a' + 1),同时需要将 hval 进行缩放,这里一般可以对13131(较大的质数,实验发现哈希冲突较小)或者2的次方(CPU计算除法时对于2的次方速度较快)取余。
  • 对较大的数据哈希,一般也是对13131或者2的次方取余。(这其实也就是hash table的范围)

字符串哈希

#define HASHSIZE 10
#define ull unsigned long long

int getHashVal(char* str) {
    int i = 0;
    ull hval = 0;
    while (str[i] != '\0') {
        hval = (hval << 5) + (str[i] - 'a' + 1);
        ++i;
    }
    // 当然这里可以直接返回hval,但是直接的返回值无法通过数组的index进行建立hash table.
    return hval % HASHSIZE; 
}

当然上述方法只适用于str全由小写字母组成的字符串哈希,对于大小写混合后者纯大写字母组成的字符串仅仅需要依据需求修改移位操作就可以。这里仅仅以纯小写字母为例进行算法说明。

(str[i] - 'a' + 1) 可以使得任何一个小写字母均可以由1~26的数字来表示,切记这里需要+1,否则你讲无法区别“a”和“aa”等。

(hval << 5) 左移5位正好可以完全表示1~26,当然也有部分剩余。

(unsigned long long) 最大可以表示64byte的数据,所以这种hash方法一般用来表示字符串长度不超过12的情况。

在这里,我们仅仅是对字符串进行了哈希操作(字符串转整形),但在实际应用中还设计哈希的冲突解决以及检索等,下面我们接着阐述后续内容。

哈希存储结构

char str[] 存储需要哈希的字符串信息。同时需要记录节点的头尾指针。

struct Node {
    char str[11];
    int value;
    Node* next;
    Node* prev;
};

Node hash_table[HASHSIZE];

哈希查找

这里为了解决哈希冲突,我们引入了双向循环链表(即将冲突的节点插入到循环链表中)。需要结合双向链表进行操作(具体在哈希冲突解决节中详细阐述)。

当找到str对应的字符串时返回对应节点的指针,当没有找到(哈希表中不存在)时返回空指针nullptr

Node* findHash(char* str) {
    int hash = getHashVal(str);
    Node* find_node = hash_table[hash].next;
    while (find_node != &hash_table[hash]) {
        if (strcmp(find_node->str, str) == 0) {
            break;
        }
        find_node = find_node->next;
    }
    if (find_node == &hash_table[hash]) {
        return nullptr;
    }
    return find_node;
}

哈希冲突解决(哈希插入更新)

主要通过函数insertHash来实现插入更新,需要基于双向循环链表。

没有时需要插入新Node,当已有时进行响应的更新操作即可(当然这里需要根据自己的具体需求进行修改完善)。

void _add(Node* now, Node* prev, Node* next) {
    now->prev = prev;
    now->next = next;
    prev->next = now;
    next->prev = now;
}
void addNode(Node* now, Node* head) {
    _add(now, head, head->next);
}
void insertHash(char* str, int value) {
    int hash = getHashVal(str);
    Node* node = findHash(str);
    if (node == nullptr) {
        Node* new_node = new Node;
        strcpy(new_node->str, str);
        new_node->value = value;
        addNode(new_node, &hash_table[hash]);
    }
    else {
        node->value = value;
    }
}

以上是hash table的具体实现方法,当然在C++中可以将其封装为类,以便使用中更加方便。这里提供一种封装策略,当然这个的泛化性还需更一步改进。

Hash table 整体实现

#define ull unsigned long long
struct Node {
    char str[11];
    int value;
    Node* next;
    Node* prev;
};

template<int HASHSIZE>
class HashTable {
public:
    void initHash() {
        for (int i = 0; i < HASHSIZE; ++i) {
            hash_table[i].next = hash_table[i].prev = &hash_table[i];
        }
    }

    Node* findHash(char* str) {
        int hash = getHashVal(str);
        Node* find_node = hash_table[hash].next;
        while (find_node != &hash_table[hash]) {
            if (strcmp(find_node->str, str) == 0) {
                break;
            }
            find_node = find_node->next;
        }
        if (find_node == &hash_table[hash]) {
            return nullptr;
        }
        return find_node;
    }
    
    void insertHash(char* str, int value) {
        int hash = getHashVal(str);
        Node* node = findHash(str);
        if (node == nullptr) {
            Node* new_node = new Node;
            strcpy(new_node->str, str);
            new_node->value = value;
            addNode(new_node, &hash_table[hash]);
        }
        else {
            node->value = value;
        }
    }
private:
    int getHashVal(char* str) {
        int i = 0;
        ull hval = 0;
        while (str[i] != '\0') {
            hval = (hval << 5) + (str[i] - 'a' + 1);
            ++i;
        }
        return hval % HASHSIZE;
    }

    inline void _add(Node* now, Node* prev, Node* next) {
        now->prev = prev;
        now->next = next;
        prev->next = now;
        next->prev = now;
    }
    void addNode(Node* now, Node* head) {
        _add(now, head, head->next);
    }

    Node hash_table[HASHSIZE];
};

相关资料:

图文并茂详解数据结构之哈希表 - 知乎 (zhihu.com)

来吧!一文彻底搞定哈希表! - 知乎 (zhihu.com)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值