当前位置: 首页 > news >正文

C++学习——哈希表(一)

文章目录

  • 前言
  • 一、哈希表的模板代码
  • 二、哈希计数器
  • 三、哈希表中的无序映射
  • 四、哈希表的总结


前言

本文为《C++学习》的第11篇文章,今天学习最后一个数据结构哈希表(散列表)。


一、哈希表的模板代码

#include<iostream>
using namespace std;

// 哈希表节点模板类(链地址法实现)
template<typename KeyType, typename ValueType>
class HashNode {
public:
    KeyType key;            // 键(支持泛型)
    ValueType value;        // 值(支持泛型)
    HashNode *next;         // 指向下一个节点的指针
    
    // 构造函数(初始化键值对)
    HashNode(const KeyType &key, const ValueType &value) {
        this->key = key;
        this->value = value;
        this->next = nullptr;
    }
};

// 哈希表模板类
template<typename KeyType, typename ValueType>
class HashTable {
private:
    int size;                            // 哈希表桶数量
    HashNode<KeyType, ValueType> **table;// 桶数组(指针数组)

    // 哈希函数(简单取模法,仅适合整数类型键)
    int hash(const KeyType &key) const {
        int hashkey = key % size;        // 核心哈希计算[3](@ref)
        if(hashkey < 0) hashkey += size; // 处理负数键
        return hashkey;
    }
        
public:
    HashTable(int size = 256);          // 构造函数(默认桶数256)
    ~HashTable();                       // 析构函数(释放内存)
    void insert(const KeyType &key, const ValueType &value); // 插入键值对
    void remove(const KeyType &key);    // 删除键
    bool find(const KeyType &key, ValueType &value) const; // 查找键
};

/* 构造函数:初始化桶数组 */
template<typename KeyType, typename ValueType>
HashTable<KeyType, ValueType>::HashTable(int size) {
    this->size = size;
    this->table = new HashNode<KeyType, ValueType> *[size]; // 动态分配桶数组
    for(int i = 0; i < size; ++i) {
        this->table[i] = nullptr; // 初始化所有桶为空
    }
}

/* 析构函数:释放所有节点内存 */
template<typename KeyType, typename ValueType>
HashTable<KeyType, ValueType>::~HashTable() {
    for(int i = 0; i < size; ++i) {
        HashNode<KeyType, ValueType> *current = table[i];
        while(current) { // 遍历链表释放节点
            HashNode<KeyType, ValueType> *next = current->next;
            delete current; // 释放当前节点
            current = next;
        }
    }
    delete[] table; // 修正:需用delete[]释放数组[1](@ref)
}

/* 插入操作(头插法,时间复杂度O(1)) */
template<typename KeyType, typename ValueType>
void HashTable<KeyType, ValueType>::insert(const KeyType &key, const ValueType &value) {
    int index = hash(key); // 计算桶索引
    HashNode<KeyType, ValueType> *newNode = new HashNode<KeyType, ValueType>(key, value);
    // 头插法插入链表
    newNode->next = table[index]; // 新节点指向原链表头
    table[index] = newNode;        // 更新链表头为新节点[3](@ref)
}

/* 删除操作(需处理头节点和中间节点) */
template<typename KeyType, typename ValueType>
void HashTable<KeyType, ValueType>::remove(const KeyType &key) {
    int index = hash(key);
    if (!table[index]) return; // 空桶直接返回

    // 情况1:删除头节点
    if (table[index]->key == key) {
        HashNode<KeyType, ValueType> *temp = table[index];
        table[index] = table[index]->next; // 头节点指向下一个节点
        delete temp;
        return;
    }

    // 情况2:删除中间节点
    HashNode<KeyType, ValueType> *current = table[index];
    while (current->next && current->next->key != key) {
        current = current->next;
    }
    if (current->next) { // 找到目标节点
        HashNode<KeyType, ValueType> *temp = current->next;
        current->next = temp->next; // 跳过被删除节点
        delete temp;
    }
}

