Java數據結構第二十三期:Map與Set的高效應用之道(二)

專欄:Java數據結構秘籍

個人主頁:手握風云

目錄

一、哈希表

1.1. 概念

1.2. 沖突

1.3. 避免沖突

1.4. 解決沖突

1.5. 實現

二、OJ練習

2.1.?只出現一次的數字

2.2.?隨機鏈表的復制

?2.3.?寶石與石頭


一、哈希表

1.1. 概念

????????順序結構以及平衡樹中,元素關鍵碼與其存儲位置之間沒有對應的關系,因此在查找?個元素時,必須要經過關鍵碼的多次比較。順序查找時間復雜度為O(n),平衡樹中為樹的?度,即O(n),搜索的效率取決于搜索過程中元素的比較次數。

????????理想的搜索?法:可以不經過任何比較,?次直接從表中得到要搜索的元素。如果構造?種存儲結構,通過某種函數(hashFunc)使元素的存儲位置與它的關鍵碼之間能夠建立一一映射的關系,那么在查找時通過該函數可以很快找到該元素。

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

????????哈希函數設置為:hash(key) = key % capacity;其中capacity為存儲元素底層空間總的大小。

? ? ? ? 我們設一個整數集合{1,7,6,4,5,9},把capacity設置為10,那我們就可以按照下圖來存儲。如果我們再想存放一個元素12,我們可以直接通過哈希函數存進下標2中,要想搜索,直接通過2下標來找到12,這樣時間復雜度為O(n),從而提高效率。

1.2. 沖突

????????不同關鍵字通過相同哈希函數計算出相同的哈希地址,該種現象稱為哈希沖突或哈希碰撞。把具有不同關鍵碼?具有相同哈希地址的數據元素稱為“同義詞”。例如我們要想存放一個14,通過上面的哈希函數應該存到4下標,但此時4下標已經存了一個4,就會造成哈希沖突。

????????出現了哈希沖突,我們就要想辦法避免沖突。由于我們哈希表底層數組的容量往往是小于實際要存儲的關鍵字的數量的,就會導致沖突的發?是必然的,但我們能做的應該是盡量的降低沖突率。

1.3. 避免沖突

? ? ? ? 第一種方法可以設計合理的哈希函數。設計原則:定義域必須包括需要存儲的全部關鍵碼,而如果散列表允許有m個地址時,其值域必須在0到m-1之間;計算出來的地址能均勻分布在整個空間中;比較簡單。

? ? ? ? 直接訂制法:取關鍵字的某個線性函數為散列地址:Hash(Key) = A*Key + B。優點:簡單、均勻。缺點:需要事先知道關鍵字的分布情況。使用場景:適合查找比較小且連續的情況。

? ? ? ? 除留余數法:設散列表中允許的地址數為m,取?個不大于m,但最接近或者等于m的質數p作為除數,按照哈希函數:Hash(key) = key% p(p<=m),將關鍵碼轉換成哈希地址。

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

? ? ? ? 我們還有另外一種就是調節負載因子。哈希表的載荷因子為:?=填入表中元素個數/哈希表長度。當沖突率達到?個?法忍受的程度時,我們需要通過降低負載因子來變相的降低沖突率。已知哈希表中已有的關鍵字個數是不可變的,那我們能調整的就只有哈希表中的數組的大小。

1.4. 解決沖突

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

????????閉散列:也叫開放地址法,當發?哈希沖突時,如果哈希表未被裝滿,說明在哈希表中必然還有空位置,那么可以把key存放到沖突位置中的“下?個” 空位置中去。那如何尋找下?個空位置呢?此時我們就需要應用線性探索。從發生沖突的位置開始,依次向后探測,直到尋找到下?個空位置為止。但這樣還是會有一個缺點,就是會使得沖突元素聚集在一起,并且如果我們把4刪除了,又如何尋找14、24、34這些元素。因此線性探測采?標記的偽刪除法來刪除?個元素。

?????????次探測為了避免該問題,找下?個空位置的?法為:Hi = (H0+i^2)%m,i表示沖突的次數,m為表的大小。

