成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

基于拉鏈式和線性探測式散列表實現Map

開發 前端
實現散列表的第一步就是需要考慮如何把一個鍵轉換為數組的下標,這時候就需要使用到散列函數,先把鍵值轉換成一個整數然后在使用除留余數法計算出數組的下標。每種類型的鍵我們都需要一個不同的散列函數。

[[392733]]

前言

前幾篇我們一起學習了基于數組、鏈表、二叉樹、紅黑樹來實現Map的操作,本篇我們將會一起來學習基于散列表來實現Map,這種方式對應著java里面的HashMap,這也是使用最多的一種方式

散列表實現Map主要分為了兩個步驟:

  1. 基于散列函數將被查找鍵轉換為數組的下標
  2. 處理散列值沖突的情況,有兩種方式來處理沖突:拉鏈式和線性探測

散列函數

實現散列表的第一步就是需要考慮如何把一個鍵轉換為數組的下標,這時候就需要使用到散列函數,先把鍵值轉換成一個整數然后在使用除留余數法計算出數組的下標。每種類型的鍵我們都需要一個不同的散列函數。

在java中對于常用的數據類型已經實現了hashCode,我們只需要根據hashCode的值使用除留余數法即可轉換成數組的下標

java中的約定:如果兩個對象的equals相等,那么hashCode一定相同;如果hashCode相同,equals不一定相同。對于自定義類型的鍵我們通常需要自定義實現hashCode和equals;默認的hashCode返回的是對象的內存地址,這種散列值不會太好。

下面我來看看Java中常用類型的hashCode計算方式

Integer

