【追求卓越09】算法--散列表(哈希表)

引導

????????通過前面幾個章節的學習(二分查找,跳表),我們發現想要快速查找某一個元素,首先需要將所有元素進行排序,再利用二分法思想進行查找,復雜度是O(logn)。有沒有更快的查找方式呢?

????????本章介紹一下我們工作中經常接觸到的散列表(哈希表)。它能夠使查找的效率達到O(1)。主要是理論方面,讓大家開始了解哈希思想。

散列表

????????提到查找復雜度是O(1),我們在前面接觸到的就是數組了。散列思想就是基于數組支持下標隨機訪問數據特想實現的。那么問題就是如何將元素和數組下標進行映射?

????????例:學校開運動會,有100個運動員,編號是0~99。我要查找86號運動員的信息,該怎么做呢?

????????思路:我們可以建立一個數組,將標號和數組下標一一對應,訪問哪一個標號的運動員,直接查找對應數組下標即可

????????該散列思想中,編號我們稱作為鍵或者關鍵字。將關鍵字轉換為數組下標的,我們稱作為散列函數。而散列函數計算得到的值就叫做散列值(數組下標)。

????????散列思想:散列表用的就是數組支持按照下標隨機訪問。當我們通過散列函數將元素的鍵值映射為數組下標時,然后將數組保存到數組對應位置中。當我們查找都一個元素時,我們使用同一個散列函數,將鍵值轉化為數組下標,從對應的數組下標獲取數據。

散列函數

????????從散列思想中,我們可以知道散列表中散列函數起到很重要的作用。針對不同的問題,散列函數都可能不同。比如上面的例子,我可以設置這樣的哈希函數:

int hash(int key)
{
??? return key;
}
/*
將關鍵字作為下標*/

由于該例子較為簡單,也比較容易想到。但是無論什么想的問題,我覺得散列函數應該盡量做到一下幾點:

  1. 散列函數計算得到的散列值是一個非負整數。因為散列值會作為數組下標。
  2. 如果key1 == key2,那么hash(key1)==hash(key2)。
  3. 如果key1 != key2,那么hash(key1)!=hash(key2)。

其中1,2好理解,但是3實際上是很實現的。因為數組的大小是有限的,但是不同元素可能會有很多。這就導致肯定會存在key1 != key2,hash(key1)==hash(key2)的情況。這種情況我們稱之為散列沖突。再優秀的散列函數也避免不了散列沖突

散列沖突的解決方式

散列沖突的解決方式有兩種:開放尋址法,鏈表法。

開放尋址法

開放尋址法的思想是:出現了散列沖突,我們就重新探測到一個空閑位置,將其插入

如何探測空閑位置?

????????如果某個數據經過散列函數之后,存儲位置已經被占用了,我們就從當前位子開始,依次往后查找,看是否有空閑位子,直到找到為止。

如何查找?

????????通過散列函數求出要查找出的鍵值對應的散列值,然后比較數組中下標為散列值的元素和要查找的元素。如果相等,則說明是我們要找的元素;否則繼續往后查找。如果遍歷到空閑位子,還沒有找到,則說明散列表中沒有要查找的元素。

刪除的注意事項?

????????我們知道散列表有查找操作肯定也有刪除操作。當我們刪除一個元素時,如果將其設置為空閑,那么在查找的過程中就會出現問題(原本在散列表中的元素,會認定為不存在)。我們可以將刪除的元素置為delete狀態。該狀態的位子,可以插入數據。查找的時候,不必停下來,繼續向后查找。

開放尋址法的缺點?

????????從開放尋址法的思想中,我們可以考慮到,當散列表中的元素越來越多的時候,散列沖突可能性越來越大,空閑位置越來越少,線性探測的時間就會越來越久。極端情況下,我們可能需要探測整個散列表,最壞的情況下,時間復雜度是O(n)。

裝載因子

一般情況下,我們會盡量保證散列表中有一定比例的空閑槽位,我們用裝載因子表示空位的多少。

散列表的裝載因子=填入表中的元素個數/散列表的長度

鏈表法

