【數據結構和算法】6. 哈希表

本文根據 數據結構和算法入門 視頻記錄

文章目錄

  • 1. 哈希表的概念
    • 1.1 哈希表的實現方式
    • 1.2 哈希函數(Hash Function)
    • 1.3 哈希表支持的操作
  • 2. Java實現

在前幾章的學習中,我們已經了解了數組和鏈表的基本特性,不管是數組還是鏈表,如果我們想要尋找一個特定的數值,時間復雜度都為O(n)。那有什么辦法用最快的速度來找到一個特定的元素呢,今天我們就來學習工業界中常用的數據結構“哈希表”,在哈希表中,不管是尋找、刪除、增加一個新元素,時間復雜度都是O(1)。

1. 哈希表的概念

哈希表以鍵值的方式來存儲元素,也就是說每個數據都是以鍵值(key,value)的方式存儲的,關鍵字(key)是不可重復的,而值是對應于鍵的。我們可以把哈希表簡單理解為一本字典,每個鍵(key)是一個單詞,而每個單詞都有自己對應的解釋,也就是值(value)。

1.1 哈希表的實現方式

那我們需要用什么數據結構實現哈希表呢?我們知道數組的特點是尋址容易,插入和刪除數組困難。而鏈表的特點是尋址困難,插入和刪除數據容易。那么我們能不能綜合兩者的特性,創建一種尋址容易,插入刪除也容易的數據結構呢?是的,哈希表就使用這么一種聰明的實現方式:“拉鏈法”,可以簡單理解為“一堆由鏈表組成的數組”,如圖所示:
在這里插入圖片描述
可以看到哈希表的左側是數組,數組的每個成員包括一個指針,指向一個鏈表的頭節點,當然鏈表可能為空,也可能鏈接多個節點。

我們存儲鍵值對(key-value pair)的方式主要依靠鍵的特征,通過鍵的哈希值找到對應的數組下標,然后將其鍵值對添加到對應的鏈表中,尋找元素時,也是根據其鍵的哈希值,找到特定鏈表其對應的值。

那我們如何得到所謂的哈希值呢?我們先簡單理解一下此過程:哈希表使用哈希函數(Hash Function)將鍵(key)轉換成一個哈希值(整形數字),然后將該數組對數組長度取余,取余得到的數字就當做數組的下標,然后將其鍵值對添加到對應的鏈表中。尋找一個鍵所對應的值時,我們也是使用哈希函數將鍵轉換為對應的數組下標,并在其鏈表中定位到該關鍵字所對應的數值。

1.2 哈希函數(Hash Function)

可見哈希表能快速添加和查找元素的核心原因就是哈希函數,哈希函數能快速將一個數值轉換成哈希值(整數)。所以哈希表必須保持哈希值的計算一致,如果兩個哈希值是不相同的,那么這兩個哈希值的原始輸入也是不相同的。

那如果兩個不同的輸入得到相同的哈希值呢?這也就是所謂的哈希值沖突,這也是為什么我們結合數組和鏈表來實現哈希表,如果一個關鍵字對應的數組下標已經有其他元素了,只需要在其對應的鏈表后創建一個新的節點即可。

簡單來說,我們輸入關鍵字x,使用哈希函數f(x)計算出哈希值y,然后使用哈希值y來找特定的數組下標,并在對應位置插入新數據。在之后的實現中,我們會使用Java來實現哈希表,而JVM自動生成哈希值,我們只需要再將其哈希值和我們的哈希表數組長度取模(mod)就能拿到其對應下標。

1.3 哈希表支持的操作

為了幫助大家更好地理解哈希表的原理,我們來詳細描述一對鍵值被插入的過程,首先我們將鍵值對以鏈表節點的方式儲存,其節點包含自己的鍵和值。如果我們要將一對鍵值存入哈希表,我們需要使用哈希函數計算鍵的哈希值,并與哈希表的長度取模,然后找到對應的數組下標,如果該位置沒有其他鍵值節點,直接將其位置指向新節點即可。如果對應的位置有其他節點,直接將其新節點加到鏈表的最后面即可。

如果我們要查找一個鍵所對應的值,我們只需要計算該鍵對應的哈希值,找到數組下標所對應的頭節點后,從頭節點開始尋找我們所要的鍵值對。
在這里插入圖片描述
以下哈希表支持的操作:

  • get(K key):通過特定的關鍵字拿到其所對應的值
  • add(Key key, Value value):將一對新的鍵值對加入哈希表
  • remove(Key key):通過關鍵字,刪除哈希表中的鍵值對
  • getSize():當前鍵值對的數量
  • isEmpty():查看哈希表是否為空

