查找演算法總結(二)散列表

来源:http://www.cnblogs.com/xiangkejin/archive/2017/05/21/6885857.html
-Advertisement-
Play Games

時間複雜度上,紅黑樹在平均情況下插入,查找以及刪除上都達到了lgN的時間複雜度。 那麼有沒有查找效率更高的數據結構呢,答案就是本文接下來要介紹了散列表,也叫哈希表(Hash Table) 什麼是哈希表 哈希表就是一種以 鍵-值(key-indexed) 存儲數據的結構,我們只要輸入待查找的值即key ...


時間複雜度上,紅黑樹在平均情況下插入,查找以及刪除上都達到了lgN的時間複雜度。

那麼有沒有查找效率更高的數據結構呢,答案就是本文接下來要介紹了散列表,也叫哈希表(Hash Table)

什麼是哈希表

哈希表就是一種以 鍵-值(key-indexed) 存儲數據的結構,我們只要輸入待查找的值即key,即可查找到其對應的值。

哈希的思路很簡單,如果所有的鍵都是整數,那麼就可以使用一個簡單的無序數組來實現:將鍵作為索引,值即為其對應的值,這樣就可以快速訪問任意鍵的值。這是對於簡單的鍵的情況,我們將其擴展到可以處理更加複雜的類型的鍵。

使用哈希查找有兩個步驟:

  1. 使用哈希函數將被查找的鍵轉換為數組的索引。在理想的情況下,不同的鍵會被轉換為不同的索引值,但是在有些情況下我們需要處理多個鍵被哈希到同一個索引值的情況。所以哈希查找的第二個步驟就是處理衝突
  2. 處理哈希碰撞衝突。有很多處理哈希碰撞衝突的方法,本文後面會介紹拉鏈法和線性探測法。

哈希表是一個在時間和空間上做出權衡的經典例子。如果沒有記憶體限制,那麼可以直接將鍵作為數組的索引。那麼所有的查找時間複雜度為O(1);如果沒有時間限制,那麼我們可以使用無序數組併進行順序查找,這樣只需要很少的記憶體。哈希表使用了適度的時間和空間來在這兩個極端之間找到了平衡。只需要調整哈希函數演算法即可在時間和空間上做出取捨。

哈希函數

哈希查找第一步就是使用哈希函數將鍵映射成索引。這種映射函數就是哈希函數。如果我們有一個保存0-M數組,那麼我們就需要一個能夠將任意鍵轉換為該數組範圍內的索引(0~M-1)的哈希函數。哈希函數需要易於計算並且能夠均勻分佈所有鍵。比如舉個簡單的例子,使用手機號碼後三位就比前三位作為key更好,因為前三位手機號碼的重覆率很高。再比如使用身份證號碼出生年月位數要比使用前幾位數要更好。

在實際中,我們的鍵並不都是數字,有可能是字元串,還有可能是幾個值的組合等,所以我們需要實現自己的哈希函數。

1. 正整數

獲取正整數哈希值最常用的方法是使用除留餘數法。即對於大小為素數M的數組,對於任意正整數k,計算k除以M的餘數。M一般取素數。

2. 字元串

將字元串作為鍵的時候,我們也可以將他作為一個大的整數,採用再採用保留除餘法。我們可以將組成字元串的每一個字元取值然後進行哈希,比如

public int GetHashCode(string str)
{
    char[] s = str.ToCharArray();
    int hash = 0;
    for (int i = 0; i < s.Length; i++)
    {
        hash = s[i] + (31 * hash); 
    }
    return hash;
}
java中String的預設實現就是類似於此。

上面的哈希值是Horner計算字元串哈希值的方法,公式為:

   h = s[0] · 31L–1 + … + s[L – 3] · 312 + s[L – 2] · 311 + s[L – 1] · 310

舉個例子,比如要獲取”call”的哈希值,字元串c對應的unicode為99,a對應的unicode為97,L對應的unicode為108,所以字元串”call”的哈希值為 3045982 = 99·313 + 97·312 + 108·311 + 108·31= 108 + 31· (108 + 31 · (97 + 31 · (99)))

如果對每個字元去哈希值可能會比較耗時,所以可以通過間隔取N個字元來獲取哈西值來節省時間,比如,可以 獲取每8-9個字元來獲取哈希值:

public int GetHashCode(string str)
{
    char[] s = str.ToCharArray();
    int hash = 0;
    int skip = Math.Max(1, s.Length / 8);
    for (int i = 0; i < s.Length; i+=skip)
    {
        hash = s[i] + (31 * hash);
    }
    return hash;
}

