[數據結構]哈希表

目錄

1、哈希表

1.1、概念

1.2、沖突

2、哈希函數設計

3、負載因子調節

4、閉散列

5、開散列/哈希桶(重點掌握)

6、實現哈希桶

6.1、put方法

6.2、HashMap的擴容機制

6.3、get方法

7、HashMap

8、HashSet

8.1、哈希表性能分析

9、hashcode

9.1、hashCode和equals的區別

10、實現哈希桶


1、哈希表

1.1、概念

順序結構以及平衡樹中,元素關鍵碼與其存儲位置之間沒有對應的關系,因此在查找一個元素時,必須要經過關鍵碼的多次比較。

順序查找時間復雜度為O(N),平衡樹中為樹的高度,即O(logN),搜索的效率取決于搜索過程中元素的比較次數。
理想的搜索方法:可以不經過任何比較,一次直接從表中得到要搜索的元素。 如果構造一種存儲結構,通過某種函數(hashFunc)使元素的存儲位置與它的關鍵碼之間能夠建立一一映射的關系,那么在查找時通過該函數可以很快找到該元素。


當向該結構中:
插入元素:根據待插入元素的關鍵碼,以此函數計算出該元素的存儲位置并按此位置進行存放
搜索元素:對元素的關鍵碼進行同樣的計算,把求得的函數值當做元素的存儲位置,在結構中按此位置取元素比較,若關鍵碼相等,則搜索成功
該方式即為哈希(散列)方法

哈希方法中使用的轉換函數稱為哈希(散列)函數

構造出來的結構稱為哈希表(HashTable)(或者稱散列表)

按照上述哈希方式,向集合中插入元素14,會出現沖突的現象,這種現象稱為哈希沖突

1.2、沖突

概念:

哈希沖突/哈希碰撞:不同的關鍵字KEY 通過相同的哈希函數計算出相同的哈希地址


避免:

由于哈希表底層數組的容量往往是小于實際要存儲的關鍵字的數量的,這就導致一 個問題,沖突的發生是必然的,但我們能做的應該是盡量的降低沖突率


解決哈希沖突兩種常見的方法是:閉散列和開散列

2、哈希函數設計

引起哈希沖突的一個原因可能是:哈希函數設計不夠合理。 哈希函數設計原則:

  • 哈希函數的定義域必須包括需要存儲的全部關鍵碼,而如果散列表允許有m個地址時,其值域必須在0到m-1之間
  • 哈希函數計算出來的地址能均勻分布在整個空間中
  • 哈希函數應該比較簡單

常見哈希函數
1. 直接定址法--(常用)
取關鍵字的某個線性函數為散列地址:Hash(Key)= A*Key + B

優點:簡單、均勻

缺點:需要事先知道關鍵字的分布情況

使用場景:適合查找比較小且連續的情況

????????例如:key : 91

????????int index = key - minVal????????minVal是一組數據中最小的數據
2. 除留余數法--(常用)
設散列表中允許的地址數為m,取一個不大于m,但最接近或者等于m的質數p作為除數,按照哈希函數:Hash(key) = key% p(p<=m),將關鍵碼轉換成哈希地址
3. 平方取中法--(了解)
假設關鍵字為1234,對它平方就是1522756,抽取中間的3位227作為哈希地址; 再比如關鍵字為4321,對它平方就是18671041,抽取中間的3位671(或710)作為哈希地址 平方取中法比較適合:不知道關鍵字的分布,而位數又不是很大的情況
4. 折疊法--(了解)

5. 隨機數法--(了解)

6. 數學分析法--(了解)

注意:哈希函數設計的越精妙,產生哈希沖突的可能性就越低,但是無法避免哈希沖突

3、負載因子調節

散列表的負載因子定義為:α = 填入表中的元素個數 / 散列表的長度

α 是散列表裝滿程度的標志因子。由于表長是定值,α 與“填入表中的元素個數”成正比,所以,α 越大,表明填入表中的元素越多,產生沖突的可能性就越大;反之,α 越小,表明填入表中的元素越少,產生沖突的可能性就越小。

Java的系統庫限制了負載因子為0.75,超過此值將使用擴容的方式重構散列表,將負載因子控制在 0.75 內

所以當沖突率達到一個無法忍受的程度時,我們需要通過降低負載因子來變相的降低沖突率。 已知哈希表中已有的關鍵字個數是不可變的,那我們能調整的就只有哈希表中的數組的大小

4、閉散列