/* 查找操作(遍历链表) */
template<typename KeyType, typename ValueType>
bool HashTable<KeyType, ValueType>::find(const KeyType &key, ValueType &value) const {
    int index = hash(key);
    HashNode<KeyType, ValueType> *current = table[index];
    while (current) {
        if (current->key == key) {
            value = current->value; // 返回找到的值
            return true;            
        }
        current = current->next;
    }
    return false; // 未找到返回false
}

int main() {
    // 测试1:基础功能测试(整数键)
    HashTable<int, string> ht(10); // 桶数设为10便于观察冲突

    // 插入测试
    ht.insert(1, "Apple");
    ht.insert(11, "Banana"); // 与1同桶(哈希冲突)
    ht.insert(21, "Orange"); // 继续冲突
    ht.insert(5, "Grape");

    // 查找测试
    string value;
    cout << "--- 查找测试 ---" << endl;
    cout << "查找1: " << (ht.find(1, value) ? value : "Not Found") << endl; // Apple
    cout << "查找11: " << (ht.find(11, value) ? value : "Not Found") << endl; // Banana
    cout << "查找99: " << (ht.find(99, value) ? value : "Not Found") << endl; // Not Found

    // 删除测试
    cout << "\n--- 删除测试 ---" << endl;
    ht.remove(11);
    cout << "删除后查找11: " << (ht.find(11, value) ? value : "Not Found") << endl; // Not Found
    ht.remove(1);
    cout << "删除头节点后查找1: " << (ht.find(1, value) ? value : "Not Found") << endl; // Not Found
    cout << "删除后查找21: " << (ht.find(21, value) ? value : "Not Found") << endl; // Orange(验证链表连接正确性)

    // 测试2:模板类型测试(字符串键需自定义哈希函数,此处需改进)
    // HashTable<string, int> strHT; // 当前哈希函数仅支持整数,需扩展

    // 测试3:性能测试(插入1000个元素)
    HashTable<int, int> perfHT;
    for (int i = 0; i < 1000; ++i) {
        perfHT.insert(i, i*10);
    }
    int foundCount = 0;
    for (int i = 0; i < 1000; ++i) {
        int val;
        if (perfHT.find(i, val)) foundCount++;
    }
    cout << "\n--- 性能测试 ---\n成功查找次数: " << foundCount << "/1000" << endl;

    return 0; 
}

二、哈希计数器

#include<iostream>
#include<string>

using namespace std;

// 哈希表节点模板类(链地址法实现)
template<typename KeyType, typename ValueType>
class HashNode {
public:
    KeyType key;            // 键(支持泛型)
    ValueType value;        // 值(支持泛型)
    HashNode *next;         // 指向下一个节点的指针
    
    // 构造函数(初始化键值对)
    HashNode(const KeyType &key, const ValueType &value) {
        this->key = key;
        this->value = value;
        this->next = nullptr;
    }
};

// 哈希表模板类
template<typename KeyType, typename ValueType>
class HashTable {
private:
    int size;                            // 哈希表桶数量
    HashNode<KeyType, ValueType> **table;// 桶数组(指针数组)

    // 哈希函数(简单取模法,仅适合整数类型键)
    int hash(const KeyType &key) const {
        int hashkey = key % size;        // 核心哈希计算[3](@ref)
        if(hashkey < 0) hashkey += size; // 处理负数键
        return hashkey;
    }
        
public:
    HashTable(int size = 256);          // 构造函数(默认桶数256)
    ~HashTable();                       // 析构函数(释放内存)
    void insert(const KeyType &key, const ValueType &value); // 插入键值对
    void remove(const KeyType &key);    // 删除键
    bool find(const KeyType &key, ValueType &value) const; // 查找键
};

/* 构造函数:初始化桶数组 */
template<typename KeyType, typename ValueType>
HashTable<KeyType, ValueType>::HashTable(int size) {
    this->size = size;
    this->table = new HashNode<KeyType, ValueType> *[size]; // 动态分配桶数组
    for(int i = 0; i < size; ++i) {
        this->table[i] = nullptr; // 初始化所有桶为空
    }
}

