简介:
这里先说一下哈希表的定义:哈希表是一种根据关键码去寻找值的数据映射结构,该结构通过把关键码映射的位置去寻找存放值的地方,说起来可能感觉有点复杂,我想我举个例子你就会明白了,最典型的的例子就是字典,大家估计小学的时候也用过不少新华字典吧,如果我想要获取“按”字详细信息,我肯定会去根据拼音an去查找 拼音索引(当然也可以是偏旁索引),我们首先去查an在字典的位置,查了一下得到“安”,结果如下。这过程就是键码映射,在公式里面,就是通过key去查找f(key)。其中,按就是关键字(key),f()就是字典索引,也就是哈希函数,查到的页码4就是哈希值。
基本概念
通过字典查询数据
-
若关键字为k,则其值存放在f(k)的存储位置上。由此,不需比较便可直接取得所查记录。称这个对应关系f为散列函数,按这个思想建立的表为散列表。
对不同的关键字可能得到同一散列地址,即k1≠k2,而f(k1)=f(k2),这种现象称为碰撞(英语:Collision)。具有相同函数值的关键字对该散列函数来说称做同义词。综上所述,根据散列函数f(k)和处理碰撞的方法将一组关键字映射到一个有限的连续的地址集(区间)上,并以关键字在地址集中的“像”作为记录在表中的存储位置,这种表便称为散列表,这一映射过程称为散列造表或散列,所得的存储位置称散列地址。
若对于关键字集合中的任一个关键字,经散列函数映象到地址集合中任何一个地址的概率是相等的,则称此类散列函数为均匀散列函数(Uniform Hash function),这就是使关键字经过散列函数得到一个“随机的地址”,从而减少碰撞。
-
处理冲突
1. 开放寻址法:Hi=(H(key) + di) MOD m,i=1,2,…,k(k<=m-1),其中H(key)为散列函数,m为散列表长,di为增量序列,可有下列三种取法:
1.1. di=1,2,3,…,m-1,称线性探测再散列;
1.2. di=1^2,-1^2,2^2,-2^2,⑶^2,…,±(k)^2,(k<=m/2)称二次探测再散列;
1.3. di=伪随机数序列,称伪随机探测再散列。
2. 再散列法:Hi=RHi(key),i=1,2,…,k RHi均是不同的散列函数,即在同义词产生地址冲突时计算另一个散列函数地址,直到冲突不再发生,这种方法不易产生“聚集”,但增加了计算时间。
3. 链地址法(拉链法)
4. 建立一个公共溢出区
代码实现
这里主要演示的是开放寻址法和连地址法
基本代码
这里演示的是没有经过hash冲突处理的。
/**
* 基础员工类
* @project JavaData
* @date 2018年4月10日 下午5:15:20
* @author Huaxu-Charles
*/
public class Info {
private String key;
private String name;
public Info(String key, String name) {
this.key = key;
this.name = name;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
基本功能实现
public class HashTable {
private Info[] arr;
/**
* 默认的构造方法
*/
public HashTable() {
arr = new Info[100];
}
/**
* 指定数组初始化大小
*/
public HashTable(int maxSize) {
arr = new Info[maxSize];
}
/**
* 插入数据
*/
public void insert(Info info) {
arr[hashCode(info.getKey())] = info;
}
/**
* 查找数据
*/
public Info find(String key) {
return arr[hashCode(key)];
}
public int hashCode(String key) {
BigInteger hashVal = new BigInteger("0");
BigInteger pow27 = new BigInteger("1");
for(int i = key.length() - 1; i >= 0; i--) {
int letter = key.charAt(i) - 96;
BigInteger letterB = new BigInteger(String.valueOf(letter));
hashVal = hashVal.add(letterB.multiply(pow27));
pow27 = pow27.multiply(new BigInteger(String.valueOf(27)));
}
return hashVal.mod(new BigInteger(String.valueOf(arr.length))).intValue();
}
}
测试方法
public class TestHashTable {
public static void main(String[] args) {
HashTable ht = new HashTable();
ht.insert(new Info("a","张三"));
ht.insert(new Info("ct","李四"));
ht.insert(new Info("wangwu","王五"));
System.out.println(ht.find("a").getName());
System.out.println(ht.find("ct").getName());
}
}
开放寻址法
当要插入一个元素时,可以连续地检查散列表的个各项,直到找到一个空槽来放置这个元素为止。检查顺序可以是线性的,可以是二次的,也可以是再次散列的。普通话解释就是,当你去超市买的这个东西没有时,你会继续寻找别的超市,知道买到这个东西。但是找的方式可以有很多种。这里需要注意的就是插入定义的规则一定也要落实到查找和删除中。
public class HashTable {
private Info[] arr;
/**
* 默认的构造方法
*/
public HashTable() {
arr = new Info[100];
}
/**
* 指定数组初始化大小
*/
public HashTable(int maxSize) {
arr = new Info[maxSize];
}
/**
* 插入数据
*/
public void insert(Info info) {
//获得关键字
String key = info.getKey();
//关键字所自定的哈希数
int hashVal = hashCode(key);
//如果这个索引已经被占用,而且里面是一个未被删除的数据
while(arr[hashVal] != null && arr[hashVal].getName() != null) {
//进行递加
++hashVal;
//循环
hashVal %= arr.length;
}
arr[hashVal] = info;
}
/**
* 查找数据
*/
public Info find(String key) {
int hashVal = hashCode(key);
while(arr[hashVal] != null) {
if(arr[hashVal].getKey().equals(key)) {
return arr[hashVal];
}
++hashVal;
hashVal %= arr.length;
}
return null;
}
/**
* 删除数据
* @param key
* @return
*/
public Info delete(String key) {
int hashVal = hashCode(key);
while(arr[hashVal] != null) {
if(arr[hashVal].getKey().equals(key)) {
Info tmp = arr[hashVal];
tmp.setName(null);
return tmp;
}
++hashVal;
hashVal %= arr.length;
}
return null;
}
public int hashCode(String key) {
// int hashVal = 0;
// for(int i = key.length() - 1; i >= 0; i--) {
// int letter = key.charAt(i) - 96;
// hashVal += letter;
// }
// return hashVal;
BigInteger hashVal = new BigInteger("0");
BigInteger pow27 = new BigInteger("1");
for(int i = key.length() - 1; i >= 0; i--) {
int letter = key.charAt(i) - 96;
BigInteger letterB = new BigInteger(String.valueOf(letter));
hashVal = hashVal.add(letterB.multiply(pow27));
pow27 = pow27.multiply(new BigInteger(String.valueOf(27)));
}
return hashVal.mod(new BigInteger(String.valueOf(arr.length))).intValue();
}
}
测试
public class TestHashTable {
public static void main(String[] args) {
HashTable ht = new HashTable();
ht.insert(new Info("a","张三"));
ht.insert(new Info("ct","李四"));
ht.insert(new Info("b","王五"));
System.out.println(ht.find("a").getName());
System.out.println(ht.find("ct").getName());
System.out.println(ht.find("b").getName());
ht.delete("b");
System.out.println(ht.find("b").getName());
}
}
链地址法
把具有相同散列地址的关键字(同义词)值放在同一个单链表中,称为同义词链表。有m个散列地址就有m个链表,同时用指针数组T[0..m-1]存放各个链表的头指针,凡是散列地址为i的记录都以结点方式插入到以T[i]为指针的单链表中。T中各分量的初值应为空指针。
/*
* 链结点
*/
public class Node {
//数据域
public Info info;
//指针域
public Node next;
public Node(Info info) {
this.info = info;
}
}
public class LinkList {
//头结点
private Node first;
public LinkList() {
first = null;
}
/**
* 插入一个结点,在头结点后进行插入
*/
public void insertFirst(Info info) {
Node node = new Node(info);
node.next = first;
first = node;
}
/**
* 删除一个结点,在头结点后进行删除
*/
public Node deleteFirst() {
Node tmp = first;
first = tmp.next;
return tmp;
}
/**
* 查找方法
*/
public Node find(String key) {
Node current = first;
while(!key.equals(current.info.getKey())) {
if(current.next == null) {
return null;
}
current = current.next;
}
return current;
}
/**
* 删除方法,根据数据域来进行删除
*/
public Node delete(String key) {
Node current = first;
Node previous = first;
while(!key.equals(current.info.getKey())) {
if(current.next == null) {
return null;
}
previous = current;
current = current.next;
}
if(current == first) {
first = first.next;
} else {
previous.next = current.next;
}
return current;
}
}
public class HashTable {
private LinkList[] arr;
/**
* 默认的构造方法
*/
public HashTable() {
arr = new LinkList[100];
}
/**
* 指定数组初始化大小
*/
public HashTable(int maxSize) {
arr = new LinkList[maxSize];
}
/**
* 插入数据
*/
public void insert(Info info) {
//获得关键字
String key = info.getKey();
//关键字所自定的哈希数
int hashVal = hashCode(key);
if(arr[hashVal] == null) {
arr[hashVal] = new LinkList();
}
arr[hashVal].insertFirst(info);
}
/**
* 查找数据
*/
public Info find(String key) {
int hashVal = hashCode(key);
return arr[hashVal].find(key).info;
}
/**
* 删除数据
* @param key
* @return
*/
public Info delete(String key) {
int hashVal = hashCode(key);
return arr[hashVal].delete(key).info;
}
public int hashCode(String key) {
// int hashVal = 0;
// for(int i = key.length() - 1; i >= 0; i--) {
// int letter = key.charAt(i) - 96;
// hashVal += letter;
// }
// return hashVal;
BigInteger hashVal = new BigInteger("0");
BigInteger pow27 = new BigInteger("1");
for(int i = key.length() - 1; i >= 0; i--) {
int letter = key.charAt(i) - 96;
BigInteger letterB = new BigInteger(String.valueOf(letter));
hashVal = hashVal.add(letterB.multiply(pow27));
pow27 = pow27.multiply(new BigInteger(String.valueOf(27)));
}
return hashVal.mod(new BigInteger(String.valueOf(arr.length))).intValue();
}
}
测试
public class TestHashTable {
public static void main(String[] args) {
HashTable ht = new HashTable();
ht.insert(new Info("a","张三"));
ht.insert(new Info("ct","李四"));
ht.insert(new Info("b","王五"));
ht.insert(new Info("dt","赵柳"));
System.out.println(ht.find("a").getName());
System.out.println(ht.find("ct").getName());
System.out.println(ht.find("b").getName());
System.out.println(ht.find("dt").getName());
// System.out.println(ht.hashCode("a"));
// System.out.println(ht.hashCode("ct"));
System.out.println(ht.delete("a").getName());
System.out.println(ht.find("a").getName());
}
}