简介:
这里先说一下哈希表的定义:哈希表是一种根据关键码去寻找值的数据映射结构,该结构通过把关键码映射的位置去寻找存放值的地方,说起来可能感觉有点复杂,我想我举个例子你就会明白了,最典型的的例子就是字典,大家估计小学的时候也用过不少新华字典吧,如果我想要获取“按”字详细信息,我肯定会去根据拼音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()); } }