/* 析构函数:释放所有节点内存 */
template<typename KeyType, typename ValueType>
HashTable<KeyType, ValueType>::~HashTable() {
    for(int i = 0; i < size; ++i) {
        HashNode<KeyType, ValueType> *current = table[i];
        while(current) { // 遍历链表释放节点
            HashNode<KeyType, ValueType> *next = current->next;
            delete current; // 释放当前节点
            current = next;
        }
    }
    delete[] table; // 修正:需用delete[]释放数组[1](@ref)
}

/* 插入操作(头插法,时间复杂度O(1)) */
template<typename KeyType, typename ValueType>
void HashTable<KeyType, ValueType>::insert(const KeyType &key, const ValueType &value) {
    int index = hash(key); // 计算桶索引
    HashNode<KeyType, ValueType> *newNode = new HashNode<KeyType, ValueType>(key, value);
    // 头插法插入链表
    newNode->next = table[index]; // 新节点指向原链表头
    table[index] = newNode;        // 更新链表头为新节点[3](@ref)
}

/* 删除操作(需处理头节点和中间节点) */
template<typename KeyType, typename ValueType>
void HashTable<KeyType, ValueType>::remove(const KeyType &key) {
    int index = hash(key);
    if (!table[index]) return; // 空桶直接返回

    // 情况1:删除头节点
    if (table[index]->key == key) {
        HashNode<KeyType, ValueType> *temp = table[index];
        table[index] = table[index]->next; // 头节点指向下一个节点
        delete temp;
        return;
    }

    // 情况2:删除中间节点
    HashNode<KeyType, ValueType> *current = table[index];
    while (current->next && current->next->key != key) {
        current = current->next;
    }
    if (current->next) { // 找到目标节点
        HashNode<KeyType, ValueType> *temp = current->next;
        current->next = temp->next; // 跳过被删除节点
        delete temp;
    }
}

/* 查找操作(遍历链表) */
template<typename KeyType, typename ValueType>
bool HashTable<KeyType, ValueType>::find(const KeyType &key, ValueType &value) const {
    int index = hash(key);
    HashNode<KeyType, ValueType> *current = table[index];
    while (current) {
        if (current->key == key) {
            value = current->value; // 返回找到的值
            return true;            
        }
        current = current->next;
    }
    return false; // 未找到返回false
}

/* 哈希计数器模板类
 * 功能:统计任意类型键的出现次数
 * 实现原理:
 *   1. 内部维护一个哈希表,映射键到计数数组索引
 *   2. 计数数组存储实际计数值
 *   3. 链地址法处理哈希冲突 */
template<typename KeyType>
class HashCounter {
private:
    int *counter;           // 计数数组,存储每个键的计数值
    int counterIndex;        // 当前可分配的计数数组索引
    int counterSize;         // 计数数组容量(最大唯一键数)
    HashTable<KeyType, int> *hash; // 哈希表,用于键到索引的映射
    
public:
    /* 构造函数
     * @param size 最大支持的不同键数量 */
    HashCounter(int size = 256);
    
    /* 析构函数:释放动态内存 */
    ~HashCounter();
    
    /* 重置所有计数和映射关系 */
    void reset();
    
    /* 增加键的计数(若不存在则创建)
     * @return 更新后的计数值 */
    int add(const KeyType &key);
    
    /* 减少键的计数(最小为0)
     * @return 更新后的计数值 */
    int sub(const KeyType &key);
    
    /* 获取键的当前计数 */
    int get(const KeyType &key);
};

// 构造函数:初始化计数数组和哈希表
template<typename KeyType>
HashCounter<KeyType>::HashCounter(int size) {
    counterSize = size;
    counterIndex = 0;
    counter = new int[counterSize](); // 动态分配并初始化为0
    hash = new HashTable<KeyType, int>(size); // 哈希表容量与计数数组一致
}

