數據結構---哈希表(散列表)

来源:https://www.cnblogs.com/zhuweiheng/archive/2018/01/05/8207255.html
-Advertisement-
Play Games

我們在Java容器中談到:有哈希表(也稱為散列表)支持的HashMap、LinkedHashSet等都具有非常高的查詢效率。這其中就是Hash起的作用。順序查找的時間複雜度為O(N) ,二分查找和查找樹的時間複雜度為O(logN),而 哈希表的時間複雜度為O(1) 。不過這隻是理想狀態,實際並不那麼 ...


我們在Java容器中談到:有哈希表(也稱為散列表)支持的HashMapLinkedHashSet等都具有非常高的查詢效率。這其中就是Hash起的作用。順序查找的時間複雜度為O(N) ,二分查找查找樹的時間複雜度為O(logN),而 哈希表的時間複雜度為O(1) 。不過這隻是理想狀態,實際並不那麼完美。


1.哈希表的概念和思想 

哈希表是唯一的專用於集合的數據結構。可以以常量的平均時間實現插入、刪除和查找。

哈希表的思想是用一個與集合規模差不多大的數組來存儲這個集合,將數據元素的關鍵字映射到數組的下標,這個映射稱為“散列函數”,數組稱為“散列表”。查找時,根據被查找的關鍵字找到存儲數據元素的地址,從而獲取數據元素。 image

由於任何類型的數據都能轉化為一個整數型,我們假設所有的數據元素的關鍵字都是較小的非負整數,就可以用一個簡單的數組Data保存這個集合,數據類型就是集合中的元素的類型。

開始時,給數組的每個元素賦空值,NULL。當要在集合中插入一個關鍵字為Key的數據元素時,就檢查Data[Key]是否為空。如果為空,則吧插入的元素存儲在Data[Key]中;如果不為空,則表示遇到重覆元素,插入失敗。

當要查找某個數據元素時,只需要根據查找的關鍵字Key直接取出Data[Key]的值,如果Data[Key]的值為空,則表示關鍵字對應的元素不存在。否則,Data[Key]就是要查找的元素。刪除時,只需要將對應數組的元素設為NULL即可。每種操作的時間為一個常量。

散列函數函數的應用帶來一個複雜的問題:

因為散列函數的定義域範圍比值域大,兩個或更多的數據元素可能被映射到同一個位置,稱為“衝突或碰撞”。這種情況是不可避免的。因此,實現散列表的兩個最基本的問題是如何設計散列函數,如何解決碰撞


2.散列函數

在散列表中。插入、刪除和查找都會用到散列函數。散列函數的計算速度直接影響散列表的性能。好的散列函數的一個標準就是:計算速度快。另一點就是:結點的散列地址儘可能均勻。使得衝突的機會儘可能少

常用的散列函數包括直接定址法、保留餘數法、數字分析法、平方取中法和摺疊法等。

(1)直接定址法

直接取關鍵字的值或關鍵字的某個線性函數的值作為散列地址。設關鍵字為x那麼散列地址可表示為:

                                                               H(x) = x 或  H(x) = ax + b (a、b為常數)

例如:關鍵字集合為{100, 400,600, 200, 800, 900},利用直接定址法,若選取散列函數為H(x) = x/100,則散列表如下:

image

(2)保留餘數法

如果M是散列表的大小,關鍵字 x 的數據元素的散列地址為:H(x) = x mod M

在保留餘數法中,選取合適的餘數M很重要,如果選取不當,則導致大量的碰撞。

經驗表明:M為素數(除了1和它本身以外不再有其他因數。)時,散列表的分佈比較均勻。

(3)數字分析法

在某些領域,關鍵字之間的區別集中在某些位上面,例如電腦的IP地址,一個IP地址由兩部分組成:網路號和主機號。在同一子網中的主機的網路號是相同的。在某個網路中,我們可以將IP地址作為關鍵字。如果採取散列方法保存這個集合,可以選取IP地址的主機號部分作為存儲地址。

更一般的說,如果在關鍵字集合中,每個關鍵字均由n位組成,分析關鍵字中每一位的分佈規律,並從中提取分佈均勻的若幹位或它們的組合作為地址。

例如:右邊的數據

第一列、第二列、第五列 對於不是區分關鍵字沒有價值。

第三列只有 7 、 8 、9 三種數字。

餘下的幾列比較均勻,可作為散列表的地址選用。

若散列表的地址是三位,直接選取剩下的三位即可;

散列表的地址小於三位。則選擇其他方法,比如:保留餘數法等等

將三位關鍵字映射到一個更小的值域中。

 

image

(4)平方取中法

如果關鍵字中的各位的分佈都比較均勻,但關鍵字的值域比數組的規模大,則可以將關鍵字平方後,取其結果中間各位作為散列函數值。

由於中間各位和每一位數字有關係,因此均勻分佈的可能性較大。

例如:4731 X 4731 = 22 382 361.中間選取幾位,依賴於散列表的單元總數。若散列表中有100個單位,選取中間4,5兩位,即關鍵字

4731的地址為82.

(5)摺疊法

如果關鍵字相當長,以至於和散列表的單元總數相比大的多,則採取摺疊法。如果數字的分佈大體上是均勻的,通常選取一個長度後,將關鍵字按長度分組相加。例如:542 242 241,摺疊後542 + 242 + 241 = 1025,拋棄進位,得到散列表的結果為25.