????????鏈表法相對于開發尋址法較為簡單,也容易理解。使用的是指針數組結構。數組元素對應的是一個鏈表,所有散列值相同的元素都在一個鏈表中。如圖:

????????那么鏈表法的插入操作,我們選擇鏈表的頭插,所以時間復雜度是O(1)。

????????但是查找和刪除操作的時間復雜度是O(k),k表示鏈表的長度。理論上每個鏈表的長度應該是均勻的,k=n/m。m是散列表的長度。這就要求散列表的優秀。若散列表不夠好,那么查找和刪除的復雜度可能會退化到O(n)。

如何設計散列函數

????????我們知道散列函數是散列表中重要的一個部分,它的好壞,決定了散列沖突的概率以及散列表的性能。

????????散列函數在設計的時候除了盡量遵循,上章節中指明的三點,還要考慮下面幾點因素:

  1. 散列函數不能設計的太復雜。復雜的散列函數會消耗過多的資源,間接影響性能。
  2. 散列值盡量隨機并且分布均勻。這樣才能降低散列沖突的概率,即使出現散列沖突,每個鏈表長度也比較均勻(針對鏈表法)

????????常見的散列函數的設計方法:數據分析法,直接尋址法,平方取中法,折疊法,隨機數法等。

裝載因子調整

????????我們知道當裝載因子過大的時候,說明散列表中的元素越多,空閑位置越少,散列沖突的概率就會越大。

????????對于沒有頻繁插入和刪除的靜態散列表,我們可以設計出一個完美的散列表。因為數據都是我們提前已知的。

????????但是對于動態的散列表,數據集合是動態變化的。我們無法事先申請一個足夠大的散列表。但是當裝載因子逐漸變大,散列沖突變得無法接受的時候,我們就需要動態擴容。

????????比如當散列表的裝載因子是0.8時,我們將散列表擴容一倍,那么裝載因子就變為了0.4。僅僅將散列表擴容就可以嗎?

????????散列表擴容之后,我們需要將原先的散列表拷貝到新的散列表中。這個過程需要重新進行散列函數計算。

問題:我們知道在擴容的時候,涉及到數據的搬移,這會消耗很多時間。這就導致響應不及時。該如何處理?

解:這種動態擴容,直接將數據進行搬移的方式,非常的耗時,用戶體驗很不好(出現概率較低)。我們可以進行優化,將數據搬移的操作進行分批處理。第一次進行擴容,我們只申請散列表,并將數據插入,不進行數據的搬移。當下次再插入數據時,我們從舊的散列表中拷貝一個到新的散列表(舊的delete)。一直當舊的散列表全部被搬移完。在這個過程中,我們查詢時,需要先舊的散列表中查詢,查詢不到,再到新的散列表中查詢。如果都沒有,說明不存在。這種均攤的方式,將任何情況下,插入操作的時間復雜度是O(1);

選擇散列沖突的解決方案

????????完全通過動態擴容來解決散列沖突是不可能的。從上一節中,我們知道解決散列沖突的方法有兩種,開放地址法和鏈表法。這兩者都項目中都有使用,但是場景不同。

開放地址法

開放地址法,我們知道是將數據都放到數組中,這就能夠利用操作系統的局部性原理(CPU緩存),提高訪問效率。這是它的優點

缺點:

  1. 刪除較為麻煩,需要進行特殊標記
  2. 沖突概率較高
  3. 因為裝載因子不能大于1,故相比較于鏈表法,更加浪費內存空間

總結:開放地址法適用于數據量較小,裝載因子較小的情景

鏈表法

鏈表法的優點:

  1. 內存的利用率較高,可以使用時再申請
  2. 能夠容忍大裝載因子。只要元素均分,即使裝載因子為10,也只是鏈表長度變長了。即使鏈表長度很長,我們也可以通過跳表,紅黑樹等方式,增加查詢效率。

缺點:

  1. 儲存在內存中不連續,對CPU不友好
  2. 需要存儲指針,浪費內存。(對于大的數據對象,也可以忽略)

總結:鏈表法適用于數據量大,裝載因子較大的場景。適合儲存對象比較大

總結

????????本章我們主要介紹一些散列表相關的概念:散列函數,散列沖突,散列值,關鍵值,以及散列沖突的解決方式。

