前端學數據結構之字典和散列表

字典

集合表示一組互不相同的元素(不重複的元素)。在字典中,存儲的是[鍵,值]對,其中鍵名是用來查詢特定元素的。字典和集合很類似,集合以[值,值]的形式存儲元素,字典則是以[鍵,值]的形式來存儲元素。字典也稱做映射。node

字典如同ES6中的Map類。

相關功能數組

set(key,value):向字典中添加新元素。
delete(key):經過使用鍵值來從字典中移除鍵值對應的數據值。
has(key):若是某個鍵值存在於這個字典中,則返回true,反之則返回false。
get(key):經過鍵值查找特定的數值並返回。
clear():將這個字典中的全部元素所有刪除。
size():返回字典所包含元素的數量。與數組的length屬性相似。
keys():將字典所包含的全部鍵名以數組形式返回。
values():將字典所包含的全部數值以數組形式返回。

代碼微信

function Dictionary(){

  var items = {};

  // 添加
  this.set = function(key, value){
      items[key] = value; 
  };

  // 刪除
  this.delete = function(key){
      if (this.has(key)){
          delete items[key];
          return true;
      }
      return false;
  };

  // 判斷是否有某個key值
  this.has = function(key){
      return key in items;
  };

  // 經過某個key獲取value
  this.get = function(key) {
      return this.has(key) ? items[key] : undefined;
  };

  // 清楚
  this.clear = function(){
      items = {};
  };

  // 長度
  this.size = function(){
      return Object.keys(items).length;
  };

  // 鍵
  this.keys = function(){
      return Object.keys(items);
  };

  // 值
  this.values = function(){
      return Object.values(items);
  };

  // 字典
  this.getItems = function(){
      return items;
  }
}

驗證數據結構

var dictionary = new Dictionary(); 
dictionary.set('Gandalf', 'gandalf@email.com'); 
dictionary.set('John', 'johnsnow@email.com'); 
dictionary.set('Tyrion', 'tyrion@email.com');
console.log(dictionary.has('Gandalf')); // true
console.log(dictionary.size()); // 3
console.log(dictionary.keys()); // ["Gandalf", "John", "Tyrion"]
console.log(dictionary.values()); // ["gandalf@email.com", "johnsnow@email.com", "tyrion@email.com"] 
console.log(dictionary.get('Tyrion')); // tyrion@email.com
dictionary.delete('John');
console.log(dictionary.keys()); // ["Gandalf", "Tyrion"] 
console.log(dictionary.values());// ["gandalf@email.com", "tyrion@email.com"]
console.log(dictionary.getItems()); // Object {Gandalf: "gandalf@email.com", Tyrion: "tyrion@email.com"}

散列表

1、哈希表app

一、概念函數

哈希表(Hash Table)也叫散列表,是依據關鍵碼值(Key Value)而直接進行訪問的數據結構。它經過把關鍵碼值映射到哈希表中的一個位置來訪問記錄,以加快查找的速度。

這個映射函數就作散列函數。存放記錄的數組叫作散列表。this

二、散列存儲的基本思路spa

以數據中每個元素的keyword爲自變量。經過散列函數H(k)計算出函數值,以該函數值做爲一塊連續存儲空間的的單元地址,將該元素存儲到函數值相應的單元中。

三、哈希表查找的時間複雜度code

哈希表存儲的是鍵值對,其查找的時間複雜度與元素數量多少無關。哈希表在查找元素時是經過計算哈希碼值來定位元素的位置從而直接訪問問元素的,所以,哈希表查找的時間複雜度爲O(1)。

2、常用的哈希函數blog

1.直接尋址法

取keyword或者keyword的某個線性函數值做爲哈希地址,即H(Key)=Key或者H(Key)=a*Key+b(a,b爲整數),這樣的散列函數也叫作自身函數.假設H(Key)的哈希地址上已經有值了,那麼就往下一個位置找,知道找到H(Key)的位置沒有值了就把元素放進去.

2.數字分析法

