背景
查找算法不论是顺序查找还是二分查找,其查找的过程中都需要进行若干次的比较,比如顺序查找需要经过n次的比较,二分查找需要经过logn次的比较,那么有没有什么数据结构可以保证1次比较或者只需要几次比较就可以判断是否能找到这样的元素呢?这种数据结构就是hash表。
什么是哈希表
哈希表又称为散列表,它是一种以键值对形式来存储数据的结构,只要输入待查找的key,就可以通过该key寻找到对应的值。
哈希表用的是数据支持下标随机访问数据的特性来实现的,所以说哈希表是数组的扩展,是由数据演化而成。
哈希表的新增操作
可以通过mod运算来算出数据应该存放到哈希表的哪个位置
比如key=19,value=40,hash表的表长是length=13,那么key % length = 6 ,因此key=19这个元素应该放在哈希表下标为6的位置。

哈希表的查找操作
对于给定的key通过mod运算找出对应的位置,然后判断再这个位置上是否有元素,即可以判断是否找到对应的元素。
哈希冲突
在我们对哈希表执行新增操作的时候,如果有多个元素映射到同一个哈希表中的位置,那么就产生了哈希冲突,也称之为哈希碰撞。
比如在上图的哈希表中,我们再新增元素key=89,value=89,此时我们计算出映射下标idx = 1 ,但之前的idx=1的位置已经存放了key=12的元素,这时就产生了哈希冲突。

哈希冲突解决方法
线性探测法:当发生冲突以后,就顺次向后挪动,直到找到第一个没有冲突的位置就停止下来。

二次探测法:当发生冲突以后,按照先右后左的顺序摆动,直到找到第一个没有冲突的位置就停止下来。

链地址法:基于链表的操作,来解决冲突问题, 当发生冲突以后,则把元素以链表的方法链接在对应的位置下面即可解决哈希冲突。

哈希表在有哈希冲突下的查询操作
首先我们需要计算出待查询元素位于哈希表的下标idx,如果下标idx元素为空,那么就认为找不到该元素
如果idx处不为空且结果不等于我们待查找的元素,应该按照哈希冲突的方法不断的挪动待查找的位置,直到找到元素或者遇到了第一个为空的位置就认为找不到元素。
哈希表在有哈希冲突下的删除操作
针对链地址法的哈希表,直接物理删除即可,因为物理删除不会引发歧义。
针对线性探测的哈希表和二次探测的哈希表,只能进行逻辑删除,即删除的时候做一个逻辑标记即可。如果直接物理删除,那么可能会导致后面待查询的元素明明在哈希表中,但却出现查询不到的情况。

评价哈希算法的指标
装载因子: 实际存储的空间 总存储空间
比如上图中装载因子 = 5 11 = 0.456,装载因子越大越容易发生哈希冲突。
手写实现一个以链表法解决冲突的哈希表
需要注意的点:
1、当实际装载的哈希槽超过了最大装载因子的容量需要考虑hash扩容
2、当同一个槽中产生的hash冲突超过一定数量的时候,需要考虑链表转为红黑树
3、哈希表的遍历时候,考虑实现一个迭代器
定义链表的节点
public class Node {private String key;private String value;private int hash;private Node next;public Node(String key, String value, int hash, Node next) {this.key = key;this.value = value;this.hash = hash;this.next = next;}public Node() {}public void setValue(String val){this.value = val;}public String getKey(){return key;}public Node getNext(){return next;}public void setNext(Node next){this.next = next;}@Overridepublic boolean equals(Object o) {if (this == o) {return true;}if (!(o instanceof Node)){return false;}Node node = (Node) o;return Objects.equals(key, node.key);}@Overridepublic int hashCode() {return Objects.hash(key);}@Overridepublic String toString() {return "Node{" +"key='" + key + '\'' +", value='" + value + '\'' +", hash=" + hash +'}';}}
定义哈希表
public class MyHashTable {private Node[] table;private double loadFactor;private int size;private int availableBucket;// static 表示成员变量属于这个类private static final double default_load_factor = 0.75;private static final int default_capacity = 16;private static final int linkToTreeCount = 8;public MyHashTable(int capacity,double loadFactor){this.loadFactor = loadFactor;table = new Node[capacity];size = 0;availableBucket = 0;}public MyHashTable(){this(default_capacity,default_load_factor);}private int hash(String key){return Objects.hash(key);}public void output(){for(int i =0;i<table.length;i++){Node p = table[i];System.out.println("第 "+i+" 桶");while(p!=null){System.out.printf("%s\t",p.toString());p = p.getNext();}System.out.println();}}public Node get(String key){int hash = hash(key);int idx = (table.length-1)&hash;Node p = table[idx];while(p!=null && !p.getKey().equals(key)){p = p.getNext();}return p;}public boolean put(String key,String value){int hash = hash(key);int idx = (table.length-1)&hash;// if(table[idx] instanceof Node){//// }else{// 红黑树插入// }Node head = table[idx];if(head == null){table[idx] = new Node(key,value,hash,null);size++;availableBucket++;}else{Node p = head;Node tar = new Node(key,value,hash,null);Node tail = null;int linkLen = 0;while(p!=null){if(p.equals(tar)){// 进行一个替换p.setValue(value);return false;}tail = p;p = p.getNext();linkLen++;}if(p==null){tail.setNext(tar);linkLen++;}if(linkLen > linkToTreeCount){// 链表转红黑树// link2tree();}}if(availableBucket >= (int)table.length*loadFactor){// 扩容// reszie();}return true;}}
Main函数调用
public class Main {public static void main(String[] args) {MyHashTable table = new MyHashTable();table.put("123","1234");table.put("1234","12345");table.put("12","123");table.put("1","12");table.put("1235","123456");table.put("123456","1234567");table.put("123","321");table.output();System.out.println("get(123) = " +table.get("123"));}}