????????散列函數設計的好壞決定了散列沖突的概率,也決定了散列表的性能。想要設計一個好的散列表,需要從散列函數,裝載因子,散列沖突解決方案著手。

????????關于散列函數,我們不能設計的太復雜,并且盡量使散列值均勻分布,這樣盡可能減少散列沖突,即便散列沖突,鏈表長度也較為均勻。

????????關于裝載因子,我們需要設置一個合理的裝載因子上限,并在動態擴容的過程中,需要考慮均分搬移數據

????????關于散列沖突解決方案,有開放地址法和鏈表法。兩者都有適用的情景,那么我們就要針對情況進行選擇。不過鏈表法總體而言較為通用

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

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

相關文章

微軟發布最新.NET 8長期支持版本,云計算、AI應用支持再強化

11 月 15 日開始的為期三天的 .NET Conf 在線活動的開幕日上,.NET 8作為微軟的開源跨平臺開發平臺正式發布。.NET 團隊著重強調云、性能、全棧 Blazor、AI 和 .NET MAUI 是.NET 8的主要亮點。.NET團隊在 .NET Conf 2023 [1]活動開幕式上表示:“通過這個版…

nginx 模塊相關配置及結構理解

文章目錄 模塊配置結構模塊配置指令先看一下 ngx_command_t 結構一個模塊配置的demo簡單模塊配置的案例演示 模塊上下文結構模塊的定義 模塊配置結構 Nginx中每個模塊都會提供一些指令,以便于用戶通過配置去控制該模塊的行為。 Nginx的配置信息分成了幾個作用域(sc…

使用注解的AOP編程

使用注解的AOP編程 當注解沒有參數時 當使用注解進行面向切面編程(AOP)時,你可以按照以下步驟來實現: 步驟: 1. 創建自定義注解: 首先,創建自定義的注解,以便在代碼中標記需要進…

Excel換不了行怎么解決?

方法一: 使用Alt Enter鍵 在Excel中,輸入文字時按下回車鍵,光標將會移到下一個單元格,如果想要換行,可以嘗試使用Alt Enter鍵。具體操作如下: 1.在單元格中輸入文字; 2.想要換行時,在需要換行的位置按下Alt Enter鍵; 3…

延時任務定時發布,基于 Redis 與 DB 實現

目錄 1、什么是延時任務,分別可以使用哪些技術實現? 1.2 使用 Redis 和 DB 相結合的思路圖以及分析 2、實現添加任務、刪除任務、拉取任務 3、實現未來數據的定時更新 4、將數據庫中的任務數據,同步到 Redis 中 1、什么是延時任務&#xff…

網絡運維與網絡安全 學習筆記2023.11.23

網絡運維與網絡安全 學習筆記 第二十四天 今日目標 VRRP負載均衡、BFD原理與配置、BFD典型應用 DHCP工作原理、全局模式DHCP VRRP負載均衡 VRRP單組缺陷 每網段存在一個VRRP組,缺點如下: 主網關數據轉發壓力大 備份網關不轉發任何數據 網絡設備利用…

Hook技術(鉤子技術)

HOOK(鉤子技術) 這里的hook我理解的意思就是通過攔截指令,將指令換成自己想要的指令,從而做道繞過原本的程序指令,要修改這個指令,要用匯編技術,從二進制入手。 擴展: 木馬病毒之…

git clone慢的解決辦法

在網站 https://www.ipaddress.com/ 分別搜索: github.global.ssl.fastly.net github.com 得到ip: 打開hosts文件 sudo vim /etc/hosts 在hosts文件末尾添加 140.82.114.3 github.com 151.101.1.194 github.global-ssl.fastly.net 151.101.65.194 g…

外部網關協議_邊界網關協議BGP

一.邊界網關協議BGP的基本概念 邊界網關協議(Border Gateway Protocol,BGP)屬于外部網關協議EGP這個類別,用于自治系統AS之間的路由選擇協議。由于在不同AS內度量路由的“代價”(距離、帶寬、費用等)可能不同,因此對于…

elasticsearch 7安裝

問題提前報 max virtual memory areas error max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144] 如果您的環境是Linux,注意要做以下操作,否則es可能會啟動失敗 1 用編輯工具打開文件/etc/sysctl.conf 2 …

