哈希表
哈希表作用是什么?是将一个数映射为另一个数,如果说离散化是将“有序”的数进行映射,那么哈希表就是更加广义上的映射。
比如,现在有一个区间-1e9~1e9。但是真正用到的数只有1e5个。所以我们要进行映射。
其中一个方法就是取模。比如x mod 1e5。但是这样会有一个问题,就是可能会有重复的映射值。比如,1~10 mod 5。那么5 mod 5 =0。10 mod 5 = 0。这就有两个重复的数字了。那么如何解决?
方法一:“拉链法”。
例题一:
维护一个集合,支持如下几种操作:
I x
,插入一个整数 x;Q x
,询问整数 x 是否在集合中出现过;
现在要进行 N 次操作,对于每个询问操作输出对应的结果。
输入格式
第一行包含整数 N,表示操作数量。
接下来 N 行,每行包含一个操作指令,操作指令为 I x
,Q x
中的一种。
输出格式
对于每个询问指令 Q x
,输出一个询问结果,如果 xx 在集合中出现过,则输出 Yes
,否则输出 No
。
每个结果占一行。
数据范围
1≤N≤1e5
−1e9≤x≤1e9
输入样例:
5
I 1
I 2
I 3
Q 2
Q 5
输出样例:
Yes
No
如果有负数的话,哈希函数应该这样取值:
int map(int x){
return (x%N +N) %N;
}
#include<iostream>
#include<cstring>
using namespace std;
const int N = 100003;
int myhash[N],e[N],ne[N],idx;
void insert(int x){
int k = (x % N + N) % N;
e[idx] = x;//原始值
ne[idx] = myhash[k];
myhash[k] = idx;
idx++;
}
int find(int x){
int k = (x % N + N )% N;
for(int i = myhash[k];i != -1;i = ne[i]){
if(e[i] == x)
return 1;
}
return -1;
}
int main(){
int n;
cin>>n;
memset(myhash,-1,sizeof(myhash));
while(n--){
char op[2];
int v;
scanf("%s%d",op,&v);
if(op[0]=='I'){
insert(v);
}else{
int tag = find(v);
if(tag != -1) puts("Yes");
else puts("No");
}
}
}
具体讲解一下这个过程。(这里假设没有负数,N=10。且哈希函数是x%N)
初始化myhash数组:
抽象来说是这样的:
即myhash的值其实是“节点的身份证”,是“指针”。
插入20:
20 % 10 = 0;
下面就是插入的逻辑
int k = (x % N + N) % N;
e[idx] = x;//原始值
ne[idx] = myhash[k];
myhash[k] = idx;
idx++;可以看出:进行映射后,将映射后的值作为下标(或者说是指针,其真实值存储在e[idx]中)。
查找的逻辑:
int find(int x){
int k = (x % N + N )% N;
for(int i = myhash[k];i != -1;i = ne[i]){
if(e[i] == x)
return 1;
}
return -1;
}首先要找到要查找的数,经过映射后是多少。然后根据这个映射后的数(是数组的索引),查找这个映射后的数都对应着哪些数。
遍历逻辑:找到要查找的数的哈希值k,然后从hash[k]开始找,注意hash[k]存的是映射为这个数的指针(一个连着一个),因为初始时是空的(hash[k]==-1)。所以只要不等于-1就可以继续遍历。
删除逻辑:
其实只要查找x的下标i,然后将e[i] = -2;做一个标记就可以删除了。
总体逻辑:
首先将原数值ori映射为一个较小的数x(采用取模的方式),然后将这个x作为数组myhash的下标,将myhash[x]中指向值为ori的节点。说白了就是myhash中存的是指针。
接下来载看一种算法来模拟哈希表。
插入20:
插入30:
因为20和30都映射到0号位置,但是先插入的20,所以30要从20所在位置依次向后面找一个非空的位置。
#include<iostream>
#include<cstring>
using namespace std;
const int N = 100003;
int myhash[2*N],inf = 0x3f3f3f3f;
int find(int x){//如果存在,则返回所在的位置;如果不存在则返回应在存在的位置。
int k = (x % N + N )% N;
while(myhash[k] != x && myhash[k] != inf){//当前位置不等于x,并且当前位置不等于空
k++;
if(k==N) k = 0;
}
return k;
}
int main(){
int n;
cin>>n;
memset(myhash,inf,sizeof(myhash));
while(n--){
char op[2];
int v;
scanf("%s%d",op,&v);
if(op[0]=='I'){
int k = find(v);
myhash[k] = v;
}else{
int k = find(v);
if(myhash[k] != inf) puts("Yes");
else puts("No");
}
}
}
注意find函数返回一个位置,如何存在这个数就返回其所在位置,否则返回其应该在的位置。
int find(int x){//如果存在,则返回所在的位置;如果不存在则返回应在存在的位置。
int k = (x % N + N )% N;
while(myhash[k] != x && myhash[k] != inf){//当前位置不等于x,并且当前位置不等于空
k++;
if(k==N) k = 0;
}
return k;
}
STL基础
1. vector
用法小概括:
- 创建:
vector<type> vec;
或vector<type> vec(size);
- 添加元素:
vec.push_back(value);
- 访问元素:
vec[index]
或vec.at(index)
- 删除元素:
vec.pop_back();
或vec.erase(iterator);
- 插入元素:
vec.insert(position, value);
- 大小:
vec.size()
返回size_t
- 清空:
vec.clear()
代码实战:
#include <iostream>
#include <vector>
using namespace std;
int main() {
// 创建一个 vector
vector<int> vec;
// 添加元素
vec.push_back(1); // vec = [1]
vec.push_back(2); // vec = [1, 2]
// 访问元素
int first = vec[0]; // first = 1
int second = vec.at(1); // second = 2
// 插入元素
vec.insert(vec.begin() + 1, 3); // vec = [1, 3, 2]
// 删除元素
vec.pop_back(); // vec = [1, 3]
// 清空
vec.clear(); // vec = []
// 输出
cout << "Size: " << vec.size() << endl; // Size: 0
return 0;
}
运行结果:
Size: 0
2. stack
用法小概括:
- 创建:
stack<type> stk;
- 添加元素:
stk.push(value);
- 访问元素:
stk.top()
返回type&
- 删除元素:
stk.pop();
- 大小:
stk.size()
返回size_t
- 是否为空:
stk.empty()
返回bool
代码实战:
#include <iostream>
#include <stack>
using namespace std;
int main() {
// 创建一个 stack
stack<int> stk;
// 添加元素
stk.push(1); // stk = [1]
stk.push(2); // stk = [1, 2]
// 访问元素
int top = stk.top(); // top = 2
// 删除元素
stk.pop(); // stk = [1]
// 输出
cout << "Top: " << top << endl; // Top: 2
cout << "Size: " << stk.size() << endl; // Size: 1
cout << "Empty: " << stk.empty() << endl; // Empty: 0 (false)
return 0;
}
运行结果:
Top: 2
Size: 1
Empty: 0
3. queue
用法小概括:
- 创建:
queue<type> q;
- 添加元素:
q.push(value);
- 访问元素:
q.front()
返回type&
- 删除元素:
q.pop();
- 大小:
q.size()
返回size_t
- 是否为空:
q.empty()
返回bool
代码实战:
#include <iostream>
#include <queue>
using namespace std;
int main() {
// 创建一个 queue
queue<int> q;
// 添加元素
q.push(1); // q = [1]
q.push(2); // q = [1, 2]
// 访问元素
int front = q.front(); // front = 1
// 删除元素
q.pop(); // q = [2]
// 输出
cout << "Front: " << front << endl; // Front: 1
cout << "Size: " << q.size() << endl; // Size: 1
cout << "Empty: " << q.empty() << endl; // Empty: 0 (false)
return 0;
}
运行结果:
Front: 1
Size: 1
Empty: 0
4. priority_queue
用法小概括:
- 创建:
priority_queue<type> pq;
或priority_queue<type, vector<type>, greater<type>> pq;
(小根堆) - 添加元素:
pq.push(value);
- 访问元素:
pq.top()
返回type&
- 删除元素:
pq.pop();
- 大小:
pq.size()
返回size_t
- 是否为空:
pq.empty()
返回bool
代码实战(大根堆和小根堆):
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
int main() {
// 大根堆
priority_queue<int> maxHeap;
maxHeap.push(1);
maxHeap.push(3);
maxHeap.push(2);
// 小根堆
priority_queue<int, vector<int>, greater<int>> minHeap;
minHeap.push(1);
minHeap.push(3);
minHeap.push(2);
// 访问大根堆元素
int maxTop = maxHeap.top(); // maxTop = 3
maxHeap.pop(); // 之后 top = 2
// 访问小根堆元素
int minTop = minHeap.top(); // minTop = 1
minHeap.pop(); // 之后 top = 2
// 输出
cout << "Max Heap Top: " << maxTop << endl; // Max Heap Top: 3
cout << "Min Heap Top: " << minTop << endl; // Min Heap Top: 1
return 0;
}
运行结果:
Max Heap Top: 3
Min Heap Top: 1
5. map
用法小概括:
- 创建:
map<key_type, value_type> m;
- 添加元素:
m[key] = value;
- 访问元素:
m[key]
或m.at(key)
- 删除元素:
m.erase(key);
- 大小:
m.size()
返回size_t
- 是否为空:
m.empty()
返回bool
- 查找元素:
m.find(key)
代码实战:
#include <iostream>
#include <map>
using namespace std;
int main() {
// 创建一个 map
map<int, string> m;
// 添加元素
m[1] = "one";
m[2] = "two";
// 访问元素
string one = m[1]; // one = "one"
string two = m.at(2); // two = "two"
// 删除元素
m.erase(1); // 只剩下 {2: "two"}
// 查找元素
auto it = m.find(2); // it != m.end()
bool found = (it != m.end());
// 输出
cout << "One: " << one << endl; // One: one
cout << "Two: " << two << endl; // Two: two
cout << "Found key 2: " << found << endl; // Found key 2: 1 (true)
return 0;
}
运行结果:
One: one
Two: two
Found key 2: 1
6. multimap
用法小概括:
- 创建:
multimap<key_type, value_type> mm;
- 添加元素:
mm.insert({key, value});
- 访问元素:
auto range = mm.equal_range(key);
(返回一个pair<iterator, iterator>
) - 删除元素:
mm.erase(key);
或mm.erase(iterator);
- 大小:
mm.size()
返回size_t
- 是否为空:
mm.empty()
返回bool
代码实战:
#include <iostream>
#include <map>
using namespace std;
int main() {
// 创建一个 multimap
multimap<int, string> mm;
// 添加元素
mm.insert({1, "one"});
mm.insert({1, "uno"});
mm.insert({2, "two"});
// 访问元素
auto range = mm.equal_range(1);
for (auto it = range.first; it != range.second; ++it) {
cout << "Key 1: " << it->second << endl;
}
// 删除元素
mm.erase(1); // 删除所有 key 为 1 的元素
// 输出
cout << "Size: " << mm.size() << endl; // Size: 1 (只剩下 {2: "two"})
return 0;
}
运行结果:
Key 1: one
Key 1: uno
Size: 1
7. string
用法小概括:
- 创建:
string str;
或string str = "text";
- 访问字符:
str[index]
或str.at(index)
- 追加字符串:
str.append("more");
- 连接字符串:
str + other_str
- 查找子串:
str.find("substr")
返回size_t
- 子串:
str.substr(start, length)
代码实战:
#include <iostream>
#include <string>
using namespace std;
int main() {
// 创建一个 string
string str = "Hello";
// 访问字符
char ch = str[0]; // ch = 'H'
char ch_at = str.at(1); // ch_at = 'e'
// 追加字符串
str.append(" World"); // str = "Hello World"
// 连接字符串
string new_str = str + "!!!"; // new_str = "Hello World!!!"
// 查找子串
size_t found = str.find("World"); // found = 6
// 子串
string sub = str.substr(6, 5); // sub = "World"
// 输出
cout << "First char: " << ch << endl; // First char: H
cout << "Char at index 1: " << ch_at << endl; // Char at index 1: e
cout << "New string: " << new_str << endl; // New string: Hello World!!!
cout << "Found 'World' at position: " << found << endl; // Found 'World' at position: 6
cout << "Substring: " << sub << endl; // Substring: World
return 0;
}
运行结果:
First char: H
Char at index 1: e
New string: Hello World!!!
Found 'World' at position: 6
Substring: World
8. set
用法小概括:
- 创建:
set<type> s;
- 添加元素:
s.insert(value);
- 删除元素:
s.erase(value);
- 查找元素:
s.find(value)
返回iterator
- 大小:
s.size()
返回size_t
- 是否为空:
s.empty()
返回bool
- 遍历:
for (const auto& elem : s) { ... }
代码实战:
#include <iostream>
#include <set>
using namespace std;
int main() {
// 创建一个 set
set<int> s;
// 添加元素
s.insert(1);
s.insert(2);
s.insert(3);
// 删除元素
s.erase(2); // s = {1, 3}
// 查找元素
auto it = s.find(3); // it != s.end()
bool found = (it != s.end());
// 输出
cout << "Found 3: " << found << endl; // Found 3: 1 (true)
cout << "Size: " << s.size() << endl; // Size: 2
for (const auto& elem : s) {
cout << elem << " "; // 输出: 1 3
}
cout << endl;
return 0;
}
运行结果:
Found 3: 1
Size: 2
1 3
9. bitset
用法小概括:
- 创建:
bitset<size> bs;
或bitset<size> bs(value);
- 设置位:
bs.set(position);
- 清除位:
bs.reset(position);
- 访问位:
bs[position]
返回bool
- 大小:
bs.size()
返回size_t
- 位数:
bs.count()
返回size_t
代码实战:
#include <iostream>
#include <bitset>
using namespace std;
int main() {
// 创建一个 bitset
bitset<8> bs;
// 设置位
bs.set(1); // bs = 00000010
bs.set(3); // bs = 00001010
// 清除位
bs.reset(1); // bs = 00001000
// 访问位
bool bit1 = bs[1]; // bit1 = 0
bool bit3 = bs[3]; // bit3 = 1
// 位数
size_t count = bs.count(); // count = 1
// 输出
cout << "Bit 1: " << bit1 << endl; // Bit 1: 0
cout << "Bit 3: " << bit3 << endl; // Bit 3: 1
cout << "Count of set bits: " << count << endl; // Count of set bits: 1
cout << "Bitset: " << bs << endl; // Bitset: 00001000
return 0;
}
运行结果:
Bit 1: 0
Bit 3: 1
Count of set bits: 1
Bitset: 00001000
10. 哈希表(unordered_map
)
用法小概括:
- 创建:
unordered_map<key_type, value_type> um;
- 添加元素:
um[key] = value;
- 访问元素:
um[key]
或um.at(key)
- 删除元素:
um.erase(key);
- 大小:
um.size()
返回size_t
- 是否为空:
um.empty()
返回bool
- 查找元素:
um.find(key)
返回iterator
代码实战:
#include <iostream>
#include <unordered_map>
using namespace std;
int main() {
// 创建一个 unordered_map
unordered_map<int, string> um;
// 添加元素
um[1] = "one";
um[2] = "two";
// 访问元素
string one = um[1]; // one = "one"
string two = um.at(2); // two = "two"
// 删除元素
um.erase(1); // 只剩下 {2: "two"}
// 查找元素
auto it = um.find(2); // it != um.end()
bool found = (it != um.end());
// 输出
cout << "One: " << one << endl; // One: one
cout << "Two: " << two << endl; // Two: two
cout << "Found key 2: " << found << endl; // Found key 2: 1 (true)
cout << "Size: " << um.size() << endl; // Size: 1
return 0;
}
运行结果:
One: one
Two: two
Found key 2: 1
Size: 1