算法学习入门 --- 哈希表和unordered_map、unordered_set(C++)

目录

1.哈希表的概念

2.常见的哈希函数

3.处理哈希冲突

4.模拟实现哈希表

5.unordered_set与unordered_map

[6.洛谷--- [JLOI2011] 不重复数字](#6.洛谷--- [JLOI2011] 不重复数字)

[7.洛谷--- [USACO16DEC] Cities and States S](#7.洛谷--- [USACO16DEC] Cities and States S)


1.哈希表的概念

哈希表,又称散列表,是根据关键字进行访问的数据结构。

它建立了一种关键字和存储地址之间的映射关系,使每个关键字和结构中的唯一存储位置相对应

该函数记作 hash(key) = addr,hash('b') = 'b' - 'a' 代表b这个key和a去相减后,数组存在下标为1处,那么就是映射关系和地址对应

哈希表的初始值一般是无穷大INF(整型最大值)


哈希冲突:

对于 6,1007,1007 这个数组来说,如果我们用hash[key] = key%7 作为哈希函数,那么三个数组元素的存储位置都是6,出现了存储位置的冲突。下标6到底存哪个元素呢?要打架了,就称为哈希冲突

哈希冲突无法避免,而是需要设计出优秀的哈希函数,尽量避免冲突

2.常见的哈希函数

  • 直接定址法:hash[key] = key 或者 hash[key] = a×key + b
  • 除留余数法:hash[key] = key % m。key为负数时,在C++当中会是一个负数结果;所以,我们需要取模以后,加上一个N值(除数),即 key % N + N,来抵消取模为负的情况;当整数取模时,加上N值以后与原结果不符,所以需要 (key % N + N) % N
  • 乘法散列法、全域散列法......

3.处理哈希冲突

  • 线性探测:从发生冲突的位置开始,依次线性向后探测,直到寻找到下一个没有存储数据的位置为止,如果走到哈希表尾,则回绕到哈希表头的位置;如下所示,若30、20发生了哈希冲突,当30先存20后存的话,20就存到30的后面一个空位中。这种办法下,如果存的数很密集时,那么就会出现非常多次的哈希冲突,所以创建一个大一点数组可以解决这种问题。
  • 链地址法:所有数据不再直接存储在哈希表中,哈希表中只存一个指针,无数据映射时,指针为空,有多个数据映射到这个位置时,把冲突的数据链成一个链表,挂在哈希表这个位置下面。当所有元素都是相同的地址映射,那么某一下标下就要存储过大的链表。

4.模拟实现哈希表

  • 线性探测法的哈希表创建与使用
cpp 复制代码
#include<iostream>
#include<cstring>

using namespace std;
const int N = 25;
int h[N];
const int INF = 0x3f3f3f3f;

void init()
{
	memset(h,INF,sizeof(h));
}

//返回映射的位置 
int f(int x)
{
	int id = (x%N+N)%N;//id为存入位置 
	//处理哈希冲突(1.当前位置已经存数,2.该数已经存储过一次) 
	while(h[id]!=INF&&h[id]!=x)
	{
		id++;	
		if(id == N) id=0;//处理id越界 
	}	
	return id;
}

void insert(int x)
{
	int idx = f(x);//获取存放位置
	h[idx] = x; 
}

bool find(int x)
{
	int idx = f(x);
	return h[idx] == x;
}

int main()
{
	//初始化 
	init();
	int n;cin>>n;
	//开始插入与查找
	while(n--)
	{
		int op,x;cin>>op>>x;
		if(op==1) insert(x);
		else
		{
			if(find(x))cout<<"YES"<<endl;
			else cout<<"NO"<<endl;
		}	
	} 
	return 0;
}
  • 链地址法的哈希表创建与使用
cpp 复制代码
#include<iostream>
using namespace std;

const int N = 25;
int h[N];//哈希表
int ne[N],e[N],id;

int f(int x)
{
	//查找位置
	return (x%N+N)%N; 
}

void insert(int x)
{
	int idx = f(x);
	//插入
	id++;
	e[id] = x;
	//解决冲突 (链表在idx位置处,进行头插) 
	ne[id] = h[idx]; 
	h[idx] = id;//更新头节点 
}

bool find(int x)
{
	int idx = f(x);
	for(int i=h[idx];i;i=ne[i])
	{
		if(e[i] == x) return true; 	
	}	
	return false;
}

int main()
{
	int n;cin>>n;
	//开始插入与查找
	while(n--)
	{
		int op,x;cin>>op>>x;
		if(op==1) insert(x);
		else
		{
			if(find(x))cout<<"YES"<<endl;
			else cout<<"NO"<<endl;
		}	
	} 
	return 0;
} 

5.unordered_set与unordered_map

  • unordered_set 与 set 的区别在于:前者哈希表实现,后者红黑树实现。存储和查找效率不同,前者O(1),后者O(logN)。以及遍历的顺序,前者无序后者有序。其他都是一样的
  • unordered_map 与 map 的区别基本就上面这些
  • unordered_map 可以用来存图,<int,vector<int>> 类型的数据,查询和存储效率更高
  • 同时,unordered_map与unordered_set都多出了一个clear函数,代表清空整个哈希表

6.洛谷--- [JLOI2011] 不重复数字

代码:

cpp 复制代码
#include<iostream>
#include<unordered_set>
using namespace std;

int main()
{
	int T;cin>>T;
	unordered_set<int> h;
	while(T--)
	{
		h.clear();
		int n;cin>>n;
		while(n--)
		{
			int in;cin>>in;
			if(!h.count(in)) 
			{
				h.insert(in);
				cout<<in<<" ";	
			}
		}
		cout<<endl;
	}
	return 0;
}

代码问题:

用这个代码去提交之后,会发现有4个测试用例超时了,这是因为cin、cout会导致时间开销过大,所以把cin、cout都换成scanf和printf即可

7.洛谷--- [USACO16DEC] Cities and States S

本题需要注意的点是,AAC->CA & AAB->CA 代表两种不同的情况,如果出现了 CAA->AA 那么应该获得两对的结果而非一对。但也可能会出现AAD->FA的情况,所以需要写一个sum函数进行判断是否符合情况。

所以哈希表的对应关系应该为<string,vector<string>>


题目中提到两个城市来自于两个不同的州,所以当AAB->AA & AAC->AA 这种情况虽然满足特性,但因为来自同一个州所以还是得排除在外

提示:当我们觉得代码完全没问题,但死活不过时,可以通过再念两遍题目来看看有没有条件漏了或理解错了

代码:

cpp 复制代码
#include<iostream>
#include<unordered_map>
#include<string>
#include<vector>
using namespace std;

void sum(int& ret,string in,vector<string> states)
{
	for(auto st:states)
		if(in==st) ret++;
}
int main()
{
	int n;cin>>n;
	unordered_map<string,vector<string>> h;//前两个字母对应州名
	string st,sta;cin>>st>>sta;
	h[st.substr(0,2)].push_back(sta);
	n--;
	int ret=0;
	while(n--)
	{
		string name,state;cin>>name>>state;
		string in = name.substr(0,2);
        if(in==state)continue;
		if(h.count(state)) sum(ret,in,h[state]);
		h[in].push_back(state);
	}
	cout<<ret; 
	return 0;
}
相关推荐
C++ 老炮儿的技术栈2 小时前
在C++ 程序中调用被 C编译器编译后的函数,为什么要加 extern “C”声明?
c语言·c++·windows·git·vscode·visual studio
%xiao Q2 小时前
GESP C++五级-202406
android·开发语言·c++
Sarvartha2 小时前
C++ STL 栈的便捷使用
c++·算法
夏鹏今天学习了吗3 小时前
【LeetCode热题100(92/100)】多数元素
算法·leetcode·职场和发展
Aevget3 小时前
MFC扩展库BCGControlBar Pro v37.2 - 全新的VS 2026可视化管理器
c++·mfc·bcg·界面控件·ui开发
飞Link3 小时前
深度解析 MSER 最大稳定极值区域算法
人工智能·opencv·算法·计算机视觉
bubiyoushang8883 小时前
基于CLEAN算法的杂波抑制Matlab仿真实现
数据结构·算法·matlab
C+-C资深大佬4 小时前
C++类型判断
开发语言·c++
Yu_Lijing4 小时前
基于C++的《Head First设计模式》笔记——模式合作
c++·笔记·设计模式