摘要:定義散列表是字典鍵值對的一種實現(xiàn)方式。根據(jù)鍵值從散列表中移除值。分離鏈接分離鏈接法在散列表的每一個位置創(chuàng)建一個鏈表并將元素存儲在里面。一個表現(xiàn)良好的散列函數(shù)應(yīng)該有較好的插入和查找性能且有較低的沖突可能性。
定義
散列表是字典(鍵、值對)的一種實現(xiàn)方式。每次在字典中獲取一個值,都需要重復(fù)遍歷字典,如果用散列表,字典中的每個key都對應(yīng)一個確定的位置,從而不再需要遍歷。
以電子郵件地址簿為例,每個名字(key)對應(yīng)一個郵件地址,用散列函數(shù)計算每個key在散列表中的位置(這里使用key的所有字符的ASCII碼值相加),如圖:
put(key,value):向散列表增加一個新的項(也能更新散列表)。
remove(key):根據(jù)鍵值從散列表中移除值。
get(key):返回根據(jù)鍵值檢索到的特定的值。
實現(xiàn)function HashTable() { // 私有變量table,作為散列表的載體 var table = []; // 散列函數(shù),計算key對應(yīng)的hash值 var loseloseHashCode = function (key) { var hash = 0; for (var i = 0; i < key.length; i++) { hash += key.charCodeAt(i); // 所有字符的ASCII碼值相加 } // 為了將hash值變?yōu)楦〉闹担砸粋€數(shù)并取余數(shù) // 這里除以素數(shù)37是為了降低計算出重復(fù)hash的概率(后續(xù)會處理hash重復(fù)的問題) return hash % 37; }; // put方法,向散列表增加一個新的項(也能更新散列表) this.put = function(key, value) { var position = loseloseHashCode(key); // 計算key的hash值作為當(dāng)前數(shù)據(jù)在散列表中的位置 table[position] = value; // 將當(dāng)前數(shù)據(jù)插入散列表 }; // get方法,返回根據(jù)鍵值檢索到的特定的值 this.get = function (key) { return table[loseloseHashCode(key)]; //根據(jù)key計算出的hash取對應(yīng)位置中的值 }; // remove方法,根據(jù)鍵值從散列表中移除值 this.remove = function(key) { table[loseloseHashCode(key)] = undefined; }; }
到這里,一個基本的的散列表已經(jīng)實現(xiàn)了,但沒有考慮散列函數(shù)計算出重復(fù)hash值的問題,這會導(dǎo)致后添加的數(shù)據(jù)覆蓋先添加的數(shù)據(jù),比如:
var table = new HashTable(); // Jamie和Sue的hash值都為5,因此Sue的數(shù)據(jù)會覆蓋Jamie的數(shù)據(jù) table.put("Jamie", "Jamie@qq.com"); table.put("Sue", "Sue@gmail.com");
處理上述沖突的方式主要有:分離鏈接、線性探查,雙散列法,這里使用前兩種。
分離鏈接分離鏈接法在散列表的每一個位置創(chuàng)建一個鏈表并將元素存儲在里面。它的缺點是在HashTable實例之外還需要額外的存儲空間。如圖,散列表的每一個位置都是一個鏈表,鏈表里可以存儲多個數(shù)據(jù)。
下面,重寫put、get、remove方法,實現(xiàn)散列表的分離鏈接(其中鏈表類的實現(xiàn)參照鏈表)。
// 首先要添加一個新的輔助類來實例化添加到鏈表的元素 var ValuePair = function(key, value){ this.key = key; this.value = value; }; // 改寫put方法 this.put = function(key, value){ var position = loseloseHashCode(key); if (table[position] == undefined) { // 在當(dāng)前位置示例化一個鏈表 table[position] = new LinkedList(); } // 在鏈表中添加元素 table[position].append(new ValuePair(key, value)); }; // 改寫get方法 this.get = function(key) { var position = loseloseHashCode(key); if (table[position] !== undefined){ // 獲取鏈表的第一個元素 var current = table[position].getHead(); // 遍歷鏈表(這里不能遍歷到最后一個元素,后續(xù)特殊處理) while(current.next){ // 如果鏈表中存在當(dāng)前key對應(yīng)的元素,返回其值 if (current.element.key === key){ return current.element.value; } // 處理下一個元素 current = current.next; } // 處理鏈表只有一個元素的情況或處理鏈表的最后一元素 if (current.element.key === key){ return current.element.value; } } // 不存在值,返回undefined return undefined; }; // 改寫remove方法 this.remove = function (key) { var position = loseloseHashCode(key); if (table[position] !== undefined) { // 獲取當(dāng)前位置鏈表的第一個元素 var current = table[position].getHead(); // 遍歷鏈表(這里不能遍歷到最后一個元素,后續(xù)特殊處理) while (current.next) { if (current.element.key === key) { // 遍歷到對應(yīng)元素,從鏈表中刪除 table[position].remove(current.element); if (table[position].isEmpty()) { // 如果鏈表已經(jīng)空了,將散列表的當(dāng)前位置置為undefined table[position] = undefined; } // 返回true表示刪除成功 return true; } // 處理下一個元素 current = current.next; } // 處理鏈表只有一個元素的情況或處理鏈表的最后一元素 if (current.element.key === key) { table[position].remove(current.element); if (table[position].isEmpty()) { table[position] = undefined; } return true; } } // 要刪除的元素不存在,返回false return false; };線性探查
線性探查法在向散列表中插入元素時,如果插入位置position已經(jīng)被占據(jù),就嘗試插入position+1的位置,以此類推,直到找到空的位置。下面用線性探查的方式重寫put、get、remove方法
// 重寫put方法 this.put = function(key, value){ var position = loseloseHashCode(key); // 依次查找,如果當(dāng)前位置不為空,position + 1,直到找到為空的位置為止 while (table[position] != undefined){ position++; } table[position] = new ValuePair(key, value); }; // 重寫get方法 this.get = function(key) { var position = loseloseHashCode(key); var len = table.length; // 只要當(dāng)前位置小于散列表長度就要查找 if (position < len){ // 由于查找的值可能是以 position + 1 的形式類推,找到空位后插入的 // 因此需要從當(dāng)前位置(position)開始查找,直到找到key相同的位置,或者找完整個散列表 while (position < len && (table[position] === undefined || table[position].key !== key)){ position++; } // 如果最終position >= len,說明沒找到 if (position >= len) { return undefined } else { // 否則說明找到了,返回對應(yīng)值 return table[position].value; } } // 如果當(dāng)前位置為空,說明添加時沒有累加position,直接返回undefined return undefined; }; // 改寫remove方法 this.remove = function(key) { var position = loseloseHashCode(key); var len = table.length; if (position < len){ // 從當(dāng)前位置(position)開始查找,直到找到key相同的位置,或者找完整個散列表 while (position < len && (table[position] === undefined || table[position].key !== key)){ position++; } // 如果最終position < len,說明找到了,將對應(yīng)位置數(shù)據(jù)刪除 if (position < len) { table[position] = undefined; } } };更好的散列函數(shù)
上述散列函數(shù)表現(xiàn)并不好,它極易計算出相同的hash值,從而導(dǎo)致沖突。一個表現(xiàn)良好的散列函數(shù)應(yīng)該有較好的插入和查找性能且有較低的沖突可能性。下面的散列函數(shù),被證明是比較合適的。
var djb2HashCode = function (key) { var hash = 5381; // 一個較大的素數(shù)基準(zhǔn)值 for (var i = 0; i < key.length; i++) { hash = hash * 33 + key.charCodeAt(i); // 基準(zhǔn)值乘以33再加ASCII碼值 } return hash % 1013; //除以1013取余 };
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/81888.html
摘要:小結(jié)實現(xiàn)了字典和哈希表感覺沒有想象中那么困難,當(dāng)然這還是開始。 本系列所有文章:第一篇文章:學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)與算法之棧與隊列第二篇文章:學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)與算法之鏈表第三篇文章:學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)與算法之集合第四篇文章:學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)與算法之字典和散列表第五篇文章:學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)與算法之二叉搜索樹 字典 不是新華字典哦,這里的字典也稱作_映射_,是一種數(shù)據(jù)結(jié)構(gòu),跟set集合很相似的一種數(shù)據(jù)結(jié)構(gòu),都可以用來...
摘要:我經(jīng)常在業(yè)務(wù)代碼中把數(shù)據(jù)處理成這種字典的數(shù)據(jù)結(jié)構(gòu)獲取的方法哈希表在學(xué)習(xí)了類之后,我們會學(xué)習(xí)散列表,也就是哈希表。 《Javascript數(shù)據(jù)結(jié)構(gòu)和算法》筆記-「字典和散列表」 集合、字典、散列表存儲的都是「不重復(fù)」的數(shù)據(jù)結(jié)構(gòu) 集合:我們更關(guān)注每一個元素的值,并把其作為主要元素 字典:我們用[鍵,值]的形式來存儲數(shù)據(jù) 散列表: 跟字典類似,也會是用[鍵,值]的形式來存儲數(shù)據(jù) 但是「字...
摘要:散列表上面的地圖向我們展示了如何用廣度優(yōu)先搜索的思想找到北京到廣州的最短路線。在廣度優(yōu)先搜索中,我們需要用到隊列的這種思想來實現(xiàn)查找。建立了下面這個模型武漢廣州西藏上海上海武漢廣州代碼完整實現(xiàn),利用遞歸和廣度優(yōu)先搜索的思想實現(xiàn)。 什么是廣度優(yōu)先搜索? 如果只是是背概念,幼兒園的小朋友都能背下來念給你聽。 假設(shè)看這篇文章的都和我一樣是個前端工程師,我們要從廣度優(yōu)先搜索(BFS)中學(xué)到什么...
閱讀 3517·2023-04-25 22:45
閱讀 1310·2021-11-11 16:54
閱讀 2818·2019-08-30 15:44
閱讀 3208·2019-08-30 15:44
閱讀 1668·2019-08-30 13:55
閱讀 968·2019-08-29 18:45
閱讀 1222·2019-08-29 17:25
閱讀 1034·2019-08-29 12:59