JS 數據結構(三)--字典和散列表

前言

本文主要介紹字典及散列表相關內容,在ES2015 中已經對字典進行了實現 既Map類,本文中不會過多介紹Map ,學習的話能夠直接看阮一峯老師的ES6 Mapjavascript

字典

定義
  • 字典也稱作映射,符號表或者關聯數組
  • 以[鍵,值]的形式存儲
方法
  • set(key,value):向字典中添加新元素。若是 key 已經存在,那麼已存在的 value 會 被新的值覆蓋
  • remove(key):經過使用鍵值做爲參數來從字典中移除鍵值對應的數據值。
  • hasKey(key):若是某個鍵值存在於該字典中,返回 true,不然返回 false
  • get(key):經過以鍵值做爲參數查找特定的數值並返回。  clear():刪除該字典中的全部值
  • size():返回字典所包含值的數量。與數組的 length 屬性相似。
  • isEmpty():在 size 等於零的時候返回 true,不然返回 false。
  • keys():將字典所包含的全部鍵名以數組形式返回。
  • values():將字典所包含的全部數值以數組形式返回。
  • keyValues():將字典中全部[鍵,值]對返回。
  • forEach(callbackFn):迭代字典中全部的鍵值對。callbackFn 有兩個參數:key 和 value。該方法能夠在回調函數返回 false 時被停止(和 Array 類中的 every 方法類似)。
實現
class ValuePair {
  constructor(key, value) {
    this.key = key;
    this.value = value;
  }
  toString() {
    return `[#${this.key}: ${this.value}]`;
  }
}

class Dictionary {
  constructor() {
    this.table = {};
  }
  //將 key 轉化爲字 符串的函數
  toStrFn(item) {
    if (item === null) {
      return 'NULL';
    } else if (item === undefined) {
      return 'UNDEFINED';
    } else if (typeof item === 'string' || item instanceof String) {
      return `${item}`;
    }
    return item.toString(); // {1} 
  }

  hasKey(key) {
    return this.table[this.toStrFn(key)] != null;
  }

  set(key, value) {
    if (key != null && value != null) {
      const tableKey = this.toStrFn(key); // {1} 

      this.table[tableKey] = new ValuePair(key, value);
      return true;
    }
    return false;
  }

  remove(key) {
    if (this.hasKey(key)) {
      delete this.table[this.toStrFn(key)];
      return true;
    }
    return false;
  }

  get(key) {
    const valuePair = this.table[this.toStrFn(key)];
    return valuePair == null ? undefined : valuePair.value; // {2} 
  }
  keyValues() {
    return Object.values(this.table);
  }

  keys() {
    return this.keyValues().map(valuePair => valuePair.key);
  }
  values() {
    return this.keyValues().map(valuePair => valuePair.value);
  }

  forEach(callbackFn) {
    const valuePairs = this.keyValues();
    for (let i = 0; i < valuePairs.length; i++) {
      const result = callbackFn(valuePairs[i].key, valuePairs[i].value);
      if (result === false) {
        break;
      }
    }
  }
  size() {
    return Object.keys(this.table).length;
  }
  clear() {
    this.table = {};
  }

  toString() {
    if (this.isEmpty()) {
      return '';
    }
    const valuePairs = this.keyValues();
    let objString = `${valuePairs[0].toString()}`;
    for (let i = 1; i < valuePairs.length; i++) {
      objString = `${objString},${valuePairs[i].toString()}`;
    }
    return objString;
  }
  isEmpty() {
    return this.size() === 0;
  }


}


//使用
const dictionary = new Dictionary();
dictionary.set('Gandalf', 'gandalf@email.com');
dictionary.set('John', 'johnsnow@email.com');
dictionary.set('Tyrion', 'tyrion@email.com');