2. Java實現

下面我們就來使用Java實現哈希表,在我們定義的鍵值對中,關鍵字為字符串類型,數值為整數類型,以下是哈希表和哈希表節點的定義:

import java.util.ArrayList;public class HashMap {static class HashNode<String, Integer> {String key;Integer value;HashNode<String, Integer> next;public HashNode(String key, Integer value) {this.key = key;this.value = value;}}private ArrayList<HashNode<String, Integer>> bucketArray;private int numBuckets;private int size;public HashMap() {bucketArray = new ArrayList<>();numBuckets = 10;size = 0;for(int i = 0; i < numBuckets; i++) {bucketArray.add(null);}}
}

HashNode就是鍵值對節點的定義,其中key就是關鍵字,而value是關鍵字對應的數值,還包含了next指向下一個節點。HashMap則是哈希表的定義,其中包含了數據類型為ArrayList的bucketArray,大家可以將其理解為哈希表圖示左側的Array,bucketArray中存儲由HashNode組成的鏈表。HashMap還記錄了numBuckets代表哈希表數組的長度(不是節點的數量),size代表當前bucket的數量。在哈希表初始化時,我們初始化bucketArray,然后給numBuckets設置一個初始值10,初始化size,并在每個bucketArray上加上一個空的頭節點。

以下是getBucketIndex的定義:

private int getBucketIndex(String key) {int hashCode = key.hashCode();int index = hashCode % numBuckets;return index;
}

getBucketIndex的輸入是一個關鍵字,輸出是關鍵字對應的bucket位置。我們只需要通過Java內置的hashCode函數,將關鍵字轉化成整數形式的hashCode,然后將hashCode和numBuckets取模,就能得到對應bucket的指數。以下是add的定義:

public void add(String key, Integer value) {int bucketIndex = getBucketIndex(key);HashNode<String, Integer> head = bucketArray.get(bucketIndex);while (head != null) {if (head.key.equals(key)) {head.value = value;return;}head = head.next;}size++;head = bucketArray.get(bucketIndex);HashNode<String, Integer> newNode = new HashNode<String, Integer>(key, value);newNode.next = head;bucketArray.set(bucketIndex, newNode);if ((1.0 * size) / numBuckets >= 0.7) {ArrayList<HashNode<String, Integer>> temp = bucketArray;bucketArray = new ArrayList<>();numBuckets = 2 * numBuckets;size = 0;for (int i = 0; i < numBuckets; i++) {bucketArray.add(null);}for (HashNode<String, Integer> headNode : temp) {while(headNode != null) {add(headNode.key, headNode.value);headNode = headNode.next;}}}
}

在add方法中,我們使用getBucketIndex拿到關鍵字對應的bucket指數,并從對應bucket的頭節點開始,尋找是否有與其關鍵詞對應的節點,如果找到其節點,更新節點的數據,然后返回。如果沒有找到,我們創建一個新的鍵值對節點,然后將其next指針指向對應bukcet的頭節點,再把新節點更新為對應bucket的頭節點。如果當前鍵值對數量超過了bucket容量的70%,那么我們可以創建一個長度為當前數量兩倍的bucketArray,并把當前的節點轉移到新的bucketArray上。

get函數是通過關鍵字找到對應數組的函數:

public Integer get(String key) {int bucketIndex = getBucketIndex(key);HashNode<String, Integer> head = bucketArray.get(bucketIndex);while (head != null) {if (head.key.equals(key)) {return head.value;}head = head.next;}return null;
}

在此函數中,我們通過getBucketIndex拿到關鍵字對應的bucket指數,并拿到head頭指針,然后順著對應鏈表往下走,尋找是否有對應的關鍵字的節點,如果找到了,返回對應的數值,否則返回null。

public Integer remove(String key) {int bucketIndex = getBucketIndex(key);HashNode<String, Integer> head = bucketArray.get(bucketIndex);HashNode<String, Integer> prev = null;while (head != null) {if (head.key.equals(key))break;prev = head;head = head.next;}if (head == null) {return null;}size--;if (prev != null) {prev.next = head.next;} else {bucketArray.set(bucketIndex, head.next);}return head.value;
}

