【数据结构中哈希函数与哈希表】

文章目录


前言

哈希表(Hash Table)就是一种常见的、高效的数据结构,它利用哈希函数将数据映射到固定大小的空间,从而实现常数级别的插入、删除和查找操作。


一、哈希函数是什么?

哈希函数(Hash Function)是将任意长度的数据 映射为固定长度的哈希值 的函数。它的目标是尽量均匀地分布键值,避免哈希冲突。

示例(字符串转哈希):

cpp 复制代码
int simpleHash(string key, int tableSize) {
    int hashVal = 0;
    for (char ch : key) {
        hashVal = (hashVal * 31 + ch) % tableSize;
    }
    return hashVal;
}

该函数将字符串转换为整数索引,31 是常见的"乘法因子",用于增强哈希分布。


二、哈希冲突与解决方案

由于哈希函数不可能完全避免冲突,常用的冲突解决方案有以下两种:

1. 拉链法(Separate Chaining)

每个桶(bucket)保存一个链表,将冲突的元素链在一起。

cpp 复制代码
#include <iostream>
#include <list>
#include <vector>
using namespace std;

class HashTable {
private:
    vector<list<int>> table;
    int size;

    int hash(int key) {
        return key % size;
    }

public:
    HashTable(int s) : size(s) {
        table.resize(size);
    }

    void insert(int key) {
        int idx = hash(key);
        table[idx].push_back(key);
    }

    bool search(int key) {
        int idx = hash(key);
        for (int val : table[idx]) {
            if (val == key) return true;
        }
        return false;
    }
};

2. 开放地址法(Open Addressing)

如果出现冲突,就向后找下一个空位。常用方法有线性探测、二次探测和双重哈希。

线性探测示例:

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

class LinearProbingHash {
private:
    vector<int> table;
    int size;
    int EMPTY = -1;

    int hash(int key) {
        return key % size;
    }

public:
    LinearProbingHash(int s) : size(s) {
        table.resize(size, EMPTY);
    }

    void insert(int key) {
        int idx = hash(key);
        while (table[idx] != EMPTY) {
            idx = (idx + 1) % size;
        }
        table[idx] = key;
    }

    bool search(int key) {
        int idx = hash(key);
        int start = idx;
        while (table[idx] != EMPTY) {
            if (table[idx] == key) return true;
            idx = (idx + 1) % size;
            if (idx == start) break; // 表满
        }
        return false;
    }
};

三、哈希表的性能分析

操作 平均时间复杂度 最坏时间复杂度
查找 O(1) O(n)(所有冲突)
插入 O(1) O(n)(所有冲突)
删除 O(1) O(n)(所有冲突)

注意: 哈希表性能取决于负载因子(load factor),负载因子高时容易冲突,需要扩容重哈希


四、C++ STL 中的 unordered_map

标准库中的 unordered_map 就是哈希表的封装:

cpp 复制代码
#include <iostream>
#include <unordered_map>
using namespace std;

int main() {
    unordered_map<string, int> mp;
    mp["apple"] = 3;
    mp["banana"] = 5;

    if (mp.find("apple") != mp.end()) {
        cout << "Found apple, count = " << mp["apple"] << endl;
    }
}

unordered_map 使用哈希表实现,插入、删除、查找操作平均为 O(1)。


五、词频统计

假设有一段文本,要求统计每个单词出现的频率:

cpp 复制代码
#include <iostream>
#include <unordered_map>
#include <sstream>
using namespace std;

int main() {
    string text = "this is a test this is a test again";
    unordered_map<string, int> freq;
    stringstream ss(text);
    string word;

    while (ss >> word) {
        freq[word]++;
    }

    for (auto& [k, v] : freq) {
        cout << k << ": " << v << endl;
    }
    return 0;
}

总结

本文介绍了哈希函数与哈希表的基本概念、冲突解决方案(拉链法和开放地址法)、性能分析,以及实际应用示例。哈希表在工程中非常常用,比如数据库索引、缓存系统、集合判断、唯一值统计等。

相关推荐
程序员二叉9 小时前
【Java】集合面试全套精讲|HashMap/ArrayList高频考点完整版
java·面试·哈希算法
小欣加油11 小时前
leetcode1926 迷宫中离入口最近的出口
数据结构·c++·算法·leetcode·职场和发展
烬羽14 小时前
从零理解树与二叉树:用 JS 带你手撕遍历和递归
javascript·数据结构
YHL14 小时前
🚀从零理解树与二叉树 —— 概念、实现与遍历
前端·javascript·数据结构
JieE21215 小时前
JS 到底有多少种数据类型?从ECMA规范到内存本质,一文彻底搞懂
javascript·数据结构·面试
努力努力再努力wz15 小时前
【内存管理与高并发内存池系列】从 mmap 到 malloc:文件映射、匿名映射与 glibc 内存分配机制详解
linux·c语言·数据结构·数据库·c++·qt·链表
八解毒剂16 小时前
数据结构-平衡二叉树——对二叉搜索树的优化
数据结构·c++·算法
li星野17 小时前
从零构建安全文件上传系统:FastAPI + JWT + 密码哈希 + Streamlit 前端 + SQLite
安全·哈希算法·fastapi
Darling噜啦啦17 小时前
JavaScript 数组深度解析:从纯函数到二维数组陷阱,一文吃透前端数据结构核心
前端·javascript·数据结构
不会就选b19 小时前
数据结构之链表OJ题(中)
数据结构·链表