哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它经过把关键码值映射到表中一个位置来访问记录,以加快查找的速度这个映射函数叫作散列函数,存放记录的数组叫作散列表。java
通俗的理解一下:node
理解了哈希表的基本思路,咱们也就不难理解为何哈希表查询效率高了:数组
因为每一个元素都能经过哈希函数直接计算得到地址,因此查找消耗时间很是少。数据结构
举个例子:ide
咱们有哈希函数f(n)=n%3,现有元素{1,2,3},咱们使用哈希函数分别得到其哈希值,并把哈希值做为下标存入一个数组,函数
也就是放f(1)=1,f(2)=2,f(3)=0,若是使用传统线性查找,须要遍历四次,而使用哈希函数计算并查找,只须要一步就能找到,this
能够看得出,理想状况下,哪怕数列再长,找到某个元素都只须要一步。.net
按照上文的例子,数列{1,2,3}经过哈希函数f(n)=n%3能够计算出哈希值,可是若是出现两个元素的哈希值相同就会出现哈希冲突,3d
好比f(1)和f(4)都会算出1,这个时候显然不可能上上面同样经过一个一维数组直接存储。code
对此咱们有两种方法,即开放地址法和分离链表法:
开放地址法:若是某一哈希值对应的位置已经被占用了,就找另外一个没被占用的位置。
注:关于开放地址法,具体能够参考这篇文章
分离链表法:将散列表的每个单元都扩展成为一个链表,相同哈希值的元素会被存储在同一个链表中。
在jdk8中,使用的就是分离链表法,当哈希冲突超过一点的限制,链表会转为红黑树。
在这里咱们实现一个基于分离链表法的哈希表:
/** * @Author:huang * @Date:2020-06-20 10:19 * @Description:节点 */ public class Node { //节点序号 int num; //下一个节点 Node next; public Node(int num) { this.num = num; } @Override public String toString() { return "Node{" + "num=" + num + '}'; } }
/** * @Author:黄成兴 * @Date:2020-06-20 10:19 * @Description:单链表 */ public class SingleLinkList { private Node head = new Node(0); public boolean isEmpty() { return head.next == null; } /** * 添加节点到链表 * @param node 要插入的节点 */ public void add(Node node) { Node temp = head; //遍历链表 while (true) { if (temp.next == null) { break; } //不是尾节点就继续遍历下一个节点 temp = temp.next; } //将尾节点指向即将插入的新节点 temp.next = node; } /** * 展现链表 */ public void show() { //判断链表是否为空 if (isEmpty()) { return; } Node temp = head.next; //遍历链表 while (true) { if (temp == null) { break; } System.out.println(temp.toString()); temp = temp.next; } } /** * 根据序号获取节点 * @param num 要获取的节点序号 * @return */ public Node get(int num){ //判断链表是否为空 if (isEmpty()) { return null; } Node temp = head.next; //遍历链表 while (true) { if (temp == null) { return null; } if (temp.num == num) { return temp; } temp = temp.next; } } /** * 修改节点 * @param node 要更新的节点 */ public void update(Node node) { Node temp = head; //判断链表是否为空 if (isEmpty()) { return; } //获取要更新的节点序号 int nodeNum = node.num; //遍历链表 while (true) { //若是已经遍历完链表 if (temp == null) { throw new RuntimeException("编号为" + temp.num + "的节点不存在!"); } //若是找到了该节点 if (temp.num == nodeNum) { return; } //继续遍历下一节点 temp = temp.next; } } /** * 删除节点 * @param num 要删除的节点编号 */ public void delete(int num) { Node temp = head; //判断链表是否为空 if (isEmpty()) { return; } //遍历链表 while (true) { //若是链表到底了 if (temp.next == null) { return; } //若是找到了待删除节点的前一个节点 if (temp.next.num == num) { //判断待删除节点是否为尾节点 if (temp.next.next == null){ temp.next = null; }else { temp.next = temp.next.next; } return; } //继续遍历下一节点 temp = temp.next; } } }
/** * @Author:黄成兴 * @Date:2020-07-04 11:36 * @Description:哈希表 */ public class HashTable { //数组长度 private int size; //用于存放数据的数组 private SingleLinkList[] arr; public HashTable(int size) { this.size = size; //初始化数组 arr = new SingleLinkList[size]; //初始化链表 for (int i = 0; i < size; i++) { arr[i] = new SingleLinkList(); } } /** * 获取哈希值 * @param item * @return */ public int getHashCode(int item) { return item % 2; } /** * 插入元素 * @param item */ public void insert(int item) { //获取哈希值 int hashCode = getHashCode(item); //判断哈希值是否超过数组范围 if (hashCode >= size || hashCode < 0) { throw new RuntimeException("哈希值:" + hashCode + "超出初始化长度!"); } //若是该元素在链表中不存在就插入 if (arr[hashCode].isEmpty() || arr[hashCode].get(item) == null) { //插入元素 arr[hashCode].add(new Node(item)); }else { //不然就更新 arr[hashCode].update(new Node(item)); } } /** * 查找元素 * @param item */ public Node get(int item) { //获取哈希值 int hashCode = getHashCode(item); //判断哈希值是否超过数组范围 if (hashCode >= size || hashCode < 0) { return null; } //查找元素 return arr[hashCode].get(item); } /** * 删除元素 * @param item */ public void delete(int item) { //获取哈希值 int hashCode = getHashCode(item); //删除元素 arr[hashCode].delete(item); } /** * 展现某个哈希值对应链表的所有数据 * @param item */ public void show(int item) { //获取哈希值 int hashCode = getHashCode(item); arr[hashCode].show(); } /** * 展现哈希表的全部数据 */ public void showAll() { for (int i = 0; i < arr.length; i++) { //只展现非空链表 if (!arr[i].isEmpty()) { System.out.println("第"+i+"条链表:"); arr[i].show(); } } } }