目录
HASH表(不是重点)
STL——unordered_map
定义
基本操作
进阶操作
与map优缺点:
map:
unordered_map:
总结:
字符串hash
定义:
具体实现:
字符串任意子串的Hash
二维模板(具体原理BZOJ2351Matrix (矩阵) 二维哈希)
例题
模拟哈希表主要有两种方法:拉链法和开发寻址方法。
模板题:AcWing 840. 模拟散列表
#include
using namespace std;
/*拉链法接近O(N)*/
const int N=100007;
int h[N], e[N], ne[N], idx;
// 向哈希表中插入一个数
void insert(int x)
{
int k = (x % N + N) % N;
e[idx] = x;
ne[idx] = h[k];
h[k] = idx ++ ;
}
// 在哈希表中查询某个数是否存在
bool find(int x)
{
int k = (x % N + N) % N;
for (int i = h[k]; i != -1; i = ne[i])
if (e[i] == x)
return true;
return false;
}
int main()
{
int n;
scanf("%d", &n);
memset(h, -1, sizeof h);
while (n -- )
{
char op[2];
int x;
scanf("%s%d", op, &x);
if (*op == 'I') insert(x);
else
{
if (find(x)) puts("Yes");
else puts("No");
}
}
return 0;
}
#include
using namespace std;
/*拉链法*/
const int N=100007;
int h[N];
// 如果x在哈希表中,返回x的下标;如果x不在哈希表中,返回x应该插入的位置
int find(int x)
{
int t = (x % N + N) % N;
while (h[t] != null && h[t] != x)
{
t ++ ;
if (t == N)
t = 0;
}
return t;
}
int main()
{
int n;
scanf("%d", &n);
memset(h, 0x3f, sizeof h);
while (n -- )
{
char op[2];
int x;
scanf("%s%d", op, &x);
int k = find(x);
if (*op == 'I') h[k] = x;
else
{
if (h[k] != null) puts("Yes");
else puts("No");
}
}
return 0;
}
unordered_map是C++中的哈希表,可以在任意类型与类型之间做映射。
- 引用头文件(C++11):#include
- 定义:unordered_map
、unordered_map ... - 插入:例如将("ABC" -> 5.45) 插入unordered_map
hash中,hash["ABC"]=5.45 - 查询:hash["ABC"]会返回5.45
- 判断key是否存在:hash.count("ABC") != 0 或 hash.find("ABC") != hash.end()
for (auto &item : hash)
{
cout << item.first << ' ' << item.second << endl;
}
或
for (unordered_map::iterator it = hash.begin(); it != hash.end(); it ++ )
{
cout << it->first << ' ' << it->second << endl;
}
如果想让自定义的class作为key(unordered_map
示例代码:
#include
#include
#include
using namespace std;
class Myclass
{
public:
int first;
vector second;
// 重载等号,判断两个Myclass类型的变量是否相等
bool operator== (const Myclass &other) const
{
return first == other.first && second == other.second;
}
};
// 实现Myclass类的hash函数
namespace std
{
template <>
struct hash
{
size_t operator()(const Myclass &k) const
{
int h = k.first;
for (auto x : k.second)
{
h ^= x;
}
return h;
}
};
}
int main()
{
unordered_map S;
Myclass a = { 2, {3, 4} };
Myclass b = { 3, {1, 2, 3, 4} };
S[a] = 2.5;
S[b] = 3.123;
cout << S[a] << ' ' << S[b] << endl;
return 0;
}
输出:
2.5 3.123
1、优点:
(1)有序性:map结构的红黑树自身是有序的,但是要中序遍历输出
(2)时间复杂度低:内部结构时红黑树,红黑树很多操作都是在logn的时间复杂度下实现的,因此效率高。
2、缺点:
空间占有率高,因为map内部实现是红黑树,虽然它时间复杂度低,运行效率高,但是因为每一个节点都需要额外保存父 节点、孩子节点和红黑树性质,这样使得每一个节点都占用大量的空间。
3、应用场景:
应用于对顺序有要求的问题,用map会更高效。
1、优点:内部结构是哈希表,查找为O(1),效率高。
2、缺点:哈希表的建立耗费时间。
3、应用场景: 对于频繁查找的问题,用unordered_map更高效。
1、内存占用率问题转化为 红黑树 VS 哈希表,还是unordered_map内存占用高。
2、但是unordered_map执行效率高。
3、对于unordered_map或unordered_set容器,其遍历顺序与创建该容器时输入的顺序不一定相同,因为遍历是按照哈希表从前往后依次遍历的。
把任意长度的字符串映射为一个非负整数,并且冲突概率几乎为0.
假如一个字符串均为小写字母:abcde
则我们可以做如下操作:
P的经验值是131或13331,取这两个值的冲突概率低。我们知道C++溢出的时候会自动取模,int溢出会自动取模2^31,unsigned in溢出会自动取模2^32,long long 自动取模2^63, unsigned long long自动取模2^64,且这样效率极高。所以我们一般直接定义为unsigned long long便可以不用取模了。
转移我们可以利用前缀和的思想可以求出h[k](h[k]存储字符串前k个字母的哈希值),但这样我们只能求字符串前缀的Hash值,对于任意子串的Hash我们可以做如下的求法:
假如我们要求[l,r]之间的Hash,那么便有如下公式:
Hash(s[l~r])=h[r]-h[l-1]*p^(r-l+1)
例如:abcd 我们要求区间[3,4]之间的即cd的Hash
Hash(cd)=1*p^3+2*p^2+3*p^1+4*p^0-(1*p^1+2*p^0)*p^2
相当于把ab移动到与abcd的位置,才能相减。
注意就实现:前缀和求O(n)预处理,来实现和哈希一般的O(1)常数级别查询
一维Hash模板
//核心思想:将字符串看成P进制数,P的经验值是131或13331,取这两个值的冲突概率低
//小技巧:取模的数用2^64,这样直接用unsigned long long存储,溢出的结果就是取模的结果
//如果超时改为unsigned int
#include
using namespace std;
typedef unsigned long long ULL;
const int N=1e6+5;
ULL hashv[N], power[N]; // hashv[k]存储字符串前k个字母的哈希值, power[k]存储 P^k mod 2^64
char str[N];
int len,P;
// 初始化
void calHash()
{
P=131;
power[0] = 1;
for (int i = 1; i <= len; i ++ )
{
hashv[i] = hashv[i - 1] * P + (str[i]-'a'+1);
power[i] = power[i - 1] * P;
}
}
// 计算子串 str[l ~ r] 的哈希值
ULL get(int l, int r)
{
return hashv[r] - hashv[l - 1] * power[r - l + 1];
}
int main()
{
scanf("%s",str+1);
len=strlen(str+1);
calHash();
int m;
scanf("%d",&m);
while(m--)
{
int l1,r1,l2,r2;
scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
if(get(l1,r1)==get(l2,r2))
{
printf("Yes\n");
}
else
{
printf("No\n");
}
}
return 0;
}
//核心思想:将字符串看成P进制数,P的经验值是131或13331,取这两个值的冲突概率低
//小技巧:取模的数用2^64,这样直接用unsigned long long存储,溢出的结果就是取模的结果
//如果超时改为unsigned int
#include
using namespace std;
typedef unsigned long long ULL;
const int N=1e3+5;
ULL hashv[N][N], power[N*N]; // hashv[k]存储字符串前k个字母的哈希值, power[k]存储 P^k mod 2^64
char str[N][N],str1[N];
int n,m,A,B;
int P;
// 初始化
void calHash()
{
P=131;
for (int i = 1; i <= n; i++ )
{
for(int j=1; j <= m; j++)
{
hashv[i][j] = hashv[i][j-1] * P + (str[i][j]-'0');
}
}
power[0] = 1;
for(int i=1; i<=n*m; i++)
{
power[i] = power[i - 1] * P;
}
}
// 计算子串 str[l ~ r] 的哈希值
ULL get(ULL f[],int l, int r)
{
return f[r] - f[l - 1] * power[r - l + 1];
}
int main()
{
scanf("%d%d%d%d",&n,&m,&A,&B);
for(int i=1; i<=n; i++)
{
scanf("%s",str[i]+1);
}
calHash();
//将大小为A*B的矩阵Hash保存到Hash中
unordered_set Hash;
for(int i=B; i<=m; i++)
{
ULL s=0;
int l=i-B+1,r=i;
for(int j=1; j<=n; j++)
{
s=s*power[B]+get(hashv[j],l,r);
if(j-A>0)
s-=get(hashv[j-A],l,r)*power[A*B];
if(j>=A)
Hash.insert(s);
}
}
int Q;
scanf("%d",&Q);
while(Q--)
{
ULL s=0;
for(int i=1; i<=A; i++)
{
scanf("%s",str1+1);
for(int j=1; j<=B; j++)
{
s=s*P+str1[j]-'0';
}
}
if(Hash.count(s))
printf("1\n");
else
printf("0\n");
}
return 0;
}