数据结构(c++版):深入理解哈希计数器

哈希计数器:智能投票统计系统的奇妙设计在理解了哈希表这个"智能图书馆"之后,让我们来看看一个更加专业的应用——哈希计数器。这是一个专门用于统计频率的高效工具,就像是一个智能的投票统计系统。
哈希计数器的设计理念
想象你正在组织一场大型选举,有成千上万的候选人(键),你需要实时统计每个候选人的得票数。哈希计数器就是为此而生的专业系统:
template<typename Keytype>
class Hashcount {
private:int countsize; // 投票箱总数int countindex; // 当前已分配的投票箱编号int* count; // 每个投票箱的票数记录Hashtable<Keytype, int>* hash; // 候选人到投票箱的映射表
};
核心思想:将哈希表的查找能力与数组的快速访问能力完美结合!
系统初始化:建立投票站
template<typename Keytype>
Hashcount<Keytype>::Hashcount(int size)
{countsize = size; // 设置投票箱总数countindex = 0; // 从0号投票箱开始分配count = new int[countsize]; // 准备所有投票箱hash = NULL; // 初始时没有映射关系reset(); // 初始化整个系统
}
比喻:就像建立选举投票站:
countsize:准备1000个投票箱countindex:从第一个投票箱开始编号count数组:每个投票箱都有一个计数器记录票数hash:候选人名册,记录每个候选人对应哪个投票箱
重置机制:新一届选举开始
template<typename Keytype>
void Hashcount<Keytype>::reset() {if (hash) // 如果已有选举数据,先清理{delete hash;hash = NULL;}hash = new Hashtable<Keytype, int>(countsize); // 新建候选人名册countindex = 0; // 投票箱编号归零for (int i = 0; i < countsize; i++){count[i] = 0; // 所有投票箱清零}
}
工作流程:
- 清理历史:删除上一届选举的所有记录
- 新建名册:准备新的候选人登记表
- 重置计数器:所有投票箱归零,准备重新计票
核心操作:投票统计的智慧
增加票数:为新老候选人投票
template<typename Keytype>
int Hashcount<Keytype>::add(const Keytype& key)
{int idx; // 投票箱编号if (!hash->find(key, idx)) // 如果是新候选人{idx = countindex++; // 分配新的投票箱hash->insert(key, idx); // 登记候选人信息}return ++count[idx]; // 票数增加并返回当前票数
}
生动场景:
-
情况1:老候选人"张三"来投票
- 系统查名册:张三 → 5号投票箱
- 直接到5号箱投一票
- 返回:张三当前得票数
-
情况2:新候选人"李四"首次参选
- 系统查名册:李四未登记
- 分配新的投票箱(比如15号)
- 在名册登记:李四 → 15号投票箱
- 在15号箱投入第一票
- 返回:李四得票数(1票)
查询票数:实时统计查看
template<typename Keytype>
int Hashcount<Keytype>::get(const Keytype& key)
{int idx;if (hash->find(key, idx)) // 找到候选人的投票箱{return count[idx]; // 返回该投票箱的票数}return 0; // 未登记的候选人得票为0
}
比喻:记者查询某个候选人的实时得票
- 先查名册找到候选人对应的投票箱编号
- 直接读取该投票箱的计数器
- 如果候选人未登记,说明他还没有得票
减少票数:纠正错误投票
template<typename Keytype>
int Hashcount<Keytype>::sub(const Keytype& key)
{int idx;if (hash->find(key, idx)) // 确保候选人已登记{return --count[idx]; // 票数减1}return 0; // 未登记的候选人无法减票
}
应用场景:发现无效投票需要扣除
- 只能对已登记的候选人进行操作
- 确保票数不会变成负数(逻辑上合理)
内存管理:选举结束的清理工作
template<typename Keytype>
Hashcount<Keytype>::~Hashcount()
{delete[] count; // 拆除所有投票箱if (hash) // 清理候选人名册{delete hash;hash = NULL;}
}
比喻:选举结束后的场地清理
- 拆除所有投票箱和设备
- 销毁候选人名册记录
- 释放所有占用资源
设计巧妙的双剑合璧
哈希计数器的精妙之处在于结合了两种数据结构的优势:
哈希表的优势:
- 快速查找:O(1)时间找到候选人对应的投票箱
- 动态扩展:自动处理新候选人的登记
数组的优势:
- 极致速度:直接索引访问,计数操作极快
- 内存连续:缓存友好,访问效率高
协同工作流程:
候选人"张三"投票 → 哈希表查找(张三→5号箱) → 数组count[5]++ → 返回结果
这种设计避免了在哈希表节点中直接存储计数,而是通过映射关系让数组来处理计数,达到了分工合作、各司其职的效果。
实际应用演示
Hashcount<long long> hc(1000); // 建立可容纳1000个候选人的投票系统hc.add(6); // 候选人6获得第一票
hc.add(6); // 候选人6第二票
hc.add(6); // 候选人6第三票
hc.add(5); // 新候选人5获得第一票
hc.sub(6); // 候选人6扣除一票(发现无效票)cout << hc.get(6) << endl; // 查询候选人6的最终票数:2票
cout << hc.get(5) << endl; // 查询候选人5的票数:1票
与现实世界的对比
| 哈希计数器组件 | 选举系统对应物 | 功能描述 |
|---|---|---|
hash映射表 | 候选人名册 | 记录候选人到投票箱的对应关系 |
count数组 | 投票箱集合 | 实际存储每个候选人的得票数 |
add()操作 | 投票过程 | 为候选人增加票数 |
get()操作 | 票数查询 | 查看候选人当前得票 |
性能优势分析
-
时间复杂度:
- 插入/查询/删除:平均O(1)时间复杂度
- 远优于遍历查找的O(n)复杂度
-
空间效率:
- 只为实际出现的键分配空间
- 数组访问的内存局部性优势
-
扩展性:
- 轻松支持大量不同的键
- 动态处理新键的加入
总结
哈希计数器就像是一个智能的选举统计系统,它巧妙地将哈希表的快速查找能力与数组的极致访问速度相结合。这种设计模式在很多实际场景中都有应用:
- 词频统计:统计文档中每个单词出现的次数
- 用户行为分析:统计每个功能的点击次数
- 实时投票系统:大型活动的实时票数统计
- 数据去重:结合计数实现高效去重
通过这个生动的选举比喻,相信你已经深刻理解了哈希计数器的工作原理和设计智慧。下次需要统计频率时,你会想起这个高效的"智能投票系统"!
源码及运行:
#include<iostream>
#include<string>
#include<unordered_map>
using namespace std;
template<typename Keytype,typename Valutype>
class HashNode
{
public:Keytype key;Valutype valu;HashNode* next;HashNode(const Keytype& key, const Valutype& valu)//传的参数是键和值并且要加引用不能修改传入进来的参数{this->key = key;this->valu = valu;this->next = NULL;}
};
template<typename Keytype, typename Valutype>
class Hashtable
{
private:int size;HashNode<Keytype,Valutype>** table;//跟邻接表有点相似int Hash(const Keytype& key)const{int hashkey = key % size;if (hashkey < 0){hashkey += size;}return hashkey;}
public:Hashtable(int size = 256);~Hashtable();void insert(const Keytype& key, const Valutype& valu);//不希望传进来的键和值被修改void Dlete(const Keytype& key);//键和值是一个整体删除键值就自然删除了bool find(const Keytype& key,Valutype& valu)const;};template<typename Keytype, typename Valutype>
Hashtable<Keytype, Valutype>::Hashtable(int size)
{this->size = size;//改变成员变量给成员size初始化this->table = new HashNode<Keytype, Valutype>* [size];//申请一个大小为size的表for (int i = 0; i < size; i++)//把表头置空,不然就变成野指针了{table[i] = NULL;}}
template<typename Keytype, typename Valutype>
Hashtable<Keytype, Valutype>::~Hashtable()
{for (int i = 0; i < size; i++){if (table[i])//遍历所有表头{HashNode<Keytype, Valutype>* curr = table[i];//申请临时变量来删除表里的同一个键的不同元素while (curr)//{HashNode<Keytype, Valutype>* next = curr->next;delete curr;curr = next;}}table[i] = NULL;//置空防止变成野指针}delete []table;//删除所有表头table = NULL;//给申请数组置空防止变成指针
}
template<typename Keytype, typename Valutype>
void Hashtable<Keytype, Valutype>::insert(const Keytype& key, const Valutype& valu)//插入操作
{int index = Hash(key);//把下标变成键值HashNode<Keytype, Valutype>* now = new HashNode<Keytype, Valutype>(key, valu);//创建新的哈希表节点if (table[index] == NULL)//表头为空则直接把要插入的键置为表头{table[index] = now;}else {//头插法插入表中now->next = table[index];table[index] = now;}}
template<typename Keytype, typename Valutype>
void Hashtable<Keytype, Valutype>::Dlete(const Keytype& key)//键和值是一个整体删除键值就自然删除了
{int index = Hash(key);if (table[key])//哈希表头是否为空,为空就不用删除了{if (table[index] == key)//判断是否是表头{//是表头则将表头的下一个节点保存再next中,删除原来的表头,再把next置为表头HashNode<Keytype, Valutype>* next = table[index]->next;delete table[index];table[index] = next;}else {HashNode<Keytype, Valutype>* curr = table[key];//创建临时变量while (curr->next&&curr->next->key!=key)//遍历表头里面的元素找到要删除元素的前面一个{curr = curr->next;}if (curr->next)//创建临时遍历直接指向要删除的后面的节点,释放掉要删除节点的内存,把被删除的节点置为下一个节点。{HashNode<Keytype, Valutype>* next = curr->next->next;delete curr->next;curr->next = next;}}}
}
template<typename Keytype, typename Valutype>
bool Hashtable<Keytype, Valutype>::find(const Keytype& key,Valutype&valu)const//我传进来的valu会在找到后被修改所以valu前面不用const
{int index = Hash(key);//查找与删除部分逻辑相同,如果是表头就直接将值赋给传进来的键所对应的值然后返回。if (table[index]){if (table[index]->key == key){valu = table[index]->valu;return true;}else {HashNode<Keytype, Valutype>* curr = table[index];while (curr->next && curr->next->key != key){curr = curr->next;}if (curr->next){valu = curr->next->valu;return true;}}}return false;
}
template<typename Keytype>
class Hashcount
{
private:int* count;int countsize;int countindex;Hashtable<Keytype, int>* hash;//int是数组下标
public:Hashcount(int size = 256);~Hashcount();void reset();//重置数据int add(const Keytype& key);//自增操作int get(const Keytype& key);//获取元素int sub(const Keytype& key);//自减操作};template<typename Keytype>//哈希计数器
Hashcount<Keytype>::Hashcount(int size)
{countsize = size;countindex = 0;count = new int[countsize];hash = NULL;reset();}
template<typename Keytype>
Hashcount<Keytype>::~Hashcount()
{delete[]count;if (hash){delete hash;hash = NULL;}
}
template<typename Keytype>
void Hashcount<Keytype>::reset() {if (hash)//存在就先删除,然后再进行重置{delete hash;hash = NULL;}hash = new Hashtable<Keytype, int>(countsize);//申请一个大小为size的哈希表countindex = 0;for (int i = 0; i < countsize; i++){count[i] = 0;//把表置零}}//重置数据
template<typename Keytype>
int Hashcount<Keytype>::add(const Keytype& key)
{int idx;if (!hash->find(key, idx))//找不到就把当前的计数的下标给idx,再把key插入到里面,追后返回count数组自增后的值;{idx = countindex++;hash->insert(key, idx);}
return ++count[idx];}//自增操作
template<typename Keytype>
int Hashcount<Keytype>::get(const Keytype& key)//与删除出入不大,判断存在即可
{int idx;if (hash->find(key, idx)){return count[idx];}return 0;
}//获取元素
template<typename Keytype>
int Hashcount<Keytype>::sub(const Keytype& key)
{int idx;if (hash->find(key, idx)){return --count[idx];//返回删除后的值}return 0;}//自减操作
int main()
{Hashtable<int, char>h(1000);h.insert(1, 'a');h.insert(2, 'b');h.insert(3, 'c');h.insert(545674, 'd');h.insert(1001, 'g');char val;if (!h.find(51, val)){cout << "51 not found" << endl;}if (h.find(545674, val)){cout << "545674 is found is :" << val << endl;}if (h.find(1001, val)){cout << "1001 is found is :" << val << endl;}Hashcount<long long>hc(1000);hc.add(6);hc.add(6);hc.add(6);hc.add(6);hc.add(5);hc.add(6);hc.add(6);hc.sub(6);cout << hc.get(6) << endl;cout << hc.get(5) << endl;return 0;
}