在remove函數中,我們通過getBucketIndex拿到對應的bucket指數,然后拿到頭指針,通過迭代next指針,我們可以使用刪除鏈表節點的方式來刪除對應節點。最后是兩個簡單函數size和isEmpty的定義:

public int size() {return size;
}public boolean isEmpty() {return size() == 0;
}

以上就是哈希表的定義啦,可見哈希表的插入刪除很快,可是當數組快滿,要進行數據遷移的話,性能下降得非常嚴重,所以設計數組長度時,必須知道將要儲存多少數據,才能設計出一個合理有效的哈希表。

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

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

相關文章

【python】如何將文件夾及其子文件夾下的所有word文件匯總導出到一個excel文件里?

根據你的需求,這里提供一套完整的Python解決方案,支持遞歸遍歷子文件夾、提取Word文檔內容(段落+表格),并整合到Excel中。以下是代碼實現及詳細說明: 一個單元格一個word的全部內容 完整代碼 # -*- coding: utf-8 -*- import os from docx import Document import pand…

leetcode-位運算

位運算 371. 兩整數之和 題目 給你兩個整數 a 和 b &#xff0c;不使用 運算符 和 - &#xff0c;計算并返回兩整數之和。 示例 1&#xff1a; 輸入&#xff1a; a 1, b 2 輸出&#xff1a; 3 示例 2&#xff1a; 輸入&#xff1a; a 2, b 3 輸出&#xff1a; 5 提示&am…

飛帆控件:在編輯模式下額外加載的庫

飛帆是一個自由的控件設計平臺。在飛帆中&#xff0c;我們可以很方便地創建基于 Vue 2 組件的控件&#xff0c;并使用控件來搭建網頁。 他山之石&#xff0c;可以攻玉。在創建控件中&#xff0c;使用 js 、css 依賴庫能讓我們的控件更強大。 有些時候&#xff0c;在編輯模式下…

GPLT-2025年第十屆團體程序設計天梯賽總決賽題解(共計266分)

今天偶然發現天梯賽的代碼還保存著&#xff0c;于是決定寫下這篇題解&#xff0c;也算是復盤一下了 L1本來是打算寫的穩妥點&#xff0c;最后在L1-6又想省時間&#xff0c;又忘記了insert&#xff0c;replace這些方法怎么用&#xff0c;也不想花時間寫一個文件測試&#xff0c…

編碼轉換器

大批量轉換編碼 可以將整個工程文件夾從GB18030轉為UTF-8 使用Qt C制作 項目背景 比較老的工程&#xff0c;尤其是keil嵌入式的工程&#xff0c;其文本文件&#xff08;.c、.cpp、.h、.txt、……&#xff09;編碼為gb2312&#xff0c;這為移植維護等帶來了不便。現在uit-8用…

STL 核心模塊

很好&#xff01;你想深入 STL&#xff08;Standard Template Library&#xff09;和容器算法&#xff0c;是學習 C 非常關鍵的一步。下面我給你整理一份STL 容器 算法的入門指南&#xff0c;適合從零起步掌握這部分內容。 &#x1f31f; 一、STL 核心模塊 STL 分為三大塊&am…

2024沈陽區域賽,D - Dot Product Game

題目鏈接 樹狀數組求逆序對 #include<bits/stdc.h> using namespace std; using lllong long; typedef pair<int,int>PII; typedef priority_queue<int> upq; typedef priority_queue<int,vector<int>,greater<int>> dpq; const int M99…

簡易博客點贊系統實現

簡易博客點贊系統 好久沒寫 Java 了&#xff0c;整個簡單的項目進行康復訓練。 基于 Spring Boot SSM MySQL Mybatis-Plus Knife4j Swagger 的一個簡易博客點贊系統 開源地址&#xff1a;https://github.com/FangMoyu/simple-thumb 功能 登錄獲取當前登錄用戶獲取博客…

一個既簡單又詭異的問題

public class DaYaoGuai {static String s;public static void main(String[] args) {Thread t1 new Thread(){Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}s"深圳";}};t1.start();Thre…

使用docker在manjaro linux系統上運行windows和ubuntu

因為最近項目必須要使用指定版本的solidworks和maxwell&#xff08;都只能在win系統上使用&#xff09;, 且目前的ubuntu容器是沒有桌面的&#xff0c;導致我運行不了一些帶圖形的ros2功能。無奈之下&#xff0c;決定使用docker-compose寫一下配置文件&#xff0c;徹底解決問題…

