CC++ & Algorithm

C++哈希表的应用

较难5
语言版本:C++Python
概述:哈希表在生活中和编程中都有很多用途,比如统计物品数量、记录联系人或快速判断某个东西有没有出现过,非常实用。

哈希表真好用:统计、查重一网打尽

哈希表就像一个超级智能的抽屉柜,每个抽屉都贴着一个标签(键),里面可以放任何东西(值)。在C++里,unordered_map 就是这种魔法抽屉。它最厉害的地方是:你只要告诉它一个键,它就能"嗖"的一下找到对应的值,速度比翻书快多了。哈希表特别适合解决两类问题:一类是"统计"问题,比如统计全班同学最喜欢的水果有哪些;另一类是"查重"问题,比如检查游戏里有没有两次抽到同一个数字。

统计问题:数一数每种东西有多少

生活中,老师带了一盒糖果,想知道每种颜色有多少颗。如果一颗一颗数,需要很多时间。如果用哈希表,只要拿一颗糖果,看它的颜色,然后在对应颜色的抽屉里加一颗糖的计数。所有糖果分完,每种颜色的数量就出来了。

在C++中,我们直接用 unordered_map<键类型, 值类型> 来统计。键可以存名字、颜色、单词,值存次数、数量。看这个例子:统计一个班级里每种水果的个数。

#include <iostream>
#include <unordered_map>
#include <string>
using namespace std;

int main() {
    // 假设水果名单(每种水果名代表一个人)
    string fruits[] = {"苹果", "香蕉", "苹果", "橘子", "香蕉", "苹果", "葡萄"};
    int fruitNum = 7;  // 水果总数
    
    unordered_map<string, int> fruitCount;  // 哈希表:水果名 -> 个数
    
    // 遍历所有水果,逐个统计
    for (int i = 0; i < fruitNum; i++) {
        string f = fruits[i];        // 当前水果
        fruitCount[f]++;             // 对应计数加1,首次统计自动从0变成1
    }
    
    // 输出统计结果
    for (auto& pair : fruitCount) {
        cout << pair.first << " 有 " << pair.second << " 个" << endl;
    }
    return 0;
}

运行结果:

苹果 有 3 个
香蕉 有 2 个
橘子 有 1 个
葡萄 有 1 个

哈希表自动把水果名和数量对应起来,不用我们手动排序或查找。

再比如,你要统计一周七天里,每天你花了多少零花钱(单位:元)。可以这样:

unordered_map<string, int> dailySpend;  // 键:星期几,值:花的钱
dailySpend["星期一"] = 5;
dailySpend["星期二"] = 3;
dailySpend["星期三"] = 0;  // 没花钱也要记一下
// ... 然后每天增加
dailySpend["星期一"] += 10;  // 更新:星期一又花了10元

查重问题:快速判断有没有出现过

另一个重要应用是"判断重复"。比如参加游戏,每个人抽一个数字,要快速判断这个数字之前有没有被抽到过。我们可以建立一个哈希表,每抽到一个数字,检查它是否已经在哈希表里,如果没有就插入,如果有就说明重复了。这样只需要扫描一次,速度很快。

看一个判断句子中是否有重复单词的例子:

#include <iostream>
#include <unordered_map>
#include <string>
#include <sstream>  // 用于分割字符串
using namespace std;

int main() {
    string sentence = "I have a dream that one day this nation will rise up";
    unordered_map<string, bool> seenWord;  // 哈希表:记录单词是否出现过
    
    stringstream ss(sentence);
    string word;
    bool hasDuplicate = false;  // 标记是否有重复
    
    while (ss >> word) {
        if (seenWord[word] == true) {  // 检查是否已存在
            cout << "发现重复单词: " << word << endl;
            hasDuplicate = true;
        } else {
            seenWord[word] = true;  // 标记为已出现
        }
    }
    
    if (!hasDuplicate) {
        cout << "句子中没有重复单词" << endl;
    }
    return 0;
}

(注意:上句里 "a" 和 "dream" 等没有重复,所以输出"没有重复";如果句子改成 "I am I",就会输出"发现重复单词: I")

生活中的更多应用

除了统计和查重,哈希表还有很多用法:

  • 电话号码本:用名字当键,电话号码当值,快速查找朋友的号码。哈希表的查找速度比翻纸质电话本快得多。
  • 缓存数据:把计算过的结果存起来,下次遇到相同输入直接返回,不用再算一遍。比如计算斐波那契数列时,把已经算过的 f(n)f(n) 存起来,避免重复计算。
  • 记录考试成绩:用学生姓名当键,分数当值,可以快速查询某个学生的成绩,也能迅速更新。
  • 商品库存管理:商品编号做键,库存数量做值,进货或卖出时直接修改对应的值。