// 析构函数:释放内存
template<typename KeyType>
HashCounter<KeyType>::~HashCounter() {
    delete[] counter;  // 释放计数数组
    delete hash;        // 正确释放哈希表(非数组)
}

// 重置所有计数状态
template<typename KeyType>
void HashCounter<KeyType>::reset() {
    delete hash; // 释放旧哈希表
    hash = new HashTable<KeyType, int>(counterSize); // 新建空哈希表
    counterIndex = 0;
    fill_n(counter, counterSize, 0); // 计数数组清零
}

// 增加键的计数(自动处理新键)
template<typename KeyType>
int HashCounter<KeyType>::add(const KeyType &key) {
    int idx;
    if (!hash->find(key, idx)) {   // 新键处理
        if (counterIndex >= counterSize) { // 容量溢出保护
            cerr << "Error: Counter overflow! Max size: " << counterSize << endl;
            return -1;
        }
        idx = counterIndex++;
        hash->insert(key, idx);    // 插入新键与索引
    }
    return ++counter[idx];         // 计数+1并返回
}

// 减少键的计数(最小为0)
template<typename KeyType>
int HashCounter<KeyType>::sub(const KeyType &key) {
    int idx;
    if (hash->find(key, idx)) {     // 仅处理已存在键
        if (counter[idx] > 0) {
            return --counter[idx];  // 计数-1
        }
    }
    return 0;
}

// 获取键的当前计数
template<typename KeyType>
int HashCounter<KeyType>::get(const KeyType &key) {
    int idx;
    return (hash->find(key, idx)) ? counter[idx] : 0;
}

int main() {
    // 初始化哈希表(桶数设为7便于观察冲突)
    HashTable<int, string> ht(7);
    string value; 

    cout << "===== 测试1:基础插入与查找 =====" << endl;
    ht.insert(101, "Alice");
    ht.insert(202, "Bob");
    cout << "Key 101 -> " << (ht.find(101, value) ? value : "Not Found") << endl; // Alice
    cout << "Key 999 -> " << (ht.find(999, value) ? value : "Not Found") << endl; // Not Found

    cout << "\n===== 测试2:键值更新 =====" << endl;
    ht.insert(202, "Bobby");  // 更新已有键
    ht.find(202, value);
    cout << "Key 202 -> " << value << endl; // Bobby

    cout << "\n===== 测试3:哈希冲突处理 =====" << endl;
    ht.insert(7, "Conflict1");  // 7 % 7 = 0
    ht.insert(14, "Conflict2"); // 14 % 7 = 0
    cout << "Bucket 0 查找测试:" << endl;
    cout << "Key 7 -> " << (ht.find(7, value) ? value : "Not Found") << endl;    // Conflict1
    cout << "Key 14 -> " << (ht.find(14, value) ? value : "Not Found") << endl;  // Conflict2

    cout << "\n===== 测试4:删除操作 =====" << endl;
    ht.remove(202);
    cout << "删除后查找202 -> " << (ht.find(202, value) ? value : "Not Found") << endl; // Not Found

    cout << "\n===== 测试5:负数键处理 =====" << endl;
    ht.insert(-5, "Negative");  // (-5 % 7 + 7) = 2
    cout << "Key -5 -> " << (ht.find(-5, value) ? value : "Not Found") << endl;  // Negative

    return 0;
}

三、哈希表中的无序映射

#include <iostream>
#include <string>
#include <unordered_map>

using namespace std;