由于hashCode需要返回的值就是一個int值,所以Integer的hashCode實現很簡單,直接返回當前的value值

  1. @Override 
  2. public int hashCode() { 
  3.     return Integer.hashCode(value); 
  4. public static int hashCode(int value) { 
  5.     return value; 

Long

Java中Long類型的hashCode計算是先把值無符號右移32位,之后再與值相異或,保證每一位都用用到,最后強制轉換成int值

  1. @Override 
  2. public int hashCode() { 
  3.     return Long.hashCode(value); 
  4.  
  5. public static int hashCode(long value) { 
  6.     return (int)(value ^ (value >>> 32)); 

Double、Float

浮點類型的鍵java中hashCode的實現是將鍵表示為二進制

  1. public static int hashCode(float value) { 
  2.     return floatToIntBits(value); 
  3. public static int floatToIntBits(float value) { 
  4.     int result = floatToRawIntBits(value); 
  5.     // Check for NaN based on values of bit fields, maximum 
  6.     // exponent and nonzero significand. 
  7.     if ( ((result & FloatConsts.EXP_BIT_MASK) == 
  8.           FloatConsts.EXP_BIT_MASK) && 
  9.          (result & FloatConsts.SIGNIF_BIT_MASK) != 0) 
  10.         result = 0x7fc00000; 
  11.     return result; 

String

java中每個char都可以表示成一個int值,所以字符串轉換成一個int值

  1. public int hashCode() { 
  2.     int h = hash; 
  3.     if (h == 0 && value.length > 0) { 
  4.         char val[] = value; 
  5.  
  6.         for (int i = 0; i < value.length; i++) { 
  7.             h = 31 * h + val[i]; 
  8.         } 
  9.         hash = h; 
  10.     } 
  11.     return h; 

軟緩存

如果計算一個散列值比較耗時,那么我可以這個計算好的值緩存起來,即使用一個變量把這個值保存起來,在下一次調用時直接返回。Java中的String就是采用的這種方式。

拉鏈式的散列表

散列函數能夠將鍵值轉換成數組的下標;第二步就是需要處理碰撞沖突,也就是需要處理遇到了兩個散列值相同的對象應該如何存儲。有一種方式是數組中的每一個元素都指向一個鏈表用來存放散列值相同的對象,這種方式被稱為拉鏈法

拉鏈法可以使用原始的鏈表保存鍵,也可以采用我們之前實現的紅黑樹來表示,在java8中采用的這兩種方式的混合,在節點數超過了8之后變為紅黑樹。

這里我們就采用簡單的鏈表來實現拉鏈式散列表,數據結構使用在前幾篇中已經實現的LinkedMap,可以參考前面的文章《基于數組或鏈表實現Map》。

  1. public class SeparateChainingHashMap<K, V> implements Map<K, V> { 
  2.  
  3.     private int size
  4.     private LinkedMap<K, V>[] table
  5.  
  6.     public SeparateChainingHashMap(int capacity) { 
  7.         this.table = (LinkedMap<K, V>[]) new LinkedMap[capacity]; 
  8.         for (int i = 0; i < capacity; i++) { 
  9.             this.table[i] = new LinkedMap<>(); 
  10.         } 
  11.     } 
  12.  
  13.     @Override 
  14.     public void put(K key, V value) { 
  15.         this.table[hash(key)].put(key, value); 
  16.         size++; 
  17.     } 
  18.  
  19.     private int hash(K key) { 
  20.         return (key.hashCode() & 0x7fffffff) % table.length; 
  21.     } 
  22.  
  23.     @Override 
  24.     public V get(K key) { 
  25.         return this.table[hash(key)].get(key); 
  26.     } 
  27.  
  28.     @Override 
  29.     public void delete(K key) { 
  30.         this.table[hash(key)].delete(key); 
  31.         size--; 
  32.     } 
  33.  
  34.     @Override 
  35.     public int size() { 
  36.         return size
  37.     } 
  38.  

這的hash函數使用key的hashCode與上0x7fffffff得到一個非負的整數,然后再使用除留余數法計算出數組的下標。其中0x7FFFFFFF 的二進制表示就是除了首位是 0,其余都是1,也就是說,這是最大的整型數 int(因為第一位是符號位,0 表示他是正數),可以使用Integer.MAX_VALUE代替

散列表的主要目的在于把鍵值均勻的分布到數組中,所以散列表是無序的,如果你需要的Map需要支持取出最大值,最小值,那么散列表都不適合。

這里我們實現的拉鏈式散列表的數組大小是固定的,但是通常在隨著數據量的增大,越短的數組出現碰撞的幾率會增大,所以需要數組支持動態的擴容,擴容之后需要把原來的值重新插入到擴容之后的數組中。數組的擴容可以參考之前的文章《老哥是時候來復習下數據結構與算法了》

線性探測式散列表

實現散列表的另一種方式就是用大小為M來保存N個鍵值,其中M>N;解決碰撞沖突就需要利用數組中的空位;這種方式中最簡單的實現就是線性探測。

線性探測的主要思路:當插入一個鍵,發生碰撞沖突之后直接把索引加一來檢查下一個位置,這時候會出現三種情況:

下一個位置和待插入的鍵相等,那么值就修改值

下一個位置和待插入的鍵不相等,那么索引加一繼續查找

如果下一個位置還是一個空位,那么直接把待插入對象放入到這個空位

初始化

線性探測式散列表使用兩個數組來存放keys和values,capacity表示初始化數組的大小

  1. private int size
  2. private int capacity; 
  3. private K[] keys; 
  4. private V[] values
  5.  
  6. public LinearProbingHashMap(int capacity) { 
  7.     this.capacity = capacity; 
  8.     this.keys = (K[]) new Object[capacity]; 
  9.     this.values = (V[]) new Object[capacity]; 

插入

當插入鍵的位置超過了數組的大小,就需要回到數組的開始位置繼續查找,直到找到一個位置為null的才結束;index = (index + 1) % capacity

當數組已存放的容量超過了數組總容量的一半,就需要擴容到原來的2倍

  1. @Override 
  2. public void put(K key, V value) { 
  3.     if (Objects.isNull(key)) { 
  4.         throw new IllegalArgumentException("Key can not null"); 
  5.     } 
  6.     if (this.size > this.capacity / 2) { 
  7.         resize(2 * this.capacity); 
  8.     } 
  9.     int index
  10.     for (index = hash(key); this.keys[index] != nullindex = (index + 1) % capacity) { 
  11.         if (this.keys[index].equals(key)) { 
  12.             this.values[index] = value; 
  13.             return
  14.         } 
  15.     } 
  16.     this.keys[index] = key
  17.     this.values[index] = value; 
  18.     size++; 

動態調整數組的大小

我們可以參考之前在文章《老哥是時候來復習下數據結構與算法了》中實現的動態調整數據的大小;在線性探測中需要把原來的數據重新插入到擴容之后的數據,因為數組的大小變了需要重新計算索引的位置。

  1. private void resize(int cap) { 
  2.     LinearProbingHashMap<K, V> linearProbingHashMap = new LinearProbingHashMap<>(cap); 
  3.     for (int i = 0; i < capacity; i++) { 
  4.         linearProbingHashMap.put(keys[i], values[i]); 
  5.     } 
  6.     this.keys = linearProbingHashMap.keys; 
  7.     this.values = linearProbingHashMap.values
  8.     this.capacity = linearProbingHashMap.capacity; 

查詢

線性探測散列表中實現查詢的思路:根據待查詢key的hash函數計算出索引的位置,然后開始判斷當前位置上的key是否和待查詢key相等,如果相等那么就直接返回value,如果不相等那么就繼續查找下一個索引直到遇到某個位置是null才結束,表示查詢的key不存在;

  1. @Override 
  2. public V get(K key) { 
  3.     if (Objects.isNull(key)) { 
  4.         throw new IllegalArgumentException("Key can not null"); 
  5.     } 
  6.     int index
  7.     for (index = hash(key); this.keys[index] != nullindex = (index + 1) % capacity) { 
  8.         if (this.keys[index].equals(key)) { 
  9.             return this.values[index]; 
  10.         } 
  11.     } 
  12.     return null

刪除元素

線性探測式的刪除稍微麻煩一些,首先需要查找出待刪除的元素位置,刪除元素之后需要把當前索引之后的連續位置上的元素重新插入;因為是否有空位對于線性探測式散列表的查找至關重要;例如:5->7->9,刪除了7之后,如果不重新插入7的位置就是空位,那么get方法將無法查詢到9這個元素;

每次刪除之后都需要檢測一次數組中實際元素的個數,如果與數組的容量相差較大,就可以進行縮容操作;

  1. @Override 
  2. public void delete(K key) { 
  3.     if (Objects.isNull(key)) { 
  4.         throw new IllegalArgumentException("Key can not null"); 
  5.     } 
  6.     int index
  7.     for (index = hash(key); this.keys[index] != nullindex = (index + 1) % capacity) { 
  8.         if (this.keys[index].equals(key)) { 
  9.             this.keys[index] = null
  10.             this.values[index] = null
  11.             break; 
  12.         } 
  13.     } 
  14.  
  15.     for (index = (index + 1) % capacity; this.keys[index] != nullindex = (index + 1) % capacity) { 
  16.         this.size--; 
  17.         this.put(this.keys[index], this.values[index]); 
  18.         this.keys[index] = null
  19.         this.values[index] = null
  20.     } 
  21.     this.size--; 
  22.     if (size > 0 && size < capacity / 4) { 
  23.         resize(capacity / 2); 
  24.     } 
  25.  

文中所有源碼已放入到了github倉庫:https://github.com/silently9527/JavaCore

 

責任編輯:武曉燕 來源: 貝塔學JAVA
相關推薦

2022-10-11 23:18:28

散列表函數數組

2022-03-14 10:02:03

散列表鏈表哈希表

2022-03-24 14:58:02

Java散列表編程語言

2023-05-29 08:31:48

Redis散列表

2021-03-17 07:56:29

數組Map二叉樹

2024-12-17 09:00:00

lambda函數Python

2010-08-18 09:03:46

jQueryJSONTrimpath

2021-07-15 06:23:45

nn.Module神經網絡線性網絡

2023-11-08 07:56:38

單鏈表雙鏈表

2021-05-14 08:58:18

非線性安全Go

2021-10-28 05:47:38

PathProber暴力破解安全工具

2017-04-13 10:51:09

Consul分布式

2023-12-14 12:56:00

鏈式調用代碼

2018-07-20 09:16:04

鏈式存儲結構

2020-11-06 12:48:16

數據結構算法分析

2025-05-16 08:58:47

Mongodb分布式存儲

2017-02-21 13:24:41

iOSMVVM架構

2022-10-27 10:44:14

分布式Zookeeper

2023-11-10 15:47:06

線性回歸內核技巧

2025-05-20 08:00:00

鏈式調用異步
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 成人国产精品色哟哟 | 日韩在线视频一区二区三区 | 久久99精品国产麻豆婷婷 | 欧美成人a| 狠狠躁天天躁夜夜躁婷婷老牛影视 | 中文字幕一区二区在线观看 | 日本精品一区二区三区在线观看视频 | 欧美成人免费电影 | 黄色片在线免费看 | 成人久久18免费网站 | 亚洲在线久久 | 亚洲精品一二区 | 91.com视频| 日韩成人免费中文字幕 | 午夜激情视频 | 成人欧美一区二区三区 | 国产综合久久久 | 国产福利资源在线 | 自拍亚洲 | 国产精品精品视频 | 精品一区二区三区四区在线 | 天堂一区二区三区四区 | 美女视频网站久久 | 狠狠躁夜夜躁人人爽天天高潮 | 欧美日韩一区二区三区视频 | 欧美日韩亚洲视频 | 成人性视频免费网站 | 九九av| 欧美在线a | 欧美日韩在线视频一区二区 | 91香蕉嫩草 | 一本一道久久a久久精品蜜桃 | 久久久久国产精品一区二区 | 亚洲天堂免费 | 国产精品日韩欧美一区二区三区 | 一区二区日韩 | 国产精品99久久久久久宅男 | 国产成人av在线 | 亚洲电影一区二区三区 | 精品日本中文字幕 | 最新国产精品精品视频 |