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

相关文章
相关标签/搜索