1

对JS散列表的简单学习

HashTable类也叫HashMap类,是Dictionary类的一种散列表实现方式。

散列算法的作用是尽可能快的在数据结构中找到一个值。

在之前的学习中,如果你想要获得数据结构中的一个值,需要遍历整个数据结构来找到它。如果使用散列函数,就能知道具体位置,也就能够快速找到该值。

使用最常见的散列函数--‘lose lose’散列函数,简单的将每个键值中的每个字母的ASCII码值相加,将得到的散列值作为地址。

(键值)John (散列函数)74+111+104+110 (散列值)399 形成散列表

地址 数据键值对
[399] john/john@email.com
...
[685] gandalf/@email.com

相关操作方法

创建一个散列表

function HashTable() {
  var table = [];
}

实现一个散列函数,即将ASCII码值相加的方法。

var loseloseHashTable = function(key) {
  var hash = 0;
  for(var i = 0; i < key.length; i++) {
    hash += key.charCodeAt(i);
  }
  return hash % 37; //这里只是为了得到比较小的数值,随便除了一个数
}

实现put(key, value)方法,向散列表中添加一个新的项。

this.put = function (key, value) {
  var position = loseloseHashTable(key); //得到散列值,即位置
  table[position] = value;
};

实现get(key)方法,返回根据键值检索到的特定的值。

this.get = function(key) {
  var position = loseloseHashTable(key);
  return table[position];
};

实现remove()方法,从散列中移除键值对应的数据值。

this.remove = function(key) {
  table[loseloseHashTable(key)] = undefined; //没有数据占据的位置默认值为undefined
};

具体使用方法这里就不赘述了,就是方法的调用。

冲突怎么办?

假如有多个键值得到的散列值相等,那么后面的元素会覆盖前面前面相同散列值的元素,
怎么解决呢?

  • 分离链接

分离链接法为散列表的每一个位置创建一个链表并将元素存储在里面。

地址 链表存储数据
[5] [no1/no1.com]指针-> [no2/no2.com]指针-> [X]null

在地址5上,链表实例上有两个元素no1.com和no2.com。

需要添加一个valuePair类,来表示将要加入链表的实例的元素。

var valuePair = function(key, value) {
  this.key = key;
  this.value = value;
  this.toString = function() {
    return `[${this.key} - ${this.value}]`;
  }
}

重写一个put()方法

this.put = function(key, value) {
  var position = loseloseHashTable(key);
  if(table[position] == undefined) {
    table[position] = new LinkedList(); //如果这个位置是第一次被加入元素,那么就初始化一个LinkedList实例
  }
  table[position].append(new valuePair(key, value)); //链表实现的append方法添加一个valuePair实例。
};

重写一个get(key)方法

this.get = function(key) {
  var position = loseloseHashTable(key);
  if(table[position] !== undefined) { //位置上有元素存在
    //遍历链表来寻找键/值
    var current = table[position].getHead();
    while (current.next) { //这里遍历不到链表最后一个位置
      if(current.element.key === key) {
        return current.element.value; //element属性是valuePair的实例,包含key和value属性
      }
      current = current.next;
    }

    //检查元素在链表第一个或者最后一个的情况
    if(current.element.key === key) {
      return current.element.value;
    }
  }
  return undefined; //位置上没有值
};

重写remove(key)方法

this.remove = function(key) {
  var position = loseloseHashTable(key);

  if(table[position] !== undefined) {
    var current = table[position].getHead();
    while (current.next) {
      if(current.element.key === key) {
        table[position].remove(current.element); //链表实现的remove方法
        if(table[position].isEmpty()) { //删除元素之后判断链表是否变空
          table[position] = undefined;
        }
        return true;
      }
      current = current.next;
    }

    //检查是否是第一个或者最后一个元素
    if(current.element.key === key) {
      table[position].remove(current.element);
      if(table[position].isEmpty()) {
        table[position] = undefined;
      }
      return true;
    }
  }
  return false;
}
  • 线性探查

如果索引为index的位置已经被占据了,就尝试index+1的位置,以此类推。

5的位置被占据,就寻找6的位置,6的位置被占据,就找7,7没被占据就赋值(本应该在位置5上,但是线性探查变成了位置7)

实现put(key, value)方法

this.put = function(key, value) {
  var position = loseloseHashTable(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);
  }
};

实现get()方法

this.get = function(key) {
  var position = loseloseHashTable(key);
  if(table[position] !== undefined) {
    if(table[position].key === key) { //举例位置5
      return table[position].value; //记号1
    } else {
      var index = ++position;
      while(table[index] !== undefined && (table[index] && table[index].key !== key)) { //该位置有元素但是不是要寻找的元素
        index ++; //索引增加
      }
      if(table[index] && table[index].key === key) { //确认正确性
        return table[index].value; //找到7 //记号2
      }
    }
  }
  return undefined;
};

实现remove()方法

只需要改变get方法的记号1和记号2的位置代码即可
改为table[index] = undefined;

更好的散列函数

var djb2HashCode = function(key) {
  var hash = 5381; //初始化一个hash变量并赋值为一个质数5381
  for(var i = 0; i < key.length; i++) { //遍历
    hash = hash * 33 + key.charCodeAt(i);
  }
  return hash % 1013;
}

相比来说,冲突会变少很多~


luckyziv
1.2k 声望52 粉丝

摸索中前进!!