新手容易犯的错误

  1. 忘记包含头文件:使用 unordered_map 需要有 #include <unordered_map>,使用 string 要有 #include <string>,使用 stringstream 要有 #include <sstream>。少写一个,编译就会报错。

  2. 混淆键的类型:键必须是可哈希的类型,比如 intstringdouble 都可以。但像自定义的 struct 需要自己提供哈希函数(七级暂时用不上)。

  3. 访问不存在的键时自动创建wordCount[word]++ 如果 word 不在哈希表里,会自动创建一个键值对,值初始化为 0,然后加 1。这很方便,但有时会误插入不必要的键。如果想先检查是否存在再操作,可以用 findcount 方法。

  4. 忘记 unordered_map 是无序的:它的遍历顺序不一定和你插入的顺序相同。如果需要按顺序,可以用 map(底层是红黑树,有序但慢一点)。

完整示例:统计一段文字中每个字母出现的次数

这个例子综合了统计和查重思想(虽然字母没有重复问题,但统计很典型)。程序从用户输入一行文字,然后告诉每个字母(大小写不区分)出现了几次。

#include <iostream>
#include <unordered_map>
#include <string>
#include <cctype>   // 用于 tolower 函数
using namespace std;

int main() {
    cout << "请输入一段文字:";
    string text;
    getline(cin, text);  // 读取整行,包括空格
    
    unordered_map<char, int> letterCount;  // 哈希表:字母 -> 出现次数
    
    // 逐个字符处理
    for (int i = 0; i < text.length(); i++) {
        char ch = text[i];
        if (isalpha(ch)) {              // 只统计字母
            char lowerCh = tolower(ch); // 统一转为小写,大小写合并
            letterCount[lowerCh]++;     // 计数加1
        }
    }
    
    // 输出结果
    cout << "各字母出现次数如下:" << endl;
    for (auto& entry : letterCount) {
        cout << entry.first << " : " << entry.second << " 次" << endl;
    }
    return 0;
}

运行示例:

请输入一段文字:Hello, World!
各字母出现次数如下:
h : 1 次
e : 1 次
l : 3 次
o : 2 次
w : 1 次
r : 1 次
d : 1 次

相关指引

掌握了哈希表的基本用法后,你可以继续学习:

  • set:只存储键不存储值的哈希集合,专门用于查重。
  • map:有序哈希表(红黑树),遍历时按键从小到大排列。
  • 哈希冲突与负载因子:高级话题,但理解它能让你更懂哈希表的性能。
  • 用哈希表解决更复杂的题目,比如两数之和、统计最高频元素等。

对于GESP 7级的同学来说,掌握哈希表的基本用法和两种典型应用(统计和查重)就足够了。练习时多写几个小项目,比如"记录好友的生日"、"统计文章中的标点符号数量"等,你就能越来越熟练地使用这个高效的"超级抽屉"工具啦。

例题精讲

1单选题

在C++中,使用std::unordered_map的[]运算符访问一个不存在的键时,会发生什么?

A程序编译错误
B抛出std::out_of_range异常
C自动插入一个默认值(如int为0)并返回其引用
D返回空指针
2单选题

以下哪种方法不是哈希表解决冲突的常用策略?

A链地址法(Separate Chaining)
B开放定址法(如线性探测)
C二次探测(Quadratic Probing)
D二分查找法(Binary Search)
3判断题

在C++中,std::unordered_map 的元素是按插入顺序存储的,因此迭代器遍历顺序与插入顺序一致。

4填空题
以下程序统计字符串中每个字符出现的次数,请补全代码。

#include <iostream>
#include <unordered_map>
#include <string>
using namespace std;

int main() {
    string s = "hello world";
    unordered_map<char, int> freq;
    for (char ch : s) {
        if (ch != ' ') {
            ___;  // 填空位置
        }
    }
    for (auto& p : freq) {
        cout << p.first << ": " << p.second << endl;
    }
    return 0;
}
5填空题
给定一个整数数组 nums 和一个整数目标值 target,请使用哈希表找出数组中和为目标值的两个数的下标。补全以下函数。

#include <vector>
#include <unordered_map>
using namespace std;

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int, int> hash;
        for (int i = 0; i < nums.size(); i++) {
            int complement = target - nums[i];
            if (___) {  // 填空位置
                return {hash[complement], i};
            }
            hash[nums[i]] = i;
        }
        return {};
    }
};