哈希表(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(); } } } }