哈希表又称散列表,通过键来访问值的数据结构。哈希表内部用数组存储value, 首先通过映射函数(哈希函数)将key映射成size_t型的下标并对数组长度取余, 然后访问数组。
由于哈希函数原因,不同key会被映射成相同的下标。这是可以通过拉链法,用链表数组存储value。相同映射为相同下表的键值对会被存储在一个链表中,链表每个节点都有key,value字段。
#include <iostream>
#include <string>
#include <stack>
#include <queue>
#include <vector>
#include <unordered_map>
#define NULL 0
using namespace std;
//new包括分配内存和初始化两步。对于new数组时的初始化:
// 对于对象类型只能调用默认构造或参数均有默认值的构造函数来初始化,如果对象没有这种构造报错。
// 对于内建数据类型,不能初始化。
const int Max_Size = 3;
class HashMap
{
public:
HashMap() {
arr = new Node * [Max_Size];
memset(arr, NULL, Max_Size * sizeof(Node*));
}
~HashMap() { delete[] arr; }
//hash函数
int hash(const string& key)
{
int num = 0;
for (char ch : key) num += ch;
return num % Max_Size;
}
//插入
void insert(string key, int val)
{
Node* p = arr[hash(key)];
if (arr[hash(key)] == NULL) {
arr[hash(key)] = new Node(key, val);
return;
}
while (p != NULL)
{
if (p->key == key) {
p->val = val;
return;
}
p = p->next;
}
p = new Node(key, val);
}
//取值
int operator[](string key)
{
Node* p = arr[hash(key)];
while (p)
{
if (p->key == key) return p->val;
p = p->next;
}
return -1;//取不到值返回-1
}
int erase(string key)
{
Node* p = arr[hash(key)], * pre = NULL;
while (p)
{
if (p->key == key) {
if (pre == nullptr) {
delete p;
arr[hash(key)] = NULL;
}
else {
pre->next = p->next;
delete p;
}
break;
}
pre = p;
p = p->next;
}
return -1;
}
private:
struct Node {
string key;
int val;
Node* next;
Node() {};
Node(string k, int v) :key(k), val(v), next(NULL) {};
};
int use;
Node** arr;
};
int main()
{
HashMap m;
m.insert("a", 1);
m.insert("b", 1);
m.insert("c", 1);
m.insert("d", 5);
m.insert("a", 2);
cout << m["a"] << endl;
cout << m["b"] << endl;
cout << m["c"] << endl;
cout << m["d"] << endl;
return 0;
}
一般用unordered_map<Key, Value, hash, equal_fn>表示hahs表
key:自定义类型Key
Value:自定义类型Value
hash:函数对象,对Key类型的hash操作
equal_fn:函数对象,对Key类型的比较操作
由于存在取hash和查看key是否存在于hash的操作。这两个函数对象可以用传入函数指针,lambda表达式,可调用对象。对于比较操作还可以直接对Key类型实现operator=操作符。
struct A {
string name;
int score;
A() {}
A(string s, int i):name(s), score(i){}
};
struct hash_func {
size_t operator()(const A& a) const
{//注意hash<int>是可调用对象。
return hash<string>()(a.name) ^ hash<int>()(a.score);
}
};
//也可以在A中实现==操作符。
struct equal_func {
bool operator()(const A& a, const A& b) const
{
return a.name == b.name && a.score == b.score;
}
};
int main()
{//<>内部只能传类型,所以用hash_func。
unordered_map<A, int, hash_func, equal_func> m;
m[A("mm", 10)] = 0;
m[A("boy", 20)] = 1;
return 0;
}
-
重载运算符函数一定要用const修饰,表示函数不会修改this对象,因为非const函数不能被对象调用。c++这样规定也是为了操作符函数能被const对象使用,如operator(),==,<,>,+,-
可调用对象相比于函数指针优点:可以存储状态。比如函数实现过程中需要外界参数的时候,可调用对象可以动态传入;而函数指针只能定义新的函数,不灵活。
struct Func {
Func() : thres(0) {}
Func(int a) : thres(a) {}
bool operator()(int a)
{
return a < thres;
}
private:
int thres;
};
bool func_0(int a)
{
return a < 0;
}
bool func_1(int a)
{
return a < 1;
}
//注意可调用“对象”,所以一定要生成对象,调用对象的()操作符函数。
//function<> 一切函数对象的接口:函数指针,可调用对象,lambda,bind;
function<bool(int)> f1 = func_0;
function<bool(int)> f2 = Func(); //相等价
function<bool(int)> f3 = func_1;
function<bool(int)> f4 = Func(1);//相等价