哈希
std::set、std::multiset 的底层实现是红黑树,不是哈希表,但是std::set、std::multiset 依然使用哈希函数来做映射,只不过底层的符号表使用了红黑树来存储数据
这里在说一下,一些C++的经典书籍上 例如STL源码剖析,说到了hash_set hash_map,这个与unordered_set,unordered_map又有什么关系呢?
实际上功能都是一样一样的, 但是unordered_set在C++11的时候被引入标准库了,而hash_set并没有,所以建议还是使用unordered_set比较好,这就好比一个是官方认证的,hash_set,hash_map 是C++11标准之前民间高手自发造的轮子。
所以优先使用unordereed set 而不是hashset
如果在做面试题目的时候遇到需要判断一个元素是否出现过的场景也应该第一时间想到哈希法!
那有同学可能问了,遇到哈希问题我直接都用set不就得了,用什么数组啊。
直接使用set 不仅占用空间比数组大,而且速度要比数组慢,set把数值映射到key上都要做hash计算的。
不要小瞧 这个耗时,在数据量大的情况,差距是很明显的。
基本思路:定义一个bool醒的变量,在读入数字x时,使得这个hashtable[x]=false,于是查询的时候就直接看hashtable[y]是否等于ture判断是否出现过
如果查询某数出现的次数,bool->int就好了,每次读入hashtable[x]++;
问题1:当出现的数字超过了数组能开的最大范围怎么办?
解决:通过一个函数将这个数转为另一个整数,使得这个整数可以唯一的代表这个数字
取模法:key%mod,并且当mod是一个素数,函数能覆盖每一个数,如果不同的数取模的结果得到了同一个数即冲突
解决冲突:1.检查h(key)+1下一个位置是否被占领,如果被占领向下一个位置移动,如果超过了就回到表头继续找,直到所有位置都被使用了
2.平方探查法:H(key)+12,H(key)+12,H(key)+22,H(key)-22,如果超过了表长,就把(h(key)+k^2)取mod再找,
3.拉链法:不重新计算哈希位置,而是将得到的key用链表链接起来,所以冲突的时候直接连接这个表就行了
然而上面的我们都用不到,只需要使用unordered_map就好
问题2:字符串哈希怎么办
解决:
点哈希(x,y),让hash函数为:x*range+y,酱紫任意两个整数点都不会撞到一起,然后再使用整数哈希
字符串哈希:让一个哈希函数映射成一个整数,即26进制->10进制
int getid(char name[],int len){//len为字符串长度
int id=0;
for(int i=0;i<len;i++)
{
id=id*26+(s[i]-'A');
//乘26,再加上这次的数字(26进制)
}
return id;
}
```
如果出现了小写字母,把a-z看成26-51,即52进制转为10进制
if(s[i]>='A'&&s[i]<='Z'){
id=id*26+(s[i]-'A');
}
else if(s[i]>='a'&&s[i]<='z'){
id=id*52+s[i]-'a';
}
出现了数字,可以将进制增加到62
特殊情况如果是ben4 这种字母加数字且一直数字的位数的组合,就可以只处理前面的字母for((i<=len-x)) 循环结束后再加上id=id+s[len-1]-'0';
#题目练习:
##旧键盘 给出字符串 This_is_a_test 与缺少 _hs_s_a_es 找出缺少的字符就是用不了的键(空格也算) 按照发现顺序输出
//本题自然的想法,让串1的各个字符c1
//与串2各个c2进行比较当相等的时候c1++枚举下个字符
// 当串2找不到相应的字符说明需要输出
//还要保证输出的次数为1,那么可让对于bool设为true
include
include
using namespace std;
//这里使用哈希表做法,因为串1字符更多,串二更少,所以串二设为真后,到了串1仍然为假的说明这个字符在串二没有出现过,
//输出这个字符后设为真就不会在输出了
int main()
{
string s1,s2;
cin>>s1>>s2;
bool hash[256] = {false};//256包含了数字下划线等等
int len_1 = s1.length(),len_2 = s2.length();
for(int i=0;i<len_2;++i)
{
s2[i] = towupper(s2[i]);
hash[s2[i]]=true;//让串2统统变成小写字母,并且让对应的下标设为真
}
for(int i=0;i<len_1;++i)
{
s1[i] = towupper(s1[i]);
if(hash[s1[i]]==false)//如果对应的哈希映射没有,说明串二没有
cout<<s1[i];
hash[s1[i]]=true;
}
return 0;
}
##坏键盘打字 给出字符串,和坏了的键 求屏幕出现的字符串
//定义bool型的数组设置为真,然后对读入的第一个字符串字符c让bool[c]设为假,表示键盘失效
//读入第二个字符串,遍历字符。如果c是大写字符,首先检查这个大写字符转为小写字符后的键和+键是否有效,都有效才输出
//当字符c是除了大写字母外的其他字符,只需要判断其本身键位是否有效就可
include<bits/stdc++.h>
using namespace std;
bool Map[256]={true};//这里直接赋值是不行的,只会让第一个变成真
int main(){
memset(Map,true,sizeof(Map));//这步才真正让全部都变为真
string s1,s2;
cin>>s1>>s2;
char c1;
for(int i=0;i<s1.length();i++){
c1=s1[i];
c1=tolower(c1);
Map[c1]=false;
}
if(Map[100]==true)
for(int i=0;i<s2.length();i++){
c1=s2[i];
if(c1>='A'&&c1<='Z'){//对大写特殊处理
if(Map['+']==true&&Map[c1+32]==true)/当+号ok
cout<<c1;
}
else if(Map[c1]==true){
cout<<c1;
}
}
return 0;
}
冷知识strlen是o(n)复杂度的 所以不要在for(里面用strlen)
##
vector
if (nums1.size() > nums2.size()) {
return intersect(nums2, nums1);//返回一个数组实现了 交换对长度较小的元素进行操作的效果!!秒!1
}
unordered_map <int, int> m;
for (int num : nums1) {//这个比 使用下标遍历的速度快很多
++m[num];
}
vector
for (int num : nums2) {//查找在数组二中的数字
if (m.count(num)) {//如果这个数字存在
intersection.push_back(num);//放入这个数字到结果,有几次放几次
--m[num];//并且对哈希表中的数字次数减一下
if (m[num] == 0) {//减到0 就清楚这个记录,记得删除,因为m.count是存在就返回1,0也是存在,这样会按nums2计算
m.erase(num);
}
}
}
return intersection;
##排序+双指针
class Solution {
public:
vector
sort(nums1.begin(), nums1.end());//先进行排序
sort(nums2.begin(), nums2.end());
int length1 = nums1.size(), length2 = nums2.size();
vector
int index1 = 0, index2 = 0;
while (index1 < length1 && index2 < length2) {//双指针
if (nums1[index1] < nums2[index2]) {//当这个素组指向的元素比拎一个素组的元素小,指往右边移动
index1++;
} else if (nums1[index1] > nums2[index2]) {
index2++;
} else {
intersection.push_back(nums1[index1]);//如果相当就放入并同时往右边移动
index1++;
index2++;
}
}
return intersection;
}
};
##哈希表happy number 判断是否循环出现了一个数,而这就涉及了查找操作,显然需要使用哈希表
unordered_set中的查找操作set.find()找不到会返回最后一个元素迭代器即set.end(),插入操作insert
class Solution {
public:
// 取数值各个位上的单数之和
int getSum(int n) {
int sum = 0;//必须赋初值
while (n) {
sum += (n % 10) * (n % 10);
n /= 10;
}
return sum;
}
bool isHappy(int n) {
unordered_set
while(1) {
int sum = getSum(n);
if (sum == 1) {
return true;
}
// 如果这个sum曾经出现过,说明已经陷入了无限循环了,立刻return false
if (set.find(sum) != set.end()) {
return false;
} else {
set.insert(sum);
}
n = sum;
}
}