分析一組數據,比方一組員工的出生年月,這時咱們發現出生年月的前幾位數字通常都一樣,所以,出現衝突的機率就會很是大,但是咱們發現年月日的後幾位表示月份和詳細日期的數字區別很是大,假設利用後面的幾位數字來構造散列地址,則衝突的概率則會明顯減小.所以數字分析法就是找出數字的規律,儘量利用這些數據來構造衝突概率較低的散列地址.

3.平方取中法

取keyword平方後的中間幾位做爲散列地址.一個數的平方值的中間幾位和數的每一位都有關。

所以,有平方取中法獲得的哈希地址同keyword的每一位都有關。是的哈希地址具備較好的分散性。

該方法適用於keyword中的每一位取值都不夠分散或者較分散的位數小於哈希地址所需要的位數的狀況。

4.摺疊法

摺疊法即將keyword切割成位數一樣的幾部分,最後一部分位數可以不一樣,而後取這幾部分的疊加和(注意:疊加和時,去除進位)做爲散列地址.數位疊加可以有移位疊加和間界疊加兩種方法.移位疊加是將切割後的每一部分的最低位對齊,而後相加;間界疊加是從一端向還有一端沿切割界來回摺疊,而後對齊相加.

5.隨機數法

選擇一個隨機數,去keyword的隨機值做爲散列地址,通常用於keyword長度不一樣的場合.

6.除留餘數法

取keyword被某個不大於散列表表長m的數p除後所得的餘數爲散列地址.即H(Key)=Key MOD p,p<=m.不只可以對keyword直接取模,也可在摺疊、平方取中等運算以後取模。對p的選擇很是重要,通常取素數或m,若p選得很差。則很是easy產生衝突。

通常p取值爲表的長度爲表的長度。

HashTable類

也叫HashMap類,是Dictionary類的一種散列表實現方式
企業微信截圖_15735537168763.png

散列值比較大,能夠用算出的散列值除以一個數取餘
image.png

散列值可能相同,存在衝突,處理衝突
處理衝突有幾種方法:分離連接、線性探查和雙散列法
分離連接

分離連接法包括爲散列表的每個位置建立一個鏈表並將元素存儲在裏面。它是解決衝突的最簡單的方法,可是它在HashTable實例以外還須要額外的存儲空間
image.png

// 鏈表
function LinkedList() {

    let Node = function(element){

        this.element = element;
        this.next = null;
    };

    let length = 0;
    let head = null;

    this.append = function(element){

        let node = new Node(element),
            current;

        if (head === null){
            head = node;
        } else {

            current = head;
            while(current.next){
                current = current.next;
            }

            current.next = node;
        }

        length++; 
    };

    this.isEmpty = function() {
        return length === 0;
    };

    this.getHead = function(){
        return head;
    };
    
    
    this.toString = function(){

        let current = head,
            string = '';

        while (current) {
            string += current.element + (current.next ? ', ' : '');
            current = current.next;
        }
        return string;

    };
}


function HashTableSeparateChaining(){
    // 散列表
    var table = [];
    // 使用append方法向LinkedList實例中添加一個ValuePair實例(鍵和值)
    var ValuePair = function(key, value){
     this.key = key;
     this.value = value;
     this.toString = function() {
      return '[' + this.key + ' - ' + this.value + ']'; 
      }
    };
    // 散列函數
    var loseloseHashCode = function (key) {
        var hash = 0;
        for (var i = 0; i < key.length; i++) {
            hash += key.charCodeAt(i);
        }
        return hash % 37;
    };

    var hashCode = function(key){
        return loseloseHashCode(key);
    };
    
    // 添加
    this.put = function(key, value){
        var position = hashCode(key);
        console.log(position + ' - ' + key);

        if (table[position] == undefined) {
            table[position] = new LinkedList();
        }
        table[position].append(new ValuePair(key, value));
    };
    // 查找
    this.get = function(key) {
        var position = hashCode(key);

        if (table[position] !== undefined  && !table[position].isEmpty()){

            //獲取頭節點
            var current = table[position].getHead();

            do {
                if (current.element.key === key){
                    return current.element.value;
                }
                current = current.next;
            } while(current);
        }
        return undefined;
    };
    
    // 刪除
    this.remove = function(key){

        var position = hashCode(key);

        if (table[position] !== undefined){

           //獲取頭節點
            var current = table[position].getHead();

            do {
                if (current.element.key === key){
                    table[position].remove(current.element);
                    if (table[position].isEmpty()){
                        table[position] = undefined;
                    }
                    return true;
                }
                current = current.next;
            } while(current);
        }

        return false;
    };

    // 表裏全部數據
    this.print = function() {
        for (var i = 0; i < table.length; ++i) {
            if (table[i] !== undefined) {
               console.log(i+'----'+table[i].toString());
            }
        }
    };
}