????????開散列:開散列法?叫鏈地址法(開鏈法),?先對關鍵碼集合?散列函數計算散列地址,具有相同地址的關鍵碼歸于同一子集合,每?個?集合稱為?個桶,各個桶中的元素通過?個單鏈表鏈接起來,各鏈表的頭結點存儲在哈希表中。

1.5. 實現

? ? ? ? 由于我們需要節點數組來創建哈希表,利用內部類來表示節點對象。

public class HashBucket {//創建節點數組static class Node{public int key;public int val;public Node next;public Node(int key, int val) {this.key = key;this.val = val;}}public Node[] array = new Node[10];public int UsedSize;//表示還系統中存放的元素public static final float LOAD_FACTOR = 0.75f;//負載因子表示為常數
}

????????我們先來模擬哈希表中放元素的方法。我們要想把元素放入,首先得是一個結點。比如key=14,如果表中已經有14了,就不能再放14并且更新val,所以我們首先需要遍歷數組判斷key是否相同,如果相同,更新val。下面再使用頭插法來把節點元素放入哈希表中。插入元素之后,我們還需要重新計算負載因子是否超過了我們規定的LOAD_FACTOR。如果超過了,就需要進行擴容操作。擴容的時候還需要注意,比如我們要插入的元素的key為14,擴容前需要插入下標為4的位置,擴容2倍后,就需要插入下標為14的位置。

????????完整代碼實現:

    public void put(int key, int val) {int index = key % array.length;//先遍歷index數組下的鏈表,如果有相同的key,則更新valNode cur = array[index];while (cur != null) {if (cur.key == key) {cur.val = val;return;}cur = cur.next;}//頭插法Node node = new Node(key, val);node.next = array[index];array[index] = node;UsedSize++;//重新計算負載因子是不是超過了我們規定的值if (CalculateLoadFactor() >= LOAD_FACTOR) {//擴容ReSize();}}private float CalculateLoadFactor() {return UsedSize * 1.0f / array.length;}private void ReSize() {Node[] newArray = new Node[array.length*2];for (Node node : array) {Node cur = node;while (cur != null) {int newIndex = cur.key % newArray.length;//把當前節點放入新數組的位置,再次使用頭插法Node curNext = cur.next;cur.next = newArray[newIndex];newArray[newIndex] = cur;cur = curNext;}}array = newArray;}

? ? ? ? get方法也是一樣,也是通過索引下標來尋找目標值。

    public int get(int key){int index = key % array.length;Node cur = array[index];while(cur != null){if(cur.key == key){return cur.val;}cur = cur.next;}return -1;}

? ? ? ? 我們在Test類里面進行實例化并調試。

public class Test {public static void main(String[] args) {HashBucket hashBucket = new HashBucket();hashBucket.put(11,99);hashBucket.put(2,99);hashBucket.put(43,99);hashBucket.put(4,99);hashBucket.put(14,99);hashBucket.put(24,99);hashBucket.put(7,99);hashBucket.put(8,99);}
}

????????我們上面的方法key是整型,那如果key是引用類型呢,比如String或者Person類。那我們就把整型換作是泛型K、V。需要注意的是,key換成了泛型,不能直接進行%操作,我們可以使用hashCode方法轉成整型,并且進行比較要使用equals方法。

/*** @author: gao* @create-date: 2025/3/15 16:32*/public class HashBucket<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 = (Node<K, V>[]) new Node[10];public int UsedSize;//表示還系統中存放的元素public static final float LOAD_FACTOR = 0.75f;//負載因子表示為常數public void put(K key, V val) {int hashcode = key.hashCode();int index = hashcode % array.length;//先遍歷index數組下的鏈表,如果有相同的key,則更新valNode<K, V> cur = array[index];while (cur != null) {if (cur.key == key) {cur.val = val;return;}cur = cur.next;}Node<K, V> node = new Node<K, V>(key, val);node.next = array[index];array[index] = node;UsedSize++;}public V get(K key) {int hashcode = key.hashCode();int index = hashcode % array.length;Node<K, V> cur = array[index];while (cur != null) {if (cur.key.equals(key)) {return cur.val;}cur = cur.next;}return null;}
}