不存在一種萬能的散列函數,在任何情況下都是出色的。但是大部分情況下,保留餘數法比較好。

實際中選取散列函數需要考慮的因素有

  • 計算散函數所需時間。
  • 關鍵字長度。
  • 散列表長度(散列表地址範圍)。
  • 關鍵字分佈情況。
  • 記錄的查找頻率。

3.碰撞的解決

在選取散列函數時,由於很難選取一個既均勻分佈又簡單,同時保證關鍵字和散列地址一一對應的散列表,所以衝突時不可避免的。如果具有不同關鍵字的 k 個數據元素的散列地址完全相同,就必須為 k-1個數據元素重新分配存儲單元。通常稱其為“溢出”的數據元素。

常用的處理衝突的方法有兩種:

(1)將溢出的數據元素存放到散列表中沒有使用的單元中。

這種方法是“封閉”的,不需要使用額外的存儲單元,稱為“閉散列表”。具體的實施方案有:線性探針法、二次探測發、再散列法。

  • 線性探針法:在數組中從映射到的位置開始順序查找空位置,如果需要,從最後一個位置繞回第一個位置。這種方法碰撞可能引起連鎖反應,使表中形成一些較長的連續被占用的單元,如:從1---10的地址全部被使用。從而使散列表的性能降低。
  • 二次探測發:不直接檢查下一個單元,而是檢查遠離初始探測點的某一單元,以消除線性探測法中的初始聚集的問題.
  • 再散列法:有兩個散列函數H1和H2。H1用來計算探測序列的起始地址,H2用來計算下一個位置的步長。第二個散列函數必須仔細選擇。例如,它永遠不能計算出0,必須保證所有單元能夠探測到。

(2)將映射到同一地址的數據元素分別保存到散列表以外的各自線性表中。

由於地址相同的數據元素個數變化比較大,因此通常採用鏈表的方式。散列表本身只保存一個指向各自鏈表中第一個節點的指針。這種方法稱為“開散列表",或拉鏈法,可以理解為“鏈表的數組”。

開散列表將具有同一散列地址的數據元素都存儲在一個單鏈表中。在散列表中插入、查找或刪除一個元素,就是在對應的單鏈表中進行的。為了方便操作,將單鏈表設計為帶頭結點的單鏈表。

例如;一個規模為11的開散列表中依次插入關鍵字17、21、23、60、29、38......,散列函數為

H(Key)= key mod 11,散列表如下:

在開散列表中,插入、刪除和查找操作的實現都相當簡單。

插入x時,首先計算H(x),將x插入到H(x)指向的單鏈表的表頭。

查找時,也是先計算H(x),然後順序查找H(x)指向的單鏈表。

刪除操作同樣簡單,先計算H(x),然後順序查找H(x)指向的單鏈表,

找到x後刪除

無標題

2018-01-05


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、get方法 1 package lq.httpclient.method; 2 3 import java.io.BufferedReader; 4 import java.io.IOException; 5 import java.io.InputStreamReader; 6 import ...
  • 1.3.Python基本語法 1.3.1 行和縮進 Python中,不使用括弧來表示代碼的類和函數定義塊或流程式控制制。 代碼塊是由行縮進,縮進位的數目是可變的,但是在塊中的所有語句必須縮進相同的量。 如下所示: if True: print "True"[dht1] else: print "Fals ...
  • c語言貪吃蛇詳解4.食物的投放 前幾天的實驗室培訓課後作業我佈置了貪吃蛇,今天有時間就來寫一下題解。我將分幾步來教大家寫一個貪吃蛇小游戲。由於大家c語言未學完,這個教程只涉及數組和函數等知識點。 通過前幾次的教程,我們已經做出來了能上下左右跑的小蛇了。現在我們就先來做下食物投放吧。 食物投放的基本思 ...
  • 中間件是面向切麵編程的好例子,它是一個可以介入Django的request和response處理過程的鉤子框架,一個輕量級、底層的“插件”系統,用於在全局修改Django的輸入或輸出。 要使用中間件,首先要在settings中設置: 上述是Django項目的預設設置,每一項字元串都代表一個中間件。中 ...
  • 本文目錄:1.等待、喚醒機制的原理2.Lock和Condition3.單生產者單消費者模式4.使用Lock和Condition實現單生產單消費模式5.多生產多消費模式(單麵包)6.多生產多消費模式 生產者消費者模式是多線程中最為常見的模式:生產者線程(一個或多個)生成麵包放進籃子里(集合或數組),同 ...
  • 一. 傳統C++ 傳統 C++中,普通數組、沒有構造析構和虛函數的類或結構體都可以使用 {} 進行初始化,也就是我們所說的初始化列表。而對於類對象的初始化,要麼需要通過拷貝構造、要麼就需要使用 () 進行,不支持{}。 int arr[3] = { 1,2,3 }; // 列表初始化class Fo... ...
  • 基於範圍的 for 迴圈 C++11 引入了基於範圍的迭代寫法,我們擁有了能夠寫出像 Python 一樣簡潔的迴圈語句: int array[] = { 1,2,3,4,5 };for (auto &x : array){std::cout arr(5, 100);for (auto &i : ar... ...
  • DBUtils工具包 一.介紹 DBUtils是Apache組織開源的資料庫工具類。 二.使用步驟 ①.創建QueryRunner對象 ②.調用update()方法或者query()方法執行sql語句 三.構造方法及靜態方法 QueryRunner類 1.構造方法 ①.無參構造 QueryRunne ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...