驗證

var hash = new HashTableSeparateChaining();
hash.put('Gandalf', 'gandalf@email.com');
hash.put('John', 'johnsnow@email.com');
hash.put('Tyrion', 'tyrion@email.com');
hash.put('Aaron', 'aaron@email.com');
hash.put('Donnie', 'donnie@email.com');
hash.put('Ana', 'ana@email.com');
hash.put('Jonathan', 'jonathan@email.com');
hash.put('Jamie', 'jamie@email.com');
hash.put('Sue', 'sue@email.com');
hash.put('Mindy', 'mindy@email.com');
hash.put('Paul', 'paul@email.com');
hash.put('Nathan', 'nathan@email.com');

hash.print();
線性探查

另外一種解決衝突的方法是線性探查。當想向表中某個位置加入一個新元素的時候,若是索引爲index的位置已經被佔據了,就嘗試index+1的位置。若是index+1的位置也被佔據了,就嘗試index+2的位置
image.png

function HashLinearProbing(){

    var table = [];
    var ValuePair = function(key, value){
        this.key = key;
        this.value = value;

        this.toString = function() {
            return '[' + this.key + ' - ' + this.value + ']';
        }
    };
    // 散列函數
    var loseloseHashCode = function (key) {
        var hash = 0;
        for (var i = 0; i < key.length; i++) {
            hash += key.charCodeAt(i);
        }
        return hash % 37;
    };

    var hashCode = function(key){
        return loseloseHashCode(key);
    };
    
    // 添加
    this.put = function(key, value){
        var position = hashCode(key);
        console.log(position + ' - ' + key);

        if (table[position] == undefined) {
            table[position] = new ValuePair(key, value);
        } else {
            var index = ++position;
            while (table[index] != undefined){
                index++;
            }
            table[index] = new ValuePair(key, value);
        }
    };

    // 刪除
    this.remove = function(key){
        var position = hashCode(key);

        if (table[position] !== undefined){
            if (table[position].key === key) {
                table[position] = undefined;
            } else {
                var index = ++position;
                while (table[index] === undefined || table[index].key !== key){
                    index++;
                }
                if (table[index].key === key) {
                    table[index] = undefined;
                }
            }
        }
    };

    // 輸出
    this.print = function() {
        for (var i = 0; i < table.length; ++i) {
            if (table[i] !== undefined) {
                console.log(i + ' -> ' + table[i].toString());
            }
        }
    };
}
5 -> [Jonathan - jonathan@email.com]
6 -> [Jamie - jamie@email.com]
7 -> [Sue - sue@email.com]
10 -> [Nathan - nathan@email.com]
13 -> [Donnie - donnie@email.com]
14 -> [Ana - ana@email.com]
16 -> [Tyrion - tyrion@email.com]
17 -> [Aaron - aaron@email.com]
19 -> [Gandalf - gandalf@email.com]
29 -> [John - johnsnow@email.com]
32 -> [Mindy - mindy@email.com]
33 -> [Paul - paul@email.com]
更好的散列函數

個能夠實現的比「loselose」更好的散列函數是djb2:

var djb2HashCode = function (key) {
 var hash = 5381; //{1}
 for (var i = 0; i < key.length; i++) { //{2}
     hash = hash * 33 + key.charCodeAt(i); //{3}
 }
 return hash % 1000; //{4}
};
相關文章
相關標籤/搜索