????????如果我們要判斷是否為同一個人,我們可以判斷身份證號碼是否相等。如果我們按照這種方法去寫,發現比較結果為false。這是因為我們沒有重寫equals和hashCode方法,編譯器默認調用Object方法。

class Person {public String id;public Person(String id) {this.id = id;}
}public class Test {public static void main(String[] args) {Person person1 = new Person("1234");Person person2 = new Person("1234");System.out.println(person1.equals(person2));System.out.println(person1.hashCode());System.out.println(person2.hashCode());}
}

    public boolean equals(Object obj) {return (this == obj);}

? ? ? ? 我們在Person類里面右擊,點擊Generate,再點擊equals() and hashCode(),就可以重寫。

    @Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Person person = (Person) o;return Objects.equals(id, person.id);}@Overridepublic int hashCode() {return Objects.hash(id);}

二、OJ練習

2.1.?只出現一次的數字

????????我們的基本思路是:利用HashSet,先遍歷一遍數組,把集合中沒有的數字放入,如果有,再移除,最后集合中剩下的元素就是只出現一次的數字,再遍歷一遍數組,匹配HashSet中的數組。

????????完整代碼實現:

class Solution {public int singleNumber(int[] nums) {Set<Integer> set = new HashSet<>();for (int i = 0;i < nums.length;i++) {if(! set.contains(nums[i])){set.add(nums[i]);}else{set.remove(nums[i]);}}for (int i = 0;i < nums.length;i++) {if(set.contains(nums[i])){return nums[i];}}return -1;}
}

????????執行時間還是比較高,因為使用了兩次for循環遍歷數組。

2.2.?隨機鏈表的復制

? ? ? ? 題目比較長,大概題意就是復制出一份與原來相同的鏈表。這道題的難點在于比單鏈表多了一個可以指向任意節點或者空的random域。起初,很多人會去想定義一個Node cur去遍歷一遍鏈表,一個一個節點進行拷貝,但一拷貝就會發現問題,因為我們我們不知道cur.next和cur.random是哪一個節點的地址。既然遍歷一遍鏈表不行,那就遍歷兩遍。第一遍遍歷,所有節點的val域全都拷貝過來,next域以及random域全都默認為null,每遍歷一個鏈表,就新實例化一個節點。然后我們<K,V>結構來建立老節點與新節點之間的映射關系。

????????我們每獲取一個節點的地址,都可以修改它的next域與random域。

????????完整代碼實現:

