哈希表
1、哈希表也叫散列表,依賴了數組下標的隨機訪問能力,實現了更高效的查找
2、哈希函數: 一種存儲結構,通過某種函數使元素的存儲位置與它的關鍵碼之間能夠建立一一對應的一種關係,那麼在查找的時候通過該函數能夠很快找到該元素,這種函數叫做哈希函數,構造出來的表叫做哈希表(散列表)
3、當兩個key不同的元素,經過計算得到的hash值相同,此時就出現了“hash衝突”
4、解決哈希衝突的方法:閉散列,開散列
閉散列
1.核心思路:在衝突位置開始往後找到一個合適的位置來存放這個衝突的值,當前時採用閉散列中的線性探測的方式來進行存放的
2.一旦涉及到hash衝突,此時hash表的基本操作的時間複雜度就不是嚴格的O(1)了,隨着衝突越嚴重,效率就會越低,所以在選擇哈希表長度的時候,一般都要選擇一個比較大的值(如果集合中有100個元素,就最好創建1000個元素的數組)
開散列(哈希桶)
數組爲[96273,273,1273,2273,2274]
哈希表的實現(開散列)
插入
static class Node{
public int key;
public int value;
public Node next;
public Node(int key, int value) {
this.key = key;
this.value = value;
}
}
private static final double LOAD_FACTOR=0.75;
//array就是hash表的本體,數組中的每個元素又是一個鏈表的頭結點
private Node[] array=new Node[101];
private int size=0; //
//如果key已經存在,就修改當前的value值
//如果key不存在,就插入新的鍵值對
public void put(int key,int value){
//1.需要把key映射成數組下標
int index=key%hashFunc(key);
//2.根據下標找到對應的鏈表
Node list=array[index];
//3.當前key在鏈表中是否存在
for(Node cur=list;cur!=null;cur=cur.next){
if(cur.key==key){
//key已經存在,直接修改value即可
cur.value=value;
return;
}
}
//循環結束還沒有找到key,就說明ket不存在,直接插入到鏈表的頭部
Node node=new Node(key,value);
node.next=list;
array[index]=node;
size++;
if(size/array.length>LOAD_FACTOR){
resize();
}
}
查找
//根據key查找指定元素,如果找到了返回對應的value,沒找到返回Null
public Integer get(int key){
//1.將key轉換成對應的下標
int index=hashFunc(key);
//2.根據下標找到對應的鏈表
Node list=array[index];
//3.在鏈表中查找指定元素
for(Node cur=list;cur!=null;cur=cur.next){
if(cur.key==key){
return cur.value;
}
}
return null;
}
擴容
private void resize() {
//擴容操作
Node[] newArray=new Node[array.length*2];
//把原來hash表中的所有元素搬運到新的數組上
for(int i=0;i<array.length;i++){
for(Node cur=array[i];cur!=null;cur=cur.next){
int index=cur.key%newArray.length;
Node newNode=new Node(cur.key,cur.value);
newNode.next=newArray[index];
newArray[index]=newNode;
}
}
//讓新的數組代替原來數組
array=newArray;
}
private int hashFunc(int key) {
return key%array.length;
}
負載因子
1、負載因子:通過這個指標可以衡量元素衝突的概率
2、負載因子=當前hash表中實際元素個數/數組的capacity
1)如果是閉散列:負載因子一定是小於1的
2)如果是開散列:負載因子可以大於1
3、可以根據負載因子的值來決定是否要對hash表進行擴容
所謂擴容:申請一個更大的數組作爲新的hash表,把原來的數組拷貝過去(非常耗時)
哈希表的應用