但是,對於某些情況,不同的字元串會產生相同的哈希值,這就是前面說到的哈希衝突(Hash Collisions),比如下麵的四個字元串:

hash code collision

如果我們按照每8個字元取哈希的話,就會得到一樣的哈希值。所以下麵來講解如何解決哈希碰撞:

避免哈希衝突

拉鏈法 

通過哈希函數,我們可以將鍵轉換為數組的索引(0-M-1),但是對於兩個或者多個鍵具有相同索引值的情況,我們需要有一種方法來處理這種衝突。

一種比較直接的辦法就是,將大小為M 的數組的每一個元素指向一個條鏈表,鏈表中的每一個節點都存儲散列值為該索引的鍵值對,這就是拉鏈法。

基於拉鏈法的散列表的實現簡單,在鍵的順序並不重要的應用中,它可能是最塊的(也是使用最廣泛的)符號表實現。

 

線性探測法

線性探測法是開放定址法解決哈希衝突的一種方法,基本原理為,使用大小為M的數組來保存N個鍵值對,其中M>N,我們需要使用數組中的空位解決碰撞衝突。

開放定址法中最簡單的是線性探測法:當碰撞發生時即一個鍵的散列值被另外一個鍵占用時,直接檢查散列表中的下一個位置即將索引值加1,這樣的線性探測會出現三種結果:

  1. 命中,該位置的鍵和被查找的鍵相同
  2. 未命中,鍵為空
  3. 繼續查找,該位置和鍵被查找的鍵不同。

空元素(記為null)可以作為查找結束的標誌。

和拉鏈法一樣,開放地址類的三列表的性能也依賴於a=N/M的比值,我們稱為散列表的使用率。

對於拉鏈法,a是每條鏈的長度,因此一般大於1;對於基於先行探測的散列表,a是表中已被占用的空間比例,它是不可能大於1的,

為保證性能。我們動態調整數組的大小來保證使用率在1/8和1/2之間。


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

-Advertisement-
Play Games
更多相關文章
  • Linux環境用g++編譯GDAL動態庫的詳細步驟和一些問題 ...
  • ConfigParser模塊,hashlib模塊,hmac模塊: 創建配置文件: 查看: 修改,添加,刪除: hashlib模塊: 加密類型:MD5,SHA1,SHA224,SHA256,SHA384,SHA512 hmac模塊: ...
  • 上篇用了單工程創建了SSM整合的web工程(http://www.cnblogs.com/yuanjava/p/6748956.html),這次我們把上篇的單工程改造成為多模塊工程 一:創建對應的多工程 首先原工程有對應的包如下 因為原單工程是 contoller 調用 service ,servi ...
  • 作業:計算器開發 (1)實現加減乘除及拓號優先順序解析; (2)用戶輸入 1 - 2 * ( (60-30 +(-40/5) * (-9-2*5/-3 + 7 /3*99/4*2998 +10 * 568/14 )) - (-4*3)/ (16-3*2) )等類似公式後,必須自己解析裡面的(),+,- ...
  • 有這麼一個有趣的問題,問: 有這麼一個不重覆的自然數數組,自然數長度為N,而數組長度為N 2,依次隨機把自然數放進數組中,請找出2個沒有被放進去的自然數。 例如:這個自然數數組是[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]這十個數,某次隨機放入的順序是[2, 1, 3, 5, 7, ...
  • 【題目描述】 小南有一套可愛的玩具小人,它們各有不同的職業。有一天,這些玩具小人把小南的眼鏡藏了起來。小南發現玩具小人們圍成了一個圈,它們有的面朝國內,有的面朝圈外。如下圖: 這時singer告訴小南一個謎題:“眼鏡藏在我左數第3個玩具小人的右數第1個玩具小人的左數第2個玩具小人那裡。” 小南發現, ...
  • 1.類的載入過程 JVM將類載入過程分為三個步驟:裝載(Load),鏈接(Link)和初始化(Initialize)鏈接又分為三個步驟,如下圖所示: 1) 裝載:查找並載入類的二進位數據; 2)鏈接: 驗證:確保被載入類的正確性; 準備:為類的靜態變數分配記憶體,並將其初始化為預設值; 解析:把類中的 ...
  • 三個月就這麼悄悄溜走了,本K對於前端雖然有了一定的認識,但對一些方面還是處於一種比較萌幣的狀態,就在這種萌幣狀態下,本K又跟著大神浩開始了後臺語言—PHP語言的學習。PHP的學習對於學過其他語言的人來說,是非常easy的(原因後續會提及),K在初次接觸的時候也就僅僅是對一些PHP的寫法有點膈應而已. ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...