class Solution {public Node copyRandomList(Node head) {Map<Node,Node> map = new HashMap<>();//第一遍遍歷鏈表Node cur = head;while(cur != null){Node node = new Node(cur.val);map.put(cur,node);cur = cur.next;}//第二遍遍歷鏈表cur = head;while(cur != null){map.get(cur).next = map.get(cur.next);map.get(cur).random = map.get(cur.random);cur = cur.next;}return map.get(head);}
}

?2.3.?寶石與石頭

????????題目很簡單,就是查找stones中含有jewels中的字符的個數。我們先遍歷jewels字符串,將里面的字符放入集合中,再去遍歷stones中的字符,最后返回寶石個數。

????????完整代碼實現:

class Solution {public int numJewelsInStones(String jewels, String stones) {Set<Character> set = new HashSet<>();for (int i = 0; i < jewels.length(); i++) {char ch = jewels.charAt(i);set.add(ch);}int count = 0;for (int i = 0; i < stones.length(); i++) {char ch = stones.charAt(i);if(set.contains(ch)){count++;}}return count;}
}

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

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

相關文章

OpenHarmony子系統開發 - Rust編譯構建指導

OpenHarmony子系統開發 - Rust編譯構建指導 一、Rust模塊配置規則和指導 概述 Rust是一門靜態強類型語言&#xff0c;具有更安全的內存管理、更好的運行性能、原生支持多線程開發等優勢。Rust官方也使用Cargo工具來專門為Rust代碼創建工程和構建編譯。 OpenHarmony為了集成C…

【SpringMVC】常用注解:@ModelAttribute

1.作用 該注解是在SpringMVC4.3版本后新加入的。它可以修飾方法和參數。出現在方法上&#xff0c;表示當前方法會在控制器的方法之前執行。它可以修飾 沒有返回值的方法&#xff0c;也可以修飾沒有返回值的方法。它修飾參數&#xff0c;獲取指定 的數據給參數賦值。 當表單提…

人工智能之數學基礎:如何將線性變換轉換為矩陣?

本文重點 在機器學習中,常用的理論就是線性變換,線性變化一定有對應的矩陣表示,非線性變換是不具備這個性質的,那么現在如果有一個線性變換T那么如何知道它對應的矩陣呢? 線性變換的本質 我們知道線性變換相當于一個函數,而矩陣也是一個函數,所以線性變換一定存在一個…

STM32驅動代碼規范化編寫指南(嵌入式C語言方向)

點擊下面圖片&#xff0c;為您提供全新的嵌入式學習路線 文章目錄 一、命名規范體系1.1 變量/函數命名1.2 宏定義規范1.3 類型定義 二、代碼結構組織2.1 文件組織結構2.2 頭文件規范模板 三、注釋體系構建3.1 Doxygen風格示例3.2 復雜邏輯注釋 四、硬件抽象層設計4.1 寄存器封…

C++Primer學習(7.1 定義抽象數據類型)

類的基本思想是數據抽象(data abstraction)和封裝(encapsulation)。數據抽象是種依賴于接口(interface)和實現(implementation)分離的編程(以及設計)技術。類的接口包括用戶所能執行的操作:類的實現則包括類的數據成員、負責接口實現的函數體以及定義類所需的各種私有函數。 封…

【人工智能】大語言模型學習大綱

大語言模型學習大綱 大語言模型學習知識點大綱一、基礎知識準備二、機器學習入門三、自然語言處理(NLP)基礎四、Transformer架構與實踐五、高級主題六、前沿研究與實戰項目 學習步驟第一步&#xff1a;打牢基礎第二步&#xff1a;掌握機器學習與深度學習基礎第三步&#xff1a;…

Trae與Builder模式初體驗

說明 下載的國際版&#xff1a;https://www.trae.ai/ 建議 要選新模型 效果 還是挺不錯的&#xff0c;遇到問題反饋一下&#xff0c;AI就幫忙解決了&#xff0c;真是動動嘴&#xff08;打打字就行了&#xff09;&#xff0c;做些小的原型效果或演示Demo很方便呀&#xff…

基于VM的CentOS 7.4系統安裝與配置說明系統環境主機系統

系統環境 主機系統&#xff1a;Windows 11虛擬機版本&#xff1a;VMware Workstation 17 ProDVD鏡像版本&#xff1a;CentOS-7-x86_64-DVD-1908 虛擬機配置 內存&#xff1a;1G處理器&#xff1a;1核硬盤&#xff1a;80G 安裝步驟 1. 準備鏡像文件 下載并獲取CentOS 7.4的…

【設計模式】《設計模式:可復用面向對象軟件的基礎》:設計模式怎樣解決設計問題?

文章目錄 ?前言?一、設計模式怎樣解決設計問題&#xff1f;&#x1f31f;1、尋找合適的對象&#x1f31f;2、決定對象的粒度&#x1f31f;3、指定對象接口&#x1f31f;4、描述對象的實現&#x1f31f;5、運用復用機制?(1)針對接口編程&#xff0c;而不是針對實現編程。?(2…

【SpringMVC】常用注解:@MatrixVariable

1.作用 接收矩陣變量傳送的值 或許有人聽都沒聽過矩陣變量是什么&#xff0c;下面來介紹一下 矩陣變量是一種在URL路徑中傳遞多個鍵值對參數的方式&#xff0c;它是在 Servlet 規范之外的一種擴展機制&#xff0c;可用于更靈活地傳遞參數。 例如&#xff1a;/cars;colorred…

【項目管理git】git學習

ps&#xff1a;所有東西都是個人理解 文章目錄 一、git是什么&#xff0c;它用來做什么&#xff1f;二、相關知識庫2.1 簡單的linux指令2.2 git配置指令2.3 git常見的指令2.3.1 Git的上傳原理2.3.2 版本回退相關內容 2.4 設置遠程地址&#xff0c;本地上傳到github2.4.1 ssh相…

【性能優化】MySQL 生產環境 SQL 性能優化實戰案例

&#x1f680; MySQL 生產環境 SQL 性能優化實戰案例 &#x1f3d7;? 背景介紹 最近在處理一個項目時&#xff0c;發現在生產環境的工作流相關接口中&#xff0c;某些查詢的執行時間異常緩慢&#xff0c;盡管數據量僅為 2 萬條。經過分析&#xff0c;發現以下 SQL 語句執行非…

python速通小筆記-------1.容器

1.字符串的標識 字符串需要用“”標識。 與c不同&#xff0c;python 寫變量時 不需要標明數據類型每一行最后不需要加&#xff1b; 2.print函數的使用 與c中的printf函數一致 3.運算符 4.字符串str操作 1. 實現字符串拼接 2.% 實現字符串初始化 %s占位會把變量強制轉變為…

【SpringMVC】常用注解:@SessionAttributes

1.作用 用于多次執行控制器方法間的參數共享 2.屬性 value&#xff1a;用于指定存入的屬性名稱 type&#xff1a;用于指定存入的數據類型 3.示例 先寫JSP代碼 <a href"demo1/putMethod">存入 SessionAttribute</a><br><a href"demo…

零基礎上手Python數據分析 (2):Python核心語法快速入門

寫在前面 場景:每周銷售數據報表整理 任務描述: 你需要每周從多個Excel文件中匯總銷售數據,計算各項指標(銷售額、訂單量、客單價等),并生成周報。Excel操作痛點: 文件太多,手動打開復制粘貼,效率低下,容易出錯。 多個Excel文件,每個都要打開、篩選、復制數據,重復…

【PHP】獲取PHP-FPM的狀態信息

文章目錄 一、前言二、環境三、過程1&#xff09;修改PHP-FPM配置文件2&#xff09;修改Nginx配置文件3&#xff09;訪問頁面4&#xff09;修改狀態頁面端口 一、前言 PHP-FPM內置有一個狀態頁面&#xff0c;通過這個頁面可以獲取到FPM的一些狀態信息&#xff08;見下圖&#…

CCF CSP 第30次(2023.09)(2_坐標變換(其二)_C++)

CCF CSP 第30次&#xff08;2023.09&#xff09;&#xff08;2_坐標變換&#xff08;其二&#xff09;_C&#xff09; 題目背景&#xff1a;題目描述&#xff1a;輸入格式&#xff1a;輸出格式&#xff1a;樣例輸入&#xff1a;樣例輸出&#xff1a;樣例解釋&#xff1a;子任務…

搭建Spring Boot Admin監控系統

什么是Spring Boot Admin Spring Boot Admin 是一個用于管理和監控 Spring Boot 應用程序的開源工具。它提供了一個用戶友好的 Web 界面&#xff0c;用于集中管理和監控多個 Spring Boot 應用程序的運行狀態、健康狀況、日志、配置等信息。 Spring Boot Admin 的核心功能 應用…

機器學習中的激活函數是什么起什么作用

在機器學習&#xff0c;尤其是神經網絡中&#xff0c;?激活函數?&#xff08;Activation Function&#xff09;是一個非常重要的組件。它的主要作用是為神經網絡引入非線性&#xff0c;從而使神經網絡能夠學習和表示復雜的模式或函數。 1.激活函數的定義 激活函數是一個數學…

[CISCN 2022 初賽]ezpop(沒成功復現)

打開在線環境可以看到&#xff1a; 記得之前做過一個類似的就是有點像照著漏洞去復現。應該可以直接在網上找到鏈子去打。 www.zip查看路由是 Index/test&#xff0c;然后 post 傳參 a&#xff1a; exp&#xff08;參考了別的大神的wp&#xff09;&#xff1a; <?php //…