首先大概说一下哈希表,我认为就是一个可以高效率查找的记录数据的东西,采用映射的方法将那些数据记录起来,然后映射的不同就分成了好几种方法,常见有直接定址法、除留余数法、数学分析法等等,然后呢由这些映射方法得到其键值又不一定是唯一的,有时候可能出现两个要存储的数对应键值一样,即发生冲突,那么针对解决冲突的方法又分两类,一类叫闭环另一类叫开环(大概这个意思),闭环里面就有像拉链法和溢出表法(多建一个一模一样的,顺序存放,有冲突的就放到这个里面),很多时候溢出表法效率就很高。然后我们有一些比较典型的哈希公式(效率很高)供我们利用,只需记一些就可以。比如下面的一种在算法竞赛中特别常用的字符串映射成数字的方式。
原理:1、将字符串中的每一个字母都看做是一个数字(例:从 a-z ,视为 1-26 );
2、选取两个合适的互质常数 b 和 h,其中 h 要尽可能的大一点,为了降低冲突的概率。b 常用 131,h 常用 1e9+7,这里我们需要设置公共溢出区所以,我们需要随便找一个 string 数组能开出来的数字,这里选取 999983。
3、定义哈希函数:
处理的方法:
- C 代表一个字符串,用 C =c1 c2 c3 c4...cm 表示该字符串,其中 ci 表示从前向后数的第 i 个字符;
- C 当做 b 进制数 来处理,b 是基数;
- 关于对 h 取模,若 b、h 有公因子,那么不同的字符串取余之后的结果发生冲突的几率将大大大增加(冲突:不同的字符串但会有相同的 hash 值)。
然后来看看这道题的描述:
题目描述
小发明家弗里想创造一种新的语言,众所周知,发明一门语言是非常困难的,首先你就要克服一个困难就是,有大量的单词需要处理,现在弗里求助你帮他写一款程序,判断是否出现重复的两个单词。
输入描述
第 1行,输入 N,代表共计创造了多少个单词。
第 2行至第 N+1行,输入 N个单词。
1≤N≤10^4,保证字符串的总输入量不超过 10^6。
输出描述
输出仅一行。若有重复的单词,就输出重复单词,没有重复单词,就输出 NO
,多个重复单词输出最先出现的。
输入输出样例
示例1
输入
6
1fagas
dsafa32j
lkiuopybncv
hfgdjytr
cncxfg
sdhrest
输出
NO
示例2
输入
5
sdfggfds
fgsdhsdf
dsfhsdhr
sdfhdfh
sdfggfds
输出
sdfggfds
因此对于这道题,我们可以参考上面说的那个哈希函数建立散列表
我的思路是根据那个哈希函数算出哈希值,然后把对应字符串存储到map容器中(也可以考虑下unordered_map或hash_map容器,这时候就要注意hash_map和map的区别了,简单记住就是空间够大那可以用hash_map,因为更快!),然后冲突的解决方法是开散列法,直接用简单的线性检测法。
所以首先我们要写出通过这个字符串确定哈希值的函数,参考上面那个,然后我们b值取131,h值取999983如下:
int Hs(string s) //哈希值的计算
{
int n = s.size();
int sum = 0;
for(int i=0;i<n;i++)
{
sum=sum* 131 + (s[i]-'a'+1);
}
return (sum % h); //h在上头定义了 const long long h=999983;
}
然后我们就写一个哈希表的查找和插入(如果单纯针对这道题的话,插入不成功就代表重复了,而且这题只要第一次结果,所以我们可以找个标志给他记下来,比较简单实现。)
bool isAt(string s) //查询是否存在表中 若存在则返回真
{
int Hsnum = Hs(s);
if(M1.find(Hsnum) != M1.end() ) //map.find()存在会返回其所在迭代器
{
return true;
}else
{
return false;
}
}
//插入到哈希表中的函数 若插不成功将返回false 成功返回ture
bool inHs(string s)
{
if( isAt(s) )
{
return false;
}else
{
int Hsnum = Hs(s);
M1[Hsnum] = s;
return true;
}
}
如果你能理解散列表的含义,并且知道map容器的用法,想必看到这也完全明白了,那主函数就很容易可以写出
int main()
{
// 请在此输入您的代码
unsigned int N;
unsigned char flag=0;
string name,repeatname;
cin >> N ;
for(int i=0;i<N;i++)
{
cin >> name;
bool inflag= inHs(name);
if(!inflag && flag == 0 ) //搞一个标志位,记录第一次插不进的单词(重复)
{
repeatname=name;
flag=1;
}
}
if(flag==1) //for循环完毕查看flag 看看有没有重复的
{
cout << repeatname <<endl;
}else if(flag == 0)
{
cout << "NO" <<endl;
}
return 0;
}
当然这里只是针对这道题的解法,如果要真正实现散列表的插入,还要完善一下,在插入那里冲突时可以采用插到溢出表里,或者直接线性探索下一个可以插入的位置进行插入。