qml渲染引擎介紹

qml項目啟動入口 Qt Quick項目qml腳本在C++代碼里啟動,main.cpp如下: #include <QGuiApplication> #include <QQmlApplicationEngine>int main(int argc, char *argv[]) {

VUE excel表格導出

js代碼 //下載模板 downloadExl() { // 標題 const tHeader [‘xxx’,xxx,xx名稱,電槍xx,協議xx,snxx]; // key const filterVal [agentName, stationName, equName, channelNumber, manufacturer, sn, ]; // 值 const datas [ { agentName: 你好, stationName: 我們, e…

激光雷達與慣導標定 | Lidar_IMU_Init : 編譯

激光雷達與慣導標定&#xff1a;Lidar_IMU_Init 編譯 功能包安裝安裝ceres-solver-2.0.0 &#xff08;注意安裝2.2.0不行&#xff0c;必須要安裝2.0.0&#xff09; LI-Init是一種魯棒、實時的激光雷達慣性系統初始化方法。該方法可校準激光雷達與IMU之間的時間偏移量和外部參數…

unity shaderGraph實例-可交互瀑布

不要問我水在哪里&#xff0c;你自己相像這是一個瀑布&#xff0c;瀑布的效果我還不會做 效果展示 整體結構 這里片元著色器最后輸出的baseColor應該是黑色&#xff0c;白色為錯誤。 各區域內容 區域1 計算球到瀑布的距離&#xff0c;然后減去一個值&#xff0c;實現黑色區域…

UNETR:用于三維醫學圖像分割的Transformer

論文鏈接&#xff1a;https://arxiv.org/abs/2103.10504 代碼鏈接&#xff1a; https://monai.io/research/unetr 機構&#xff1a;Vanderbilt University, NVIDIA 最近琢磨不出來怎么把3d體數據和文本在cnn中融合&#xff0c;因為確實存在在2d里面用的transformer用在3d里面…

wpf使用CefSharp.OffScreen模擬網頁登錄,并獲取身份cookie,C#后臺執行js

目錄 框架信息&#xff1a;MainWindow.xamlMainWindow.xaml.cs爬取邏輯模擬登錄攔截請求Cookie獲取 CookieVisitorHandle 框架信息&#xff1a; CefSharp.OffScreen.NETCore 119.1.20 MainWindow.xaml <Window x:Class"Wpf_CHZC_Img_Identy_ApiDataGet.MainWindow&qu…

API自動化測試:如何構建高效的測試流程

一、引言 在當前的軟件開發環境中&#xff0c;API&#xff08;Application Programming Interface&#xff09;扮演了極為重要的角色&#xff0c;連接著應用的各個部分。對API進行自動化測試能夠提高測試效率&#xff0c;降低錯誤&#xff0c;確保軟件產品的質量。本文將通過實…

SpringMVC(三)

十、攔截器 1、攔截器的配置 SpringMVC中的攔截器用于攔截控制器方法的執行 SpringMVC中的攔截器需要實現HandlerInterceptor SpringMVC的攔截器必須在SpringMVC的配置文件中進行配置&#xff1a; <bean class"com.atguigu.interceptor.FirstInterceptor">…

constexpt

constexpt constexpt是C11引入的新的關鍵字&#xff0c;它用于在編譯時而非運行時計算函數或變量的值。這個特性對于提高程序效率和優化代非常有用。 編譯時常量和運行時常量 編譯時常量&#xff08;Compile-time Constants&#xff09;和運行時常量&#xff08;Runtime Con…

8年經驗之談 —— 如何使用自動化工具編寫測試用例?

以下為作者觀點&#xff0c;僅供參考&#xff1a; 在快速變化的軟件開發領域&#xff0c;保證應用程序的可靠性和質量至關重要。隨著應用程序復雜性和規模的不斷增加&#xff0c;僅手動測試無法滿足行業需求。 這就是測試自動化發揮作用的地方&#xff0c;它使軟件測試人員能…