console.log(dictionary.hasKey('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('Gandalf')) //gandalf@email.com
複製代碼

散列表

定義
  • 散列表即HashTable類,也叫HashMap類,是Dictionary類(字典)的一種散列實現方式。
  • 散列算法的做用是儘量的在數據結構中找到一個值
  • 常見的散列函數——lose lose 散列函數,方法是簡單地將每一個鍵值中的每一個字母的 ASCII值相加
方法
  • put(key,value):向散列表增長一個新的項(也能更新散列表)
  • remove(key):根據鍵值從散列表中移除值。
  • get(key):返回根據鍵值檢索到的特定的值。
實現
class ValuePair {
 constructor(key, value) {
   this.key = key;
   this.value = value;
 }
 toString() {
   return `[#${this.key}: ${this.value}]`;
 }
}


class HashTable {
 constructor() {
   this.table = {};
 }
 //將 key 轉化爲字 符串的函數
 toStrFn(item) {
   if (item === null) {
     return 'NULL';
   } else if (item === undefined) {
     return 'UNDEFINED';
   } else if (typeof item === 'string' || item instanceof String) {
     return `${item}`;
   }
   return item.toString(); // {1} 
 }
 // key 的 每一個字符 ASCII 碼之和
 loseloseHashCode(key) {   
   if (typeof key === 'number') { 
     return key;  
   }  
   const tableKey = this.toStrFn(key); 
   let hash = 0; 
   for (let i = 0; i < tableKey.length; i++) { 
     hash += tableKey.charCodeAt(i); // {4} 
   }   
   return hash % 37; // {5} 
 } 

 hashCode(key) {  
   return this.loseloseHashCode(key); 
 }
 put(key, value) {   
   if (key != null && value != null) {
     const position = this.hashCode(key); 
     this.table[position] = new ValuePair(key, value); 
     return true;   
   }   
   return false; 
 }

 get(key) {   
   const valuePair = this.table[this.hashCode(key)];  
   return valuePair == null ? undefined : valuePair.value; 
 } 

 remove(key) {  
   const hash = this.hashCode(key);
   const valuePair = this.table[hash]; 
   if (valuePair != null) {     
     delete this.table[hash]; 
     return true;  
   }   
   return false; 
 }
 

}


const hash = new HashTable(); 
hash.put('Gandalf', 'gandalf@email.com'); 
hash.put('John', 'johnsnow@email.com'); 
hash.put('Tyrion', 'tyrion@email.com'); 

console.log(hash.hashCode('Gandalf') + ' - Gandalf'); //19 - Gandalf
console.log(hash.hashCode('John') + ' - John'); //29 - John
console.log(hash.hashCode('Tyrion') + ' - Tyrion') //16 - Tyrion

複製代碼

處理散列表中的衝突

定義

有時候一些鍵會有相同的鍵值。不一樣的的值在散列表中對應相同位置的時候,咱們稱其爲衝突。此時,當咱們經過相同的散列值去取屬性值的時候會出現相互覆蓋、數據丟失的狀況。處理衝突有幾種方法:分離連接,線性探查和雙散列法,前端

分離連接

定義
  • 分離連接法包括爲散列表的每個位置建立一個鏈表並將元素存儲在裏面
  • 在 HashTable 實例以外還須要額外的存儲空間
實現
class HashTableSeparateChaining {
  constructor() {
    this.table = {};
  }
  //將 key 轉化爲字 符串的函數
  toStrFn(item) {
    if (item === null) {
      return 'NULL';
    } else if (item === undefined) {
      return 'UNDEFINED';
    } else if (typeof item === 'string' || item instanceof String) {
      return `${item}`;
    }
    return item.toString(); // {1} 
  }
  // key 的 每一個字符 ASCII 碼之和
  loseloseHashCode(key) {   
    if (typeof key === 'number') { 
      return key;  
    }  
    const tableKey = this.toStrFn(key); 
    let hash = 0; 
    for (let i = 0; i < tableKey.length; i++) { 
      hash += tableKey.charCodeAt(i); // {4} 
    }   
    return hash % 37; // {5} 
  } 

  hashCode(key) {  
    return this.loseloseHashCode(key); 
  }
  put(key, value) {   
    if (key != null && value != null) {
      const position = this.hashCode(key);     
      if (this.table[position] == null) { //
        this.table[position] = new SinglyLinkedList(); // 爲 鏈表章節中的類 https://juejin.im/post/5e363960f265da3e51531be6#heading-19 
      }     
      this.table[position].push(new ValuePair(key, value)); 
      return true;  
    }   
    return false; 
  }

  get(key) {  
    const position = this.hashCode(key);  
    const linkedList = this.table[position]; 
    if (linkedList != null && !linkedList.isEmpty()) {    
      let current = linkedList.getHead(); 
      while (current != null) { 
        if (current.element.key === key) {
          return current.element.value; 
        }       
        current = current.next; 
      }  
    }   
    return undefined;  
  } 

  remove(key) {   
    const position = this.hashCode(key);   
    const linkedList = this.table[position];  
    if (linkedList != null && !linkedList.isEmpty()) {    
      let current = linkedList.getHead();     
      while (current != null) {       
        if (current.element.key === key) { 
          linkedList.remove(current.element); 
          if (linkedList.isEmpty()) { 
            delete this.table[position]; 
          }         
          return true; 
        }       
        current = current.next;     
      }   
    }   
    return false; 
  }
  

}
複製代碼

線性探查

定義
  • 處理衝突的方法是將元素直 接存儲到表中,而不是在單獨的數據結構中

當想向表中某個位置加入一個新元素的時候,若是索引爲index的位置已經被佔據了,就嘗試index+1的位置。若是index+1的位置也被佔據了,就嘗試index+2的位置,以此類推。示例代碼以下java

實現
class HashTableSeparateChaining {
  constructor() {
    this.table = {};
  }
  //將 key 轉化爲字 符串的函數
  toStrFn(item) {
    if (item === null) {
      return 'NULL';
    } else if (item === undefined) {
      return 'UNDEFINED';
    } else if (typeof item === 'string' || item instanceof String) {
      return `${item}`;
    }
    return item.toString(); // {1} 
  }
  // key 的 每一個字符 ASCII 碼之和
  loseloseHashCode(key) {   
    if (typeof key === 'number') { 
      return key;  
    }  
    const tableKey = this.toStrFn(key); 
    let hash = 0; 
    for (let i = 0; i < tableKey.length; i++) { 
      hash += tableKey.charCodeAt(i); // {4} 
    }   
    return hash % 37; // {5} 
  } 

  hashCode(key) {  
    return this.loseloseHashCode(key); 
  }
  put(key, value) {   
    if (key != null && value != null) {     
      const position = this.hashCode(key);     
      if (this.table[position] == null) {
        this.table[position] = new ValuePair(key, value);      
      } else {       
        let index = position + 1; 
        while (this.table[index] != null) { 
          index++;
        }       
        this.table[index] = new ValuePair(key, value); 
      }     
      return true;  
     }   
     return false; 
  }

  get(key) {   
    const position = this.hashCode(key);   
    if (this.table[position] != null) {      
      if (this.table[position].key === key) {       
        return this.table[position].value;    
      }     
      let index = position + 1;   
      while (this.table[index] != null && this.table[index].key !== key) { // {5} 
        index++;     
      }     
      if (this.table[index] != null && this.table[index].key === key) { // {6} 
        return this.table[position].value;    
      }  
    }   
    return undefined; 
  }

  remove(key) {   
    const position = this.hashCode(key);   
    if (this.table[position] != null) {     
      if (this.table[position].key === key) {       
        delete this.table[position]; // {1} 
        this.verifyRemoveSideEffect(key, position); 
        return true;     
      }     
      let index = position + 1;     
      while (this.table[index] != null && this.table[index].key !== key ) {       
        index++;     
      }     
      if (this.table[index] != null && this.table[index].key === key) {       
        delete this.table[index]; // {3} 
        this.verifyRemoveSideEffect(key, index); // {4} 
        return true;     
      }   
    }   
    return false; 
  }
  //反作用驗證
  verifyRemoveSideEffect(key, removedPosition) {   
    const hash = this.hashCode(key); // {1} 
    let index = removedPosition + 1; // {2} 
    while (this.table[index] != null) { // {3} 
      const posHash = this.hashCode(this.table[index].key); // {4} 
      if (posHash <= hash || posHash <= removedPosition) { // {5} 
        this.table[removedPosition] = this.table[index]; // {6} 
        delete this.table[index];      
        removedPosition = index;     
      }     
      index++;   
    }
  }
}
複製代碼

結語

前端界的一枚小學生es6

相關文章
相關標籤/搜索