Elasticsearch中的_source字段講解

_source 在 Elasticsearch 查詢中用于限制返回的字段,類似于 SQL 中的 SELECT 指定列。 代碼示例: esSearchResults = es_service.search_documents({"query": {"terms": {"file_id":

【論文閱讀20】-CNN-Attention-BiGRU-滑坡預測(2025-03)

這篇論文主要探討了基于深度學習的滑坡位移預測模型&#xff0c;結合了MT-InSAR&#xff08;多時相合成孔徑雷達干涉測量&#xff09;觀測數據&#xff0c;提出了一種具有可解釋性的滑坡位移預測方法。 [1] Zhou C, Ye M, Xia Z, et al. An interpretable attention-based deep…

C++ 的 IO 流

&#x1f4ac; &#xff1a;如果你在閱讀過程中有任何疑問或想要進一步探討的內容&#xff0c;歡迎在評論區暢所欲言&#xff01;我們一起學習、共同成長~&#xff01; &#x1f44d; &#xff1a;如果你覺得這篇文章還不錯&#xff0c;不妨順手點個贊、加入收藏&#xff0c;并…

spring cloud gateway前面是否必須要有個nginx

在 **"客戶端 → Nginx (前置限流) → Spring Cloud Gateway → 微服務(Sentinel 熔斷限流)"** 的架構中&#xff0c;**Spring Cloud Gateway 前面并不強制要求必須有 Nginx**&#xff0c;是否需要取決于具體場景。以下是詳細分析&#xff1a; 一、必須使用 Nginx 的…

Spark和Hadoop之間的對比和聯系

&#xff08;一&#xff09;Spark概述 Spark是一種基于內存的快速、通用、可拓展的大數據分析計算引擎。Hadoop是一個分布式系統基礎架構。 1&#xff09;官網地址&#xff1a;Apache Spark? - Unified Engine for large-scale data analytics 2&#xff09;文檔查看地址&…

多線程進階(Java)

注&#xff1a;此博文為本人學習過程中的筆記 1.常見的鎖策略 當我們需要自己實現一把鎖時&#xff0c;需要關注鎖策略。Java提供的synchronized已經非常好用了&#xff0c;覆蓋了絕大多數的使用場景。此處的鎖策略并不是和Java強相關的&#xff0c;只要涉及到并發編程&#…

c++STL——stack、queue、priority_queue的模擬實現

文章目錄 stack、queue、priority_queue的模擬實現使用部分模擬實現容器適配器deque的介紹原理真實結構deque的迭代器deque的操作deque的優缺點 stack的模擬實現按需實例化queue的模擬實現priority_queue的模擬實現為何引入仿函數代碼實現 stack、queue、priority_queue的模擬實…

【深度學習—李宏毅教程筆記】Transformer

目錄 一、序列到序列&#xff08;Seq2Seq&#xff09;模型 1、Seq2Seq基本原理 2、Seq2Seq模型的應用 3、Seq2Seq模型還能做什么&#xff1f; 二、Encoder 三、Decoder 1、Decoder 的輸入與輸出 2、Decoder 的結構 3、Non-autoregressive Decoder 四、Encoder 和 De…

C++鐫刻數據密碼的樹之銘文:二叉搜索樹

文章目錄 1.二叉搜索樹的概念2.二叉搜索樹的實現2.1 二叉搜索樹的結構2.2 二叉搜索樹的節點尋找2.2.1 非遞歸2.2.2 遞歸 2.3 二叉搜索樹的插入2.3.1 非遞歸2.3.2 遞歸 2.4 二叉搜索樹的刪除2.4.1 非遞歸2.4.2 遞歸 2.5 二叉搜索樹的拷貝 3.二叉樹的應用希望讀者們多多三連支持小…

系統架構設計師:流水線技術相關知識點、記憶卡片、多同類型練習題、答案與解析

流水線記憶要點? ?公式 總時間 (n k - 1)Δt 吞吐率 TP n / 總時間 → 1/Δt&#xff08;max&#xff09; 加速比 S nk / (n k - 1) | 效率 E n / (n k - 1) 關鍵概念 周期&#xff1a;最長段Δt 沖突?&#xff1a; ?數據沖突&#xff08;RAW&#xff09; → 旁路/…