閉散列:也叫開放定址法,當發生哈希沖突時,如果哈希表未被裝滿,說明在哈希表中必然還有空位置,那么可以把key存放到沖突位置中的 “下一個” 空位置中;那如何尋找下一個空位置呢?

1. 線性探測

比如上面的場景,現在需要插入元素14,先通過哈希函數計算哈希地址,下標為4,因此44理論上應該插在該 位置,但是該位置已經放了值為4的元素,即發生哈希沖突。

線性探測:從發生沖突的位置開始,依次向后探測,直到尋找到下一個空位置為止。

插入:

  • 通過哈希函數獲取待插入元素在哈希表中的位置
  • 如果該位置中沒有元素則直接插入新元素,如果該位置中有元素發生哈希沖突,使用線性探測找到下一個空位置,插入新元素
  • 采用閉散列處理哈希沖突時,不能隨便物理刪除哈希表中已有的元素,若直接刪除元素會影響其他元素的搜索。比如刪除元素4,如果直接刪除掉,44查找起來可能會受影響。因此線性探測采用標記的偽刪除法來刪除一個元素

2. 二次探測

二次探測法指采用前后跳躍式探測的方法,發生沖突時,向后1位探測,向前1位探測,向后2^2 位探測,向前2^2 位探測……以跳躍式探測,避免堆積。

二次探測的增量序列為di =1^2 , -1^2 , 2^2 , -2^2 , …, k^2 , -k^2(k ≤m /2),對應哈希函數中的 i = 1,2,3,4, …

研究表明:當表的長度為質數且表裝載因子 α 不超過0.5?時,新的表項一定能夠插入,而且任何一個位置都不會被探查兩次

因此只要表中有一半的空位置,就不會存在表滿的問題。

在搜索時可以不考慮表裝滿的情況,但在插入時必須確保表的裝載因子a不超過0.5,如果超出必須考慮增容。

因此:閉散列最大的缺陷就是空間利用率比較低,這也是哈希的缺陷

5、開散列/哈希桶(重點掌握)

開散列法又叫鏈地址法(開鏈法)

核心思想:將所有元素存儲在一個鏈表中,每個鏈表的頭指針存儲在哈希表的相應槽位中。當發生哈希沖突時,新的元素會被添加到對應鏈表的末尾,哈希表的每個元素就是各鏈表的頭節點

插入鏈表的時,JDK1.7及以前采用的是頭插法,1.8 開始采用尾插法

從上圖可以看出,開散列中每個桶中放的都是發生哈希沖突的元素。

開散列,可以認為是把一個在大集合中的搜索問題轉化為在小集合中做搜索了

如果沖突嚴重,就意味著小集合的搜索性能其實也時不佳的,這個時候我們就可以將這個所謂的小集合搜索問題繼續進行轉化,例如:
1. 每個桶的背后是另一個哈希表
2. 每個桶的背后改變成一棵搜索樹,比如說變成紅黑樹


Java的HashMap就是就是采用開散列的方式來解決哈希沖突的

數組+鏈表+紅黑樹(當數組長度 >= 64 && 鏈表長度 >=8 以后,就會把鏈表變成紅黑樹)

6、實現哈希桶

6.1、put方法

面試問題:

1.上圖的這種擴容方式可不可行

答:不可以,因為數組長度變了,原來位置沖突的元素可能要放到新的數組的其他位置去

2. 擴容需要注意的是什么?

答:HashMap 擴容時,原來 HashMap 中數組下的每一個結點都要重新計算它在新數組中的位置(重新哈希)

6.2、HashMap的擴容機制

為什么需要擴容?

????????隨著哈希表中存儲的元素不斷增加,哈希沖突的概率也會越來越高,這會導致哈希表的性能下降,查找、插入和刪除操作的時間復雜度可能會接近O(n)。為了保證哈希表的性能,當哈希表中的元素數量達到一定閾值時,就需要對哈希表進行擴容。


擴容的觸發條件:

在 HashMap 中,有兩個重要的參數:容量(capacity)和負載因子(loadFactor)。容量指的是哈希表中數組的長度,負載因子是一個介于 0 到 1 之間的浮點數,默認值為 0.75。當哈希表中的負載因子大小超過 0.75 時,就會觸發擴容操作。


擴容的過程:

  • 創建新數組:將原數組的容量擴大為原來的 2 倍。
  • 重新哈希:遍歷原數組中的每個元素,重新計算它們在新數組中的位置,并將元素插入到新數組中。

6.3、get方法

7、HashMap

