HASH 模板和题目总结(包括unordered_map)

目录

HASH表(不是重点)

STL——unordered_map

定义

基本操作

进阶操作

与map优缺点:

map:

unordered_map:

总结:

字符串hash

定义:

具体实现:

字符串任意子串的Hash

二维模板(具体原理BZOJ2351Matrix (矩阵) 二维哈希)

例题


 


 

HASH表(不是重点)

模拟哈希表主要有两种方法:拉链法和开发寻址方法。

模板题: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;
}

STL——unordered_map

定义

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)来使用unordered_map,需要实现:

  1. 哈希函数,需要实现一个class重载operator(),将自定义class变量映射到一个size_t类型的数。一般常用std::hash模板来实现。
  2.  判断两个自定义class类型的变量是否相等的函数,一般在自定义class里重载operator==。

示例代码:

#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

与map优缺点:


map:

      1、优点:

           (1)有序性:map结构的红黑树自身是有序的,但是要中序遍历输出

           (2)时间复杂度低:内部结构时红黑树,红黑树很多操作都是在logn的时间复杂度下实现的,因此效率高。

      2、缺点:

            空间占有率高,因为map内部实现是红黑树,虽然它时间复杂度低,运行效率高,但是因为每一个节点都需要额外保存父  节点、孩子节点和红黑树性质,这样使得每一个节点都占用大量的空间。

      3、应用场景:

            应用于对顺序有要求的问题,用map会更高效。

unordered_map:

     1、优点:内部结构是哈希表,查找为O(1),效率高。

     2、缺点:哈希表的建立耗费时间。

     3、应用场景:  对于频繁查找的问题,用unordered_map更高效。

总结:

      1、内存占用率问题转化为 红黑树 VS 哈希表,还是unordered_map内存占用高。

      2、但是unordered_map执行效率高。

      3、对于unordered_map或unordered_set容器,其遍历顺序与创建该容器时输入的顺序不一定相同,因为遍历是按照哈希表从前往后依次遍历的。
 


字符串hash

定义:

把任意长度的字符串映射为一个非负整数,并且冲突概率几乎为0.

具体实现:

假如一个字符串均为小写字母:abcde

则我们可以做如下操作:

  1. a=1,b=2,c=3,........
  2. abcde=(5*p^0+4*p^1+3*p^2+2*p^3+1*p^4)%MOD,即把字符串映射为一个P进制数。

P的经验值是131或13331,取这两个值的冲突概率低。我们知道C++溢出的时候会自动取模,int溢出会自动取模2^31,unsigned in溢出会自动取模2^32,long long 自动取模2^63,   unsigned long long自动取模2^64,且这样效率极高。所以我们一般直接定义为unsigned long long便可以不用取模了

字符串任意子串的Hash

转移我们可以利用前缀和的思想可以求出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;
}

二维模板(具体原理BZOJ2351Matrix (矩阵) 二维哈希)

//核心思想:将字符串看成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;
}

例题

 

  1. AcWing 138. 兔子与兔子 字符串Hash
  2. BZOJ2351Matrix (矩阵) 二维哈希

 

你可能感兴趣的:(模板,字符串,字符串——字符串hash,数据结构——hash)