package com;
/**
* Created with IntelliJ IDEA.
我在学习蓝桥杯过程中的一点想法
主要目标是学会散列表(hash 算法)的原理与实现,
学会灵活的运用,能够不依赖于模板根据题目独立写出各类散列表。
数据结构 Hash 属于查找算法中的一部分
知识点
Hash 的概念
构造方法
冲突处理
第一步,首先我们需要创建一个散列表和一个公共溢出区。
散列表
公共溢出区
第二步,需要定义插入散列表函数。
按照散列表的映射方式设计即可
需要传入一个参数来表示放什么数据
in(Name){
1. 无冲突
2. 冲突处理
}
第三步:定义查询函数。
isAt(){
1. 如果散列表查询成功返回 True
2. 不为成功返回 False
}
第四步,定义散列表映射函数。
此处我们采用除留余数法即可,
不了解这个方法的同学别担心,
后面在写解题代码的时候,
我会具体为大家介绍。
int out(string s){
处理字符串 s 生成对应的 Key 值
}
第五步,编写主函数代码。
输入 N
循环 N 次://
输入 word;
先查询,有相同的单词有就设置 flag 为 1,ans = word
没有的话,就执行插入操作
根据 flag 决定输出什么。
如何设计实现散列函数
在使用散列表的时候,我们有两个关键的技术问题需要解决:
散列函数的设计,如何设计一个简单、均匀、存储利用率高的散列函数?
冲突的处理,如何采取合适的处理冲突方法来解决冲突。
如何设计实现散列函数
在构建散列函数时,我们需要秉持两个原则:
简单
散列函数不应该有很大的计算量,否则会降低查找效率。
均匀:
函数值要尽量均匀散布在地址空间,这样才能保证存储空间的有效利用并减少冲突。
2. 除留余数法。
H(key)=key mod p
来个小例子:
H(key)=key mod 21
会发现产生了很多相同的 H(K),
这就是发生冲突,因为一个位置只能放一个数,
有两个值对应这里一个位置,是不可以的。
这种方法是最常用的方法,
这个方法的关键在于如何选取 P,
使得利用率较高并且冲突率较低,
一般情况下,我们会选取最接近表长且小于等于表长的最大素数。
缺点:
P 选取不当,会导致冲突率上升。
适用范围:
除留余数法是一种最简单、
也是最常用的构造散列函数的方法,
并且不要求事先知道关键码的分布。
这个方法非常常用,
我们后面题目的展开就是使用的这个方法。
在大部分的算法实现中也都是选取的这一种方式。
一共要创建两个数组
一个是根据哈希值存储的哈希表
另一个公共溢出区负责存储 在哈希表已经存在的值
公共溢出区里面的数值挨个存储
*/
import java.util.Scanner;
import java.util.Stack;
public class Demo001 {
// 第一步,我们需要创建一个散列表和一个公共溢出区:
static final int h=999983;
static String[] Value=new String [h];
static String[] UpValue=new String [h];
// 这是公共溢出区数组的指针 用来指示公共溢出区一共有多少数值
static int UpValueCount=0;
// 第二步,我们要定义散列表映射函数:
/*
采用除留余数法来定义散列表映射函数
S是数值 哈希值可以用hashcod方法来转换得到S
H(S)=(S%h+h)%h
* */
static int Hx(String s) {
return (s.hashCode()%h+h)%h;
}
// 第三步,我们定义查询函数:
static boolean isAt(String s){
int n=Hx(s);
// 待查询数值的哈希值在哈希表相应位置为空 当然是没有了
if(Value[n]==null){
return false;
// 待查询数值的哈希值在哈希表相应位置不为空 且 内容相同 这就啥查到了
}else if(Value[n].equals(s)){
return true;
}else {
// 在哈希表 为空 或者 不为空 但是相应位置的元素不同的情况下
// 开始遍历溢出区
for(int i=0;i<UpValueCount;i++){
// 如果有相同的话元素 那就 返回 true 找到了
if(UpValue[i].equals(s)){
return true;
}
}
// 经过遍历没有找到 那就是 没有 返回false
return false;
}
}
// 第四步,定义插入散列表函数:
// 按照散列表的映射方式设计即可;
// 需要传入一个参数来表示放什么数据。
/*
* */
static boolean in(String s) {
int n=Hx(s);
// 如果哈希表这个位置是空的 所以直接存储进去
if(Value[n]==null) {
Value[n]=s;
return true;
// 如果哈希表这个位置不是空的,则开始比较内容 如果内容相同 不进行插入
} else if(Value[n].equals(s)){
return false;
// 待插入元素在哈希表这个位置不为空且存的元素 不相同 那么 这个元素就存储到公共溢出区
} else{
// 在公共溢出区遍历一遍 如果有相同的元素 则不进行插入 返回false 不在执行下面的函数
for(int i=0;i<UpValueCount;i++){
if(UpValue[i].equals(s)){
return false;
}
}
// 公共溢出区挨个插入
UpValue[UpValueCount++]=s;
return true;
}
}
// 主函数
public static void main(String[] args) {
Scanner in=new Scanner(System.in);
int n=in.nextInt();
boolean flag=false;
String ans="NO";
for(int i=0;i<n;i++) {
String word;
word=in.next();
// System.out.println(Hx(word));
// 如果 flag变成了 true 表示 有相同的元素 跳过这个循环
// 插入过程中如果遇到相同的元素将不再进行插入 跳过接下来的 过程
// 例如 asdaerty 第四个数值是重复的 erty将不再进行插入
if(flag){
// 跳过当前循环继续下一个循环。
continue;
}
// 进行查询 如果有相同的
if(isAt(word)){
flag=true;
ans=word;
}else {
in(word);
}
}
System.out.println(ans);
}
}