学习js数据结构与算法-散列表(2)

123 阅读3分钟

这是我参与2022首次更文挑战的第24天,活动详情查看:2022首次更文挑战」。

个人觉得要想进大厂,就必须学习了解算法和数据结构,经历了多次面试之后,也知道了算法和数据结构的重要性,所以,我也打算从今天开始,进入学习,下载了一下电子书,边看书,边跟着书的例子来敲一下代码,下面就开始总结一下对算法和数据结构的学习吧。

第二十四天:继续学习散列表

解决散列表的冲突

会有相同键的时候,不同的值在散列表中对应相同的位置的时候,就会冲突。例如下面的代码

const hash = new HashTable();
hash.put('Ygritte', 'ygritte@email.com');
hash.put('Jonathan', 'jonathan@email.com');
hash.put('Jamie', 'jamie@email.com');
hash.put('Jack', 'jack@email.com');
hash.put('Jasmine', 'jasmine@email.com');
hash.put('Jake', 'jake@email.com');
hash.put('Nathan', 'nathan@email.com');
hash.put('Athelstan', 'athelstan@email.com');
hash.put('Sue', 'sue@email.com');
hash.put('Aethelwulf', 'aethelwulf@email.com');
hash.put('Sargeras', 'sargeras@email.com');

通过对每个提到的名字调用 hash.hashCode 方法,输出结果如下。 4 - Ygritte 5 - Jonathan 5 - Jamie 7 - Jack 8 - Jasmine 9 - Jake 10 - Nathan 7 - Athelstan 5 - Sue 5 - Aethelwulf 10 - Sargeras

  • toString

    为了获取上面代码执行的结果,我们实现一下toString方法

    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
    }
    

    调用这个toString方法之后,获得下面的输出

    {4 => [#Ygritte: ygritte@email.com]}

    {5 => [#Aethelwulf: aethelwulf@email.com]}

    {7 => [#Athelstan: athelstan@email.com]}

    {8 => [#Jasmine: jasmine@email.com]}

    {9 => [#Jake: jake@email.com]}

    {10 => [#Sargeras: sargeras@email.com]}

    由于有几个key的散列值相同,导致了冲突,下一个推进去的值覆盖了上一个值。既然有冲突,那么就需要有解决冲突的方法,解决冲突有几种方法:分离链接、线性探查和双散列法。

分离链接

分离链接法需要结合链表,在table的每一个位置保存的都是一个链表结构,就像下图

image.png

重新创建一个基本结构

class HashTableSeparateChaining {
  constructor(toStrFn = defaultToString) {
    this.toStrFn = toStrFn
    this.table = {}
  }
}

我们来重新实现一下上面三个方法

  • put

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

    第一次加入的时候,我们需要初始化一个链表的实例,如果不是第一次添加,那么需要在这个链表后面添加一个数据。

  • get

    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
    }
    

    首先需要知道table有没有这个值,同时需要知道当前链表是不是空的,如果不是,则直接返回undefied,如果有,则需要遍历table这个值的链表,找到头部,while循环遍历下去。如果key值相同就返回这个值

  • remove

    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
    }
    

    remove方法和get方法查找的方法一样,当找到后,需要移除链表的值,移除完之后还需要知道链表空了没有,如果空了,那么久删除table的这个值