散列表

194 阅读1分钟
  • 散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数
function defaultToString(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();
}

class ValuePair {
  constructor(key, value) {
    this.key = key;
    this.value = value;
  }
  toString() {
    return `[#{this.key}: ${this.value}]`;
  }
}

class HashTable {
  constructor(toStrFn = defaultToString) {
    this.toStrFn = toStrFn;
    this.table = {};
  }
  //散列函数
  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);
    }
    return hash % 37;
  }
  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;
  }
  getTable() {
    return this.table;
  }

  isEmpty() {
    return this.size() === 0;
  }

  size() {
    return Object.keys(this.table).length;
  }

  clear() {
    this.table = {};
  }

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

散列表中的冲突问题

  • 有时候一些键会有相同的散列值。不同的值在散列表对应的位置的时候。我们称其为冲突;

  • 解决办法:

    1. 分离链接法:分离链接法包括为散列表的每一个位置创建一个==链表==并将元素存储在里面。它是解决冲突的最简单的方法,但是在 HashTable 实例之外还需要额外的存储空间。

      class HashTableSepareChaining {
        constructor(toStrFn = defaultToString) {
          this.toStrFn = toStrFn;
          this.table = {};
        }
        put(key, value) {
          if (key != null && value != null) {
            const position = this.hashCode(key);
            if (this.table[positon] == null) {
              this.table[position] = new LinkedList();
            }
            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;
        }
      }
      
      
    2. 线性探查:将元素直接存储在表中,而不是在单独的数据结构中。

    • 过程:当想向表中的某个位置添加一个新元素的时候,如果索引为position的位置已经被占据了,就会尝试position+1的位置,如果position+1的位置也被占据了,就会尝试position+2的位置,以此类推,直到到在散列表中找到一个空闲的位置。
    • 线性探查分为两种:
        1. 软删除方法:我们使用一个特殊的值来表示键值对被删除了,而是真正的删除他。(经过一段时间后会逐渐减低散列表的效率)
        2. 挪动法:需要检验是否有将一个或者多个元素移动到之前的位置。当搜索一个键的时候,==这种方法可以避免找到一个空的位置==。
    //1. 软删除方法
    function defaultToString(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();
    }
    
    class ValuePairLazy {
      constructor(key, value, isDeleted = false) {
        this.key = key;
        this.value = value;
        this.isDeleted = isDeleted;
      }
      toString() {
        return `[#{this.key}: ${this.value}]`;
      }
    }
    
    
    class HashTableLinearProbingLazy {
      constructor(toStrFn = defaultToString) {
        this.toStrFn = toStrFn;
        this.table = {};
      }
    
      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);
        }
        return hash % 37;
      }
    
      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] != null && this.table[position].isDeleted)
          ) {
            this.table[position] = new ValuePairLazy(key, value);
          } else {
            let index = position + 1;
            while (this.table[index] != null && !this.table[position].isDeleted) {
              index++;
            }
            this.table[index] = new ValuePairLazy(key, value);
          }
          return true;
        }
        return false;
      }
    
      get(key) {
        const position = this.hashCode(key);
        if (this.table[position] != null) {
          if (this.table[position].key === key && !this.table[position].isDeleted) {
            return this.table[position].value;
          }
          let index = position + 1;
          while (
            this.table[index] != null
            && (this.table[index].key !== key || this.table[index].isDeleted)
          ) {
            if (this.table[index].key === key && this.table[index].isDeleted) {
              return undefined;
            }
            index++;
          }
          if (
            this.table[index] != null
            && this.table[index].key === key
            && !this.table[index].isDeleted
          ) {
            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 && !this.table[position].isDeleted) {
            this.table[position].isDeleted = true;
            return true;
          }
          let index = position + 1;
          while (
            this.table[index] != null
            && (this.table[index].key !== key || this.table[index].isDeleted)
          ) {
            index++;
          }
          if (
            this.table[index] != null
            && this.table[index].key === key
            && !this.table[index].isDeleted
          ) {
            this.table[index].isDeleted = true;
            return true;
          }
        }
        return false;
      }
    }
    
    //2.挪动法
    class ValuePair {
      constructor(key, value) {
        this.key = key;
        this.value = value;
      }
      toString() {
        return `[#{this.key}: ${this.value}]`;
      }
    }
    
    class HashTableLinearProbing {
      constructor(toStrFn = defaultToString) {
        this.toStrFn = toStrFn;
        this.table = {};
      }
    
      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);
        }
        return hash % 37;
      }
    
      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) {
            index++;
          }
          if (this.table[index] != null && this.table[index].key === key) {
            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];
            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];
            this.verifyRemoveSideEffect(key, index);
            return true;
          }
        }
        return false;
      }
    
      verifyRemoveSideEffect(key, removedPosition) {
        const hash = this.hashCode(key);
        let index = removedPosition + 1;
        while (this.table[index] != null) {
          const posHash = this.hashCode(this.table[index].key);
          if (posHash <= hash || posHash <= removedPosition) {
            this.table[removedPosition] = this.table[index];
            delete this.table[index];
            removedPosition = index;
          }
          index++;
        }
      }
    }