【算法基础9】c/c++中如何构建哈希表?字符串哈希其实很简单

一、构建哈希表的基本思路

        哈希表主要是为了将较大范围的数映射到一个较小的范围,与离散化有些类似,但离散化映射后的数仍然保持数与数之间的顺序,而哈希表中数的顺序是打乱的。哈希表的映射方法主要是将大范围的数对N取模,但可能存在多个数取模后的值相同,这样就产生了冲突。为了减少冲突,N通常取质数,根据解决冲突的方式不同,哈希表的构建又分拉链法开放寻址法两种。

      

        查找第一个大于N的质数:

//为了减少冲突,求哈希取模的数最好是质数 
int prime(int n){
	for(int i=n;;i++){
		bool flag=true;
		for(int j=2;j*j<i;j++){
			if(i%j==0){
				flag=false;
				break;
			}
		}
		if(flag) return i;
	}
}

二、拉链法

        主要思想:哈希表的每个槽位充当链表的头结点,将产生冲突的数依次链接上去,使得哈希表呈现“拉链状”,能够实现“一对多”。

        

        例题:给出n行操作,“I x”表示在哈希表中插入数x,“F x”表示在哈希表中查找数x,如果x存在则打印“YES”,否则打印“NO”。

#include<iostream>
#include<cstring>
using namespace std;

const int N=23;
int h[N],e[N],ne[N],idx;


void insert(int x){
	int k=(x%N+N)%N;//负数取模后是负数,加上N后再取一次模保证为正数
	e[idx]=x;//同数组模拟链表,详见【算法基础6】
	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,x;
	char op[2];
	scanf("%d",&n);
	
	memset(h,-1,sizeof h);//将哈希表初始化,注意memset是按字符进行赋值
	
	while(n--){
		scanf("%s %d",op,&x);
		if(*op=='I') insert(x);
		else{
			if(find(x)) printf("YES\n");
			else printf("NO\n");
		}
	}
	return 0;
	
}

三、开放寻址法

        主要思想:当前槽位已经有数存储的时候,查找下一个槽位,直到找到空槽位为止。

        例题:给出n行操作,“I x”表示在哈希表中插入数x,“F x”表示在哈希表中查找数x,如果x存在则打印“YES”,否则打印“NO”。

#include<iostream>
#include<cstring>
using namespace std;

const int N=23,null=0;//null不在输入范围即可 
int h[N];

int find(int x){//开放寻址法的核心操作
	int k=(x%N+N)%N;
	while(h[k]!=null&&h[k]!=x){//当前槽位已经有其他数存在
		k++;
		if(k==N) k=0;//走到尽头,回到k的前面查找
	}
	return k;
}

int main(){
	int n,x;
	char op[2];
	scanf("%d",&n);

	memset(h,0,sizeof(h));
	
	while(n--){
		scanf("%s %d",op,&x);
		int k=find(x);
		if(*op=='I') h[k]=x;
		else{
			if(h[k]!=null) printf("YES\n");
			else printf("NO\n");
		}
	}
	
	return 0;
	
}

四、字符串前缀和哈希 

        主要思想:

        采用P进制的方法把字符串映射成数字,再像普通哈希的一样将映射后的数字对Q取模,构成哈希表。为了减少冲突,通常将P取为131或者13331,将Q取为2的64次方。在实际应用中可用将哈希表的数据类型定义为unsigned long long,溢出的过程相当于取模,能够减少代码。

        同数字前缀和数组类似,字符串前缀和数组h[]可以由公式h[i]=h[i-1]*P+str[i]构建,求一段区间【l,r】上的字符串对应的哈希数可以由公式h[r]-h[l-1]*p[r-l+1]得到,其中p[]是为了化简运算,提前构建的P的次方数组。这样就可以在O(1)时间内判断两个字符串是否相等。

        例题:给出一个长度为n的字符串str,求m个操作,每次判断区间【l1,r1】和【l2,r2】的字符串是否相等,如果相等则输出“YES”,否则输出“NO”。

#include<iostream>
using namespace std;

const int N=10010,P=131;
typedef unsigned long long ULL;//typedef提前定义简化代码
ULL p[N],h[N];//p为P的次方数组,h为前缀和哈希数组

ULL get(int l,int r){
	return h[r]-h[l-1]*p[r-l+1];//返回一个区间段内字符串对应的数
}