? ? HashMap<String,Integer> map = new HashMap<>();
? ? map.put("hello",2);
? ? map.put("abcd",10);
? ? map.put("zhang",3);
????Integer val = map.get("abcd");
? ? System.out.println(val); // 10


? ? // 不是按順序輸出的,因為HashMap底層是哈希桶,是通過某種哈希函數確定的位置的

? ? System.out.println(map);


? ? // 遍歷方式

? ? for(Map.Entry<String,Integer> entry :map.entrySet()){
? ? ? ? ?System.out.println("key: "+entry.getKey()+" val: "+entry.getValue());
????}


Map不支持迭代器遍歷,因為支持迭代器遍歷的都實現了 Iterable 接口,HashMap和TreeMap 都沒有實現 Iterable 接口;要想用迭代器遍歷,只能將Map轉換為Set,再通過迭代器遍歷Set


Map的一個注意事項:HashMap的key可以是對象,也可以是null,TreeMap則不行,因為TreeMap的key必須是可以比較的

? ? ?HashMap<Student,Integer> map2 = new HashMap<>();
? ? ?map2.put(new Student(),2); // hashcode
? ? ?map2.put(new Student(),2);?

? ? ?map2.put(null,2);

? ? ?// 上述代碼均能通過

8、HashSet

1. Set不能存儲相同的key,天然可以去重的

????????HashSet<String> set = new HashSet<>();
????????set.add("hello");
????????set.add("abcd");
????????set.add("hello");

????????System.out.println(set); // 打印結果:[hello, abcd]

2. 不是按順序輸出的,因為HashSet底層是哈希桶,是通過某種哈希函數確定的位置的

3.?底層是一個HashMap 每次存儲元素時,默認的value是一個Objcet對象

8.1、哈希表性能分析

雖然哈希表一直在和沖突做斗爭,但在實際使用過程中,我們認為哈希表的沖突率是不高的,沖突個數是可控的, 也就是每個桶中的鏈表的長度是一個常數,所以,通常意義下,我們認為哈希表的插入/刪除/查找時間復雜度是 O(1) 。

9、hashcode

通過上述可以知道,HashMap的key可以是對象,也可以是null,使用哈希函數的key一定得轉化為整數,那么如何把對象轉化為整數呢?? -- 使用hashcode方法可以把對象轉化為整數

hashcode方法是Object類中的方法,所以可以直接調用

兩個相同id的學生,正常情況下在HashMap中在同一個位置,但上圖的運行結果得到的整數是不一樣的;想要得到正確的結果,需要重寫hashcode方法

在IDEA中直接生成,會根據id生成重寫的 equals() 和 hashCode() 方法

重寫后,再運行相同id的學生會輸出相同的整數,這樣再通過哈希之后就會在相同的位置

使用HashMap可以用關鍵字student2拿到student1的值,說明這里的HashMap調用的是自己重寫后的hashcode,如果沒有重寫則找不到

所以,當以后在寫一個自定義類型的時候,最好把equals() 和 hashCode() 方法都進行重寫,這樣就不會出現一些意料之外的事情;拓展:建議把toString也寫一下

9.1hashCode和equals的區別

1. 如果兩個對象相等,那么它們的hashCode()值一定相同。這里的相等是指,通過equals()比較兩個對象時返回true。
2. 如果兩個對象hashCode()相等,它們不一定相等。因為在散列表中,hashCode()相等,即兩個鍵值對的哈希值相等。然而哈希值相等,并不一定能得出鍵值對相等,此時就出現所謂的哈希沖突場景。

3.?hashCode和equals的最大區別就是:hashCode 用于找到關鍵字在哈希表中的位置,這個位置的關鍵字的hashCode都是相同的;找到位置后,從該位置下的所有關鍵字中找到某個關鍵字用 equals

總結:equals方法用于判斷兩個對象是否相等,hashCode方法用于生成對象的哈希碼。equals方法要求兩個對象相等時hashCode值也相等,而hashCode方法要求hashCode值相等時兩個對象不一定是相等的

10、實現哈希桶<K,V>

