這是我參與8月更文挑戰的第7天,活動詳情查看:8月更文挑戰算法
幾乎全部的編程語言都有直接或間接地應用哈希表這種數據結構。哈希表一般是基於數組實現的,它神奇的地方在於對下標值的一種變換,這種變換可稱爲哈希函數,經過哈希函數能夠獲取到HashCode
。相對於數組,它的優點在於速度快,效率更高。編程
unicode
),將大數字轉換爲哈希化的代碼的過程封裝成的函數稱爲哈希函數哈希表中的每個key都是惟一值,並且每個key都有其對應的索引值,經過這個索引值直接在哈希表中找到這個key的value值,大大地提升了查找的效率。若是數據龐大的是否,每個索引值對應的就不止一個key,此時就會產生衝突。這個衝突常見有2種解決方式:鏈地址法和開放地址法。數組
在下面的封裝中,採用的基於數組的鏈地址法。markdown
在哈希表封裝的時候,設置哈希表的總長度。通常將總長度設置爲存放個數的兩倍,好比有50個數據,那咱們設置這個哈希表的總長度爲100。數據結構
function HashTable() {
this.storage = []; // 存放元素
this.count = 0; // 存放個數
this.limit = 7; // 當前哈希表的總長度
}
複製代碼
哈希函數在哈希表中是相當重要的,幾乎每個操做都須要它。它的做用在於獲取某個key在哈希表中的索引值,而後經過一些計算方法來計算出它的hashCode
,再對這個hashCode
進行取餘(hashCode
的值是很大的,這裏是爲了讓數據量變小),而這個size,就是哈希表的this.limit
,即哈希表的長度。編程語言
這裏的37實際上是一個底數,好比abc = 1*37^2 + 2*37^1 + 3
。這裏取一個合適的數就行了,不必定就要37,不過最好是質數。函數
HashTable.prototype.hashFunc = function (str, size) {
var hashCode = 0;
// 霍納算法,計算hashCode的值
for (var i = 0; i < str.length; i++) {
hashCode = 37 * hashCode + str.charCodeAt(i);
}
// 取餘
var index = hashCode % size;
return index;
}
複製代碼
這裏使用的是鏈地址法(基於數組,即每個下標值對應的是一個數組)post
由於在哈希表中,是沒有重複的值的,因此當用戶傳入一個(key, value)
時,若是原來不存在這個key,就是插入操做;若是存在,就是修改操做。操做步驟:測試
根據key獲取索引值(經過哈希算法),將數據插入到對應的位置this
var index = this.hashFunc(key, this.limit);
複製代碼
根據索引值取出這個位置:先判斷該位置是否存在一個數組,若是不存在就建立
var bucket = this.storage[index];
if (bucket == null) {
bucket = [];
this.storage[index] = bucket;
}
複製代碼
判斷是新增仍是修改原來的值 (key同樣就是修改,不同就是新增)
若是已經有這個key了,那麼就修改該key的值;
這裏的bucket是哈希表中其中一個索引值的引用,存儲多個key: value
的數組 (這些key經過哈希函數計算出來的索引值是想經過的)
for (var i = 0; i < bucket.length; i++) {
var tuple = bucket[i]
if (tuple[0] == key) {
tuple[1] = value;
return;
}
}
複製代碼
若是沒有,執行後續的添加操做;注意這裏push的是數組對象
bucket.push([key, value]);
this.count += 1;
複製代碼
這裏的前兩個步驟跟上面的添加或修改數據是相同的。先經過哈希函數算出這個key對應的索引值,根據這個索引值找到這個key所在的數組,若是存在,再從這個數組裏面找這個key對應的value。
根據key獲取對應的index
根據index獲取對應的位置bucket(數組)
判斷bucket是否爲null,若是爲null直接返回null
if (bucket == null) return null;
複製代碼
線性查找bucket中每個key是否等於傳入的key
若是等於就返回對應的value;若是遍歷結束了還沒找到就返回null
for (var i = 0; i < bucket.length; i++) {
var tuple = bucket[i]
if (tuple[0] == key) {
return tuple[1];
}
}
return null;
複製代碼
刪除操做在這裏思路是差很少的,只不過獲取操做獲取到這個key對應的value就返回,而刪除操做時把它刪除;再判斷語句中加入下面兩行代碼對獲取到的value值連帶key刪除掉便可。
bucket.splice(i, 1);
this.count -= 1;
複製代碼
其中,0, 3, 4, 5
分別爲轉換以後的哈希表索引值,當他們的索引值相同時,就會被以key = value
的形式存入到一個數組中。
var hash = new HashTable();
hash.put('mannqo', 123);
hash.put('mama', 222);
hash.put('Ytao', 321);
hash.put('fafa', 213);
hash.put('neinei', 123);
hash.remove('neinei');
console.log(hash.get('neinei'));
console.log(hash.get('mannqo'));
console.log(hash);
複製代碼
輸出結果:
能夠看到,刪除後的元素再次get
的話返回的是空,並且在哈希表中也看不到了。而索引值相同的mama
和fafa
被存放到索引值爲0的數組當中。
隨着數據量的增多,每個索引值對應的bucket(數組)長度也會愈來愈長,會致使效率的下降。這個是否就須要擴容哈希表。在合適的狀況下對數組擴容能夠提升效率,通常是在loadFactor(存放個數/哈希表長度)>0.75
的時候進行擴容。在擴容以後,要記得對全部數據項的位置進行修改(從新調用哈希函數,獲取不一樣的位置),由於擴容以後每個元素的位置也會發生改變。
保存舊的數據內容 var oldStorage = this.storage;
重置全部的屬性
this.storage = [];
this.count = 0;
this.limit = newLimt;
複製代碼
遍歷oldStorage
中全部的bucket
這裏要先判斷每個舊的bucket有沒有東西,若是沒有就繼續執行程序,若是有就把它存放到新的bucket中。
for (var i = 0; i < oldStorage.length; i++) {
var bucket = oldStorage[i];
if (bucket == null) {
continue;
}
for (var j = 0; j < bucket.length; j++) {
var tuple = bucket[j];
this.put(tuple[0], tuple[1]);
}
}
複製代碼
if (this.count > this.limit * 0.75) {
this.resize(this.limit * 2);
}
複製代碼
在刪除的元素過多的是否,也能夠對哈希表進行縮小容量,能夠把下面這段判斷語句加到刪除操做中。
if (this.limit > 7 && this.count < this.limit * 0.25) {
this.resize(Math.floor(this.limit / 2))
}
複製代碼