int main(){
	int m,n;
	char str[N];
	scanf("%d %d %s",&n,&m,str+1);//循环里i从1开始,字符串存储也要从1开始
	
	p[0]=1,h[0]=0;
	for(int i=1;i<=n;i++){
		p[i]=p[i-1]*P;//p[i]存储的是P的i次方
		h[i]=h[i-1]*P+str[i];//求前缀和
	}
	
	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;
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
哈希表是一种常见的数据结构,可以快速地进行查找、插入和删除操作。哈希表的核心思想是将数据元素通过哈希函数映射到一个固定大小的数组,每个数据元素在数组对应一个位置,通过查询这个位置可以快速地访问该元素。 线性探测再散列是一种解决哈希冲突的方法。当两个不同的数据元素映射到同一个数组位置时,线性探测再散列会尝试在数组找到另一个空位置,并将冲突的数据元素插入到该空位置。如果没有找到空位置,线性探测再散列会重新计算哈希函数,并尝试将数据元素插入到新的位置。 下面是一个基于C语言实现的线性探测再散列哈希表的代码示例: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #define HASH_TABLE_SIZE 100 typedef struct _hash_node { char *key; char *value; } hash_node; typedef struct _hash_table { hash_node **nodes; int size; } hash_table; unsigned long hash_function(char *key) { unsigned long hash = 5381; int c; while ((c = *key++)) { hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ } return hash; } hash_table *create_hash_table(int size) { hash_table *table = (hash_table *) malloc(sizeof(hash_table)); table->nodes = (hash_node **) calloc(size, sizeof(hash_node *)); table->size = size; return table; } void destroy_hash_table(hash_table *table) { int i; for (i = 0; i < table->size; i++) { if (table->nodes[i]) { free(table->nodes[i]->key); free(table->nodes[i]->value); free(table->nodes[i]); } } free(table->nodes); free(table); } void insert_hash_table(hash_table *table, char *key, char *value) { unsigned long hash = hash_function(key) % table->size; int i = 0; while (table->nodes[hash] && strcmp(table->nodes[hash]->key, key) != 0) { i++; hash = (hash + i) % table->size; } if (table->nodes[hash]) { free(table->nodes[hash]->value); table->nodes[hash]->value = strdup(value); } else { table->nodes[hash] = (hash_node *) malloc(sizeof(hash_node)); table->nodes[hash]->key = strdup(key); table->nodes[hash]->value = strdup(value); } } char *lookup_hash_table(hash_table *table, char *key) { unsigned long hash = hash_function(key) % table->size; int i = 0; while (table->nodes[hash] && strcmp(table->nodes[hash]->key, key) != 0) { i++; hash = (hash + i) % table->size; } if (table->nodes[hash]) { return table->nodes[hash]->value; } else { return NULL; } } int main() { hash_table *table = create_hash_table(HASH_TABLE_SIZE); insert_hash_table(table, "foo", "bar"); insert_hash_table(table, "baz", "qux"); printf("%s\n", lookup_hash_table(table, "foo")); /* bar */ printf("%s\n", lookup_hash_table(table, "baz")); /* qux */ printf("%s\n", lookup_hash_table(table, "quux")); /* (null) */ destroy_hash_table(table); return 0; } ``` 在该代码,我们定义了一个`hash_node`结构体表示哈希表的一个节点,其包含了一个键值对。我们还定义了一个`hash_table`结构体表示哈希表本身,其包含了一个节点数组和哈希表的大小。 我们使用`hash_function`函数来计算键的哈希值,并使用`create_hash_table`函数来创建一个指定大小的哈希表。`destroy_hash_table`函数用于销毁哈希表。 我们使用`insert_hash_table`函数来向哈希表插入一个键值对,该函数先计算键的哈希值,然后在哈希表查找是否已经存在该键,如果存在则更新其值,否则创建一个新的节点。当出现哈希冲突时,我们使用线性探测再散列来寻找下一个空位置。 我们使用`lookup_hash_table`函数来查找哈希表指定键的值,该函数先计算键的哈希值,然后在哈希表查找该键。 最后,在`main`函数,我们创建了一个哈希表,向其插入两个键值对,并且使用`lookup_hash_table`函数查找指定键的值。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值