当前位置:首页 > 技术 > 正文内容

哈希表的C++模拟实现

访客 技术 2026年8月17日 1

一、闭散列实现哈希表

1.1 数据结构定义

在闭散列中,数据至少包含键值对和状态表示。键值对可以是 KK / V,这里我们使用 K / V

状态分为三种:

  • EMPTY
  • 存在 EXIST
  • 删除 DELETE

这些状态用于提高探测效率。存储数据结构可以用一个结构体定义:

// 节点状态
enum State
{
    EMPTY,
    EXIST,
    DELETE
};

// 存储数据结构类
template<class K, class V>
struct HashData
{
    std::pair<K, V> _kv; // 键值对
    State _state = EMPTY; // 状态
};

哈希表的整体框架如下:

namespace Aron
{
    template<class K, class V>
    class HashTable
    {
    public:
        // ...
    private:
        std::vector<HashData<K, V>> _tables; // 数据表
        size_t _n; // 有效数据量(用于计算负载因子)
    };
}

1.2 查找

查找过程通过线性探测来实现。如果当前位置为空,则停止探测,因为后面必然不存在目标数据。

HashData<K, V>* Find(const K& key)
{
    if (_n == 0)
        return nullptr;

    size_t hashi = key % _tables.size();
    size_t index = hashi;

    while (_tables[index]._state != EMPTY)
    {
        if (key == _tables[index]._kv.first && _tables[index]._state == EXIST)
        {
            return &_tables[index];
        }

        ++index;
        index %= _tables.size();

        if (index == hashi)
            break;
    }

    return nullptr;
}

1.3 插入

插入前先判断该值是否已存在。如果不存在,则进行插入。插入步骤如下:

  1. 计算哈希值。
  2. 线性探测找到可用位置。
  3. 插入数据并更新有效数据量。
bool Insert(const std::pair<K, V>& kv)
{
    if (Find(kv.first))
        return false;

    size_t hashi = kv.first % _tables.size();
    int i = 0;

    while (_tables[hashi]._state == EXIST)
    {
        hashi += i;
        hashi %= _tables.size();
        i++;
    }

    _tables[hashi]._kv = kv;
    _tables[hashi]._state = EXIST;
    ++_n;

    return true;
}

1.4 删除

删除操作将节点状态改为 DELETE 并减少有效数据量。

bool Erase(const K& key)
{
    HashData<K, V>* ret = Find(key);
    if (ret)
    {
        _n--;
        ret->_state = DELETE;
        return true;
    }
    else
        return false;
}

1.5 测试

测试查找和插入功能:

void TestHT1()
{
    int a[] = { 3, 33, 2, 13, 5, 12, 1002 };
    HashTable<int, int> ht;
    for (auto e : a)
    {
        ht.Insert(std::make_pair(e, e));
    }

    ht.Insert(std::make_pair(15, 15));

    if (ht.Find(13))
    {
        std::cout << "13在" << std::endl;
    }
    else
    {
        std::cout << "13不在" << std::endl;
    }
}

二、开散列实现哈希表

2.1 数据结构定义

开散列使用单链表解决哈希冲突。存储节点结构如下:

template<class K, class V>
struct HashNode
{
    HashNode<K, V>* _next; // 指向下一个节点
    std::pair<K, V> _kv;

    HashNode(const std::pair<K, V>& kv)
        : _next(nullptr), _kv(kv) {}
};

哈希桶中的表存储类型为节点指针:

template<class K, class V>
class HashTable
{
    typedef HashNode<K, V> Node;

public:
    // ...

private:
    std::vector<Node*> _tables;
    size_t _n = 0; // 有效数据量
};

2.2 析构函数

析构时释放单链表中的节点:

~HashTable()
{
    for (size_t i = 0; i < _tables.size(); i++)
    {
        Node* cur = _tables[i];
        while (cur)
        {
            Node* next = cur->_next;
            delete cur;
            cur = next;
        }
        _tables[i] = nullptr;
    }
}

2.3 查找

查找过程定位到具体位置后遍历单链表:

Node* Find(const K& key)
{
    if (_tables.size() == 0)
        return nullptr;

    size_t hashi = key % _tables.size();
    Node* cur = _tables[hashi];
    while (cur)
    {
        if (cur->_kv.first == key)
            return cur;

        cur = cur->_next;
    }

    return nullptr;
}

2.4 插入

插入时选择头插法,并在需要时进行扩容:

bool Insert(const std::pair<K, V>& kv)
{
    if (Find(kv.first))
        return false;

    if (_n == _tables.size())
    {
        size_t newSize = _tables.size() == 0 ? 5 : _tables.size() * 2;
        std::vector<Node*> newTables(newSize, nullptr);

        for (size_t i = 0; i < _tables.size(); i++)
        {
            Node* cur = _tables[i];
            while (cur)
            {
                Node* next = cur->_next;
                size_t hashi = cur->_kv.first % newTables.size();
                cur->_next = newTables[hashi];
                newTables[hashi] = cur;

                cur = next;
            }

            _tables[i] = nullptr;
        }

        _tables.swap(newTables);
    }

    size_t hashi = kv.first % _tables.size();
    Node* newnode = new Node(kv);

    newnode->_next = _tables[hashi];
    _tables[hashi] = newnode;

    ++_n;
    return true;
}

2.5 删除

删除操作通过单链表的删除方法实现:

bool Erase(const K& key)
{
    size_t hashi = key % _tables.size();
    Node* prev = nullptr;
    Node* cur = _tables[hashi];

    while (cur)
    {
        if (cur->_kv.first == key)
        {
            if (prev)
                prev->_next = cur->_next;
            else
                _tables[hashi] = cur->_next;

            delete cur;

            --_n;
            return true;
        }

        prev = cur;
        cur = cur->_next;
    }
    return false;
}

2.6 桶的长度

检查哈希桶的最大高度:

size_t MaxBucketSize()
{
    size_t max = 0;
    for (size_t i = 0; i < _tables.size(); i++)
    {
        Node* cur = _tables[i];
        size_t size = 0;
        while (cur)
        {
            ++size;
            cur = cur->_next;
        }

        if (size > max)
            max = size;
    }

    return max;
}

void TestHash3()
{
    srand((size_t)time(nullptr));

    HashTable<int, int> ht;

    int n = 1000000;
    for (int i = 0; i < n; i++)
    {
        int val = rand() + i;
        ht.Insert(std::make_pair(val, val));
    }

    std::cout << ht.MaxBucketSize() << std::endl;
}

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。