int main() {
    // 初始化无序映射容器
    unordered_map<string, int> student_scores;

    /****************** 插入操作 ******************/
    // 方法1: 使用 operator[] 插入新键值对(若键存在则覆盖)
    student_scores["Alice"] = 90;   // 插入 Alice:90
    student_scores["Bob"] = 85;     // 插入 Bob:85
    
    // 方法2: 使用 insert 插入(若键存在则不会覆盖)
    student_scores.insert({"Charlie", 88});  // 插入 Charlie:88
    student_scores.insert(make_pair("David", 92)); // 插入 David:92
    
    // 方法3: 使用 emplace 高效插入(C++11特性)
    student_scores.emplace("Eva", 95);  // 插入 Eva:95

    /****************** 查询操作 ******************/
    // 方法1: 使用 operator[] 查询(若键不存在会创建默认值)
    cout << "Alice's score: " << student_scores["Alice"] << endl; // 输出90
    
    // 方法2: 使用 find 安全查询(推荐)
    auto it = student_scores.find("Bob");
    if (it != student_scores.end()) {
        cout << "Bob's score: " << it->second << endl;  // 输出85
    } else {
        cout << "Bob not found" << endl;
    }

    // 方法3: 使用 count 检查键是否存在
    if (student_scores.count("Charlie")) {
        cout << "Charlie exists" << endl;  // 输出存在
    }

    /****************** 修改操作 ******************/
    // 方法1: 通过 operator[] 直接修改
    student_scores["Alice"] = 95;  // Alice成绩修改为95
    
    // 方法2: 通过迭代器修改(需先查找)
    auto update_it = student_scores.find("David");
    if (update_it != student_scores.end()) {
        update_it->second = 100;  // David成绩修改为100
    }

    /****************** 删除操作 ******************/
    // 方法1: 通过键删除
    student_scores.erase("Eva");  // 删除Eva记录
    
    // 方法2: 通过迭代器删除
    auto del_it = student_scores.find("Charlie");
    if (del_it != student_scores.end()) {
        student_scores.erase(del_it);  // 删除Charlie记录
    }

    /****************** 遍历操作 ******************/
    cout << "\nFinal scores:" << endl;
    for (const auto& pair : student_scores) {  // 范围for遍历
        cout << pair.first << ": " << pair.second << endl;
    }

    // 方法2: 使用迭代器遍历
    cout << "\nIterator traversal:" << endl;
    for (auto it = student_scores.begin(); it != student_scores.end(); ++it) {
        cout << it->first << " => " << it->second << endl;
    }

    /****************** 清空容器 ******************/
    student_scores.clear();
    cout << "\nAfter clear, size: " << student_scores.size() << endl;

    return 0;
}

四、哈希表的总结

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


这就是今天的全部内容了,谢谢大家的观看,不要忘了给一个免费的赞哦!

相关文章:

  • ASP.NET CORE MVC EF框架
  • JVM常用概念之本地内存跟踪
  • ⭐LeetCode周赛 3468. 可行数组的数目——暴力与数学⭐
  • 深度学习训练Camp:第R5周:天气预测
  • transformer模型介绍——大语言模型 LLMBook 学习(二)
  • 如何利用 Excel 表格实现精准文件批量重命名教程
  • ngx_openssl_conf_t
  • R 基础运算
  • Python+requests+unittest+excel实现接口自动化测试框架
  • 构建服务器--在线单词查询
  • 大白话请详细分析JavaScrip中的map、filter、reduce方法的功能、返回值和应用场景,并结合代码示例讲解
  • Windows软件插件-视频渲染器
  • 编译skia
  • 服务器磁盘占用率过高解决方案
  • DataWhale-三月学习任务-大语言模型初探(一、二、五章学习)
  • 从头开始开发基于虹软SDK的人脸识别考勤系统(python+RTSP开源)(三)
  • 香港服务器深度测评:AWS vs 阿里云 vs GCP 技术选型指南
  • Spring boot启动原理及相关组件
  • Spring Boot 整合 Redis 步骤详解
  • 【免费】2009-2020年上市公司环境投资明细数据
  • 西安机场回应航站楼“水帘洞”事件:屋面排水系统被冰雹堵塞
  • 中俄弘扬正确二战史观:缅怀历史,重拾初心,阻止悲剧重演
  • 红场阅兵即将开始!中国人民解放军仪仗队亮相
  • 万里云端遇见上博--搭乘“上博号”主题飞机体验记
  • 五四青年节|青春韵脚
  • 巴菲特首次明确批评贸易战,“投资界春晚”有哪些看点?一文速览