蓝桥杯C++练习题 <弗里的语言> 哈希表

首先大概说一下哈希表,我认为就是一个可以高效率查找的记录数据的东西,采用映射的方法将那些数据记录起来,然后映射的不同就分成了好几种方法,常见有直接定址法、除留余数法、数学分析法等等,然后呢由这些映射方法得到其键值又不一定是唯一的,有时候可能出现两个要存储的数对应键值一样,即发生冲突,那么针对解决冲突的方法又分两类,一类叫闭环另一类叫开环(大概这个意思),闭环里面就有像拉链法和溢出表法(多建一个一模一样的,顺序存放,有冲突的就放到这个里面),很多时候溢出表法效率就很高。然后我们有一些比较典型的哈希公式(效率很高)供我们利用,只需记一些就可以。比如下面的一种在算法竞赛中特别常用的字符串映射成数字的方式。

原理:1、将字符串中的每一个字母都看做是一个数字(例:从 a-z ,视为 1-26 );

2、选取两个合适的互质常数 b 和 h,其中 h 要尽可能的大一点,为了降低冲突的概率。b 常用 131,h 常用 1e9+7,这里我们需要设置公共溢出区所以,我们需要随便找一个 string 数组能开出来的数字,这里选取 999983。

3、定义哈希函数:

 处理的方法:

  1. C 代表一个字符串,用 C =c1 c2 c3 c4...cm 表示该字符串,其中 ci 表示从前向后数的第 i 个字符;
  2. C 当做 b 进制数 来处理,b 是基数;
  3. 关于对 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;
}

当然这里只是针对这道题的解法,如果要真正实现散列表的插入,还要完善一下,在插入那里冲突时可以采用插到溢出表里,或者直接线性探索下一个可以插入的位置进行插入。

Supongo que te gusta

Origin blog.csdn.net/mc10141222/article/details/123720581
Recomendado
Clasificación