public class HashBuck2<K,V> {static class Node<K,V> {public K key;public V val;public Node<K,V> next;public Node(K key,V val) {this.key = key;this.val = val;}}public Node<K,V>[] array;public int usedSize;public static final float DEFAULT_LOAD_FACTOR = 0.75f;public HashBuck2() {array = (Node<K,V>[])new Node[10];}public void put(K key,V val) {int hash = key.hashCode();int index = hash % array.length;Node<K,V> cur = array[index];while (cur != null) {if(cur.key.equals(key)) {//更新valuecur.val = val;return;}cur = cur.next;}Node<K,V> node = new Node(key,val);node.next = array[index];array[index] = node;usedSize++;}public V getValue(K key) {int hash = key.hashCode();int index = hash % array.length;Node<K,V> cur = array[index];while (cur != null) {if(cur.key.equals(key)) {return cur.val;}cur = cur.next;}return null;}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/901837.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/901837.shtml
英文地址,請注明出處:http://en.pswp.cn/news/901837.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

VS-Code創建Vue3項目

1 創建工程文件 創建一個做工程項目的文件夾 如&#xff1a;h5vue 2 cmd 進入文件 h5vue 3 輸入如下命令 npm create vuelatest 也可以輸入 npm create vitelatest 4 輸入項目名稱 項目名稱&#xff1a;自已輸入 回車 可以按鍵盤 a (全選) 回車&#xff1a; Playwright…

linux休眠喚醒流程

1、框架 2、休眠流程 應用層通過echo mem > /sys/power/state寫入休眠狀態&#xff0c;給一張大概流程圖 這個操作對應在kernel/power/main.c的state這個attr的store操作 static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr, …

Mysql--基礎知識點--93--兩階段提交

1 兩階段提交 以update語句的具體執行過程為例&#xff1a; 具體更新一條記錄 UPDATE t_user SET name ‘xiaolin’ WHERE id 1;的流程如下&#xff1a; 1.執行器負責具體執行&#xff0c;會調用存儲引擎的接口&#xff0c;通過主鍵索引樹搜索獲取 id 1 這一行記錄&#…

Windows 環境下 Apache 配置 WebSocket 支持

目錄 前言1. 基本知識2. 實戰前言 ?? 找工作,來萬碼優才:?? #小程序://萬碼優才/r6rqmzDaXpYkJZF 爬蟲神器,無代碼爬取,就來:bright.cn 原先寫過apache的http配置:Apache httpd-vhosts.conf 配置詳解(附Demo) 1. 基本知識 ?? WebSocket 是 HTTP 的升級協議 客戶…

UMAEA論文閱讀

Preliminaries MMKG為一個五元組G{E, R, A, V, T}&#xff0c;其中E、R、A和V分別表示實體集、關系集、屬性集和圖像集。 T?ERE是關系三元組集。 給定兩個MMKG G1 {E1, R1, A1, V1, T1} 和 G2 {E2, R2, A2, V2, T2}&#xff0c; MMEA旨在識別每個實體對&#xff08;e1…

AIGC-十款知識付費類智能體完整指令直接用(DeepSeek,豆包,千問,Kimi,GPT)

Unity3D特效百例案例項目實戰源碼Android-Unity實戰問題匯總游戲腳本-輔助自動化Android控件全解手冊再戰Android系列Scratch編程案例軟考全系列Unity3D學習專欄藍橋系列AIGC(GPT、DeepSeek、豆包、千問、Kimi)??關于作者 專注于Android/Unity和各種游戲開發技巧,以及各種資…

Qt界面卡住變慢的解決方法

本質原因: 當Qt界面出現卡頓或無響應時&#xff0c;通常是因為主線程&#xff08;GUI線程&#xff09;被耗時操作阻塞。 完全忘了。。。 Qt Creater解決方法 1. 定位耗時操作 目標&#xff1a;找到阻塞主線程的代碼段。 方法&#xff1a; 使用QElapsedTimer測量代碼執行時間…

【LangChain4j快速入門】5分鐘用Java玩轉GPT-4o-mini,Spring Boot整合實戰!| 附源碼

【LangChain4j快速入門】5分鐘用Java玩轉GPT-4o-mini&#xff0c;Spring Boot整合實戰&#xff01; 前言&#xff1a;當Java遇上大模型 在AI浪潮席卷全球的今天&#xff0c;Java開發者如何快速擁抱大語言模型&#xff1f;LangChain4j作為專為Java打造的AI開發框架&#xff0c…

Vue 3 reactive 和 ref 區別及 失去響應性問題

在 Vue 3 中&#xff0c;reactive 和 ref 是實現響應式數據的兩個核心 API&#xff0c;它們的設計目標和使用場景有所不同。以下是兩者的詳細對比&#xff1a; 1. 基本定義與核心功能 特性reactiveref作用創建對象類型的響應式代理&#xff08;對象、數組、Map 等&#xff09…

第一節:Vben Admin 最新 v5.0初體驗

系列文章目錄 基礎篇 第一節&#xff1a;Vben Admin介紹和初次運行 第二節&#xff1a;Vben Admin 登錄邏輯梳理和對接后端準備 第三節&#xff1a;Vben Admin登錄對接后端login接口 第四節&#xff1a;Vben Admin登錄對接后端getUserInfo接口 第五節&#xff1a;Vben Admin權…

Nginx部署spa單頁面的小bug

沒部署過&#xff0c;都是給后端干的&#xff0c;自己嘗試部署了一個下午終于成功了 我遇到的最大的bug是進入后只有首頁正常顯示 其他頁面全是404&#xff0c;于是問問問才知道&#xff0c;需要這個 location / { try_files $uri $uri/ /index.html; } 讓…

面試算法高頻08-動態規劃-01

動態規劃 遞歸知識要點 遞歸代碼模板&#xff1a;提供遞歸代碼的標準形式public void recur(int level, int param) &#xff0c;包含終止條件&#xff08;if (level> MAX_LEVEL)&#xff09;、當前層邏輯處理&#xff08;process(level, param)&#xff09;、向下一層遞歸…

若依框架前后端分離版部署全流程詳解(本地+服務器+高級配置)

若依框架前后端分離版部署全流程詳解&#xff08;本地服務器高級配置&#xff09; 若依&#xff08;RuoYi&#xff09;作為一款基于SpringBoot和Vue的權限管理系統&#xff0c;憑借其模塊化設計和開箱即用的特性廣受開發者歡迎。本文將從本地部署、服務器部署、高級配置三個維…

醫療設備預測性維護合規架構:從法規遵循到技術實現的深度解析

在醫療行業數字化轉型加速推進的當下&#xff0c;醫療設備預測性維護已成為提升設備可用性、保障醫療安全的核心技術。然而&#xff0c;該技術的有效落地必須建立在嚴格的合規框架之上。醫療設備直接關乎患者生命健康&#xff0c;其維護過程涉及醫療法規、數據安全、質量管控等…

LLMs基礎學習(七)DeepSeek專題(4)

LLMs基礎學習&#xff08;七&#xff09;DeepSeek專題&#xff08;4&#xff09; 文章目錄 LLMs基礎學習&#xff08;七&#xff09;DeepSeek專題&#xff08;4&#xff09;DeepSeek-R1 訓練過程的四個階段具體流程小結 “規則化獎勵”具體原因小結 “自我認知”&#xff08;se…

SQL 速查手冊

前言&#xff1a;SQL&#xff08;Structured Query Language&#xff09;是用于管理關系型數據庫的標準語言&#xff0c;廣泛應用于數據查詢、更新、定義和管理等操作。本文將為你提供一份詳細的 SQL 速查手冊&#xff0c;涵蓋從基礎到高級的各種 SQL 操作&#xff0c;幫助你快…

IDEA 中 Scala 項目遠程連接虛擬機 Spark 環境

IDEA 中 Scala 項目遠程連接虛擬機 Spark 環境 1. 環境準備 確保虛擬機 Spark 環境正常運行 虛擬機中已安裝并啟動 Spark記錄虛擬機的 IP 地址和 Spark 端口&#xff08;默認 7077&#xff09;確保虛擬機防火墻允許相關端口訪問 本地 IDEA 環境配置 安裝 Scala 插件安裝 Spar…

.net core 項目快速接入Coze智能體-開箱即用-全局說明

目錄 一、Coze智能體的核心價值 二、開箱即用-效果如下 三 流程與交互設計 為什么要分析意圖&#xff0c;而不是全部交由AI處理。 四 接入前的準備工作 五&#xff1a;代碼實現----字節Coze 簽署 JWT和獲取Token .net core 項目快速接入Coze智能體-開箱即用 .net core快…

網店運營精細化突破新路徑

內容概要 電商戰場越來越卷&#xff0c;單純靠低價和流量轟炸已經玩不轉了。今天想要站穩腳跟&#xff0c;精細化運營才是破局密碼——從商品怎么選、用戶怎么留&#xff0c;到供應鏈怎么跑得更快&#xff0c;每個環節都得摳細節。比如用數據給選品“開天眼”&#xff0c;把用…

數據結構學習筆記 :線性表的鏈式存儲詳解

目錄 單鏈表 1.1 無頭單鏈表 1.2 有頭單鏈表單向循環鏈表雙鏈表 3.1 雙鏈表 3.2 雙向循環鏈表總結與對比 一、單鏈表 1. 無頭單鏈表&#xff08;Headless Singly Linked List&#xff09; 定義&#xff1a;鏈表無頭結點&#xff0c;直接由頭指針指向第一個數據節點。 特點&…