散列表的概念
HashTable类,也叫HashMap类,是Dictionary类的一种散列表实现方式。
散列算法的作用是尽可能快地在数据结构中找到一个值。如果要在数据结构中获得一个值(使用get方法),需要遍历整个数据结构来找到它。如果使用散列函数,就知道值的具体位置,因此能够快速检索到该值。散列函数的作用是给定一个键值,然后返回值在表中的地址。
最简单无处理冲突的散列表结构
function HashTable() {
this.table = [];
var loseloseHashCode = function (key) {};
if(typeof this.put != 'function') {
HashTable.prototype.put = function(key, value) {};
HashTable.prototype.remove = function(key) {};
HashTable.prototype.get = function(key) {};
HashTable.prototype.print = function() {};
}
}
- loseloseHashCode(key):散列函数,定制哈希值
- put(key,value):向散列表增加一个新的项(也能更新散列表)。
- remove(key):根据键值从散列表中移除值
- get(key):返回根据键值检索到的特定的值
- print():打印散列表
源代码
function HashTable() {
this.table = [];
var loseloseHashCode = function (key) {
var hash = 0;
for(var i = 0; i < key.length; i++) {
hash += key[i].charCodeAt(0);
}
return hash % 37;
};
if(typeof this.put != 'function') {
HashTable.prototype.put = function(key, value) {
var position = loseloseHashCode(key);
this.table[position] = value;
};
HashTable.prototype.remove = function(key) {
this.table[loseloseHashCode(key)] = undefined;
};
HashTable.prototype.get = function(key) {
return this.table[loseloseHashCode(key)];
};
HashTable.prototype.print = function() {
for (var i = 0; i < this.table.length; ++i) {
if (this.table[i] !== undefined) {
console.log(i + ": " + this.table[i]);
}
}
};
}
}
问题
当哈希值相同时,会有冲突,新的值会覆盖旧的值,所以要处理冲突
处理冲突方法一,分离链接
分离链接法包括为散列表的每一个位置创建一个链表并将元素存储在里面。它是解决冲突的最简单的方法,但是它在HashTable实例之外还需要额外的存储空间。
需要用到单链表结构
function LinkedList() {
var Node = function(element) {
this.element = element;
this.next = null;
};
this.length = 0;
this.head = null;
if(typeof this.append != 'function') {
//表尾添加一个新的项
LinkedList.prototype.append = function(element) {
var node = new Node(element);
var current;
if(!this.head) {
this.head = node;
} else {
current = this.head;
while(current.next) {
current = current.next;
}
current.next = node;
}
++this.length;
}
//列表特定位置插入一个新的项
LinkedList.prototype.insert = function(position, element) {
var current, previous;
var index = 0;
var node = new Node(element);
if(position>=0 && position<=this.length) {
if(position == 0) {
node.next = this.head;
this.head = node;
} else {
current = this.head;
while(index < position) {
previous = current;
current = current.next;
index++;
}
node.next = current;
previous.next = node;
}
this.length++;
}
};
//根据位置从列表移除一项
LinkedList.prototype.removeAt = function(position) {
var current, previous;
var index = 0;
if(position>-1 && position<this.length) {
if(position == 0) {
this.head = this.head.next;
} else {
current = this.head;
while(index < position) {
previous = current;
current = current.next;
index++;
}
previous.next = current.next;
}
--this.length;
return true;
} else {
return false;
}
};
//移除特定元素的项
LinkedList.prototype.remove = function(element) {
var index = this.indexOf(element);
this.removeAt(index);
};
//返回索引
LinkedList.prototype.indexOf = function(element) {
var index = 0;
var current = this.head;
while(current) {
if(current.element == element) {
return index;
} else {
current = current.next;
index++;
}
}
return -1;
};
//如果链表中不包含任何元素,返回true,如果链表长度大于0则返回false。
LinkedList.prototype.isEmpty = function() {
return this.length == 0;
};
//返回链表包含的元素个数。与数组的length属性类似。
LinkedList.prototype.size = function() {
return this.length;
};
//由于列表项使用了Node类,就需要重写继承自JavaScript对象默认的toString方法,让其只输出元素的值。
LinkedList.prototype.toString = function() {
var current = this.head;
var str = "";
while(current) {
str += current.element;
current = current.next;
}
return str;
};
LinkedList.prototype.print = function() {
console.log(this.getHead());
};
LinkedList.prototype.getHead = function() {
return this.head;
}
}
}
重写散列表基本函数
function HashTable() {
this.table = [];
var loseloseHashCode = function (key) {
var hash = 0;
for(var i = 0; i < key.length; i++) {
hash += key[i].charCodeAt(0);
}
return hash % 37;
};
var ValuePair = function(key, value) {
this.key = key;
this.value = value;
this.toString = function() {
return '[' + this.key + ' - ' + this.value + ']';
}
};
if(typeof this.put != 'function') {
HashTable.prototype.put = function(key, value) {
var position = loseloseHashCode(key);
if(this.table[position] == undefined) {
this.table[position] = new LinkedList();
}
this.table[position].append(new ValuePair(key, value));
};
HashTable.prototype.remove = function(key) {
var position = loseloseHashCode(key);
if(this.table[position] !== undefined) {
var current = this.table[position].getHead();
while(current.next) {
if(current.element.key == key) {
this.table[position].remove(current.element);
}
if(this.table[position].isEmpty()) {
this.table[position] = undefined;
}
return true;
}
if(current.element.key == key) {
this.table[position].remove(current.element);
}
if(this.table[position].isEmpty()) {
this.table[position] = undefined;
}
return true
} else {
return false
}
};
HashTable.prototype.get = function(key) {
var position = loseloseHashCode(key);
if(this.table[position] !== undefined) {
var current = this.table[position].getHead();
while(current.next) {
if(current.element.key == key) {
return current.element.value;
}
current = current.next;
}
if(current.element.key == key) {
return current.element.value;
}
}
return undefined;
};
HashTable.prototype.print = function() {
for (var i = 0; i < this.table.length; ++i) {
if (this.table[i] !== undefined) {
console.log(i + ": " + this.table[i]);
}
}
};
}
}
测试代码
var hash = new HashTable();
hash.put('Gandalf', 'gandalf@email.com');
hash.put('John', 'johnsnow@email.com');
hash.put('Tyrion', 'tyrion@email.com');
hash.put('Aaron', 'aaron@email.com');
hash.put('Donnie', 'donnie@email.com');
hash.put('Ana', 'ana@email.com');
hash.put('Jonathan', 'jonathan@email.com');
hash.put('Jamie', 'jamie@email.com');
hash.put('Sue', 'sue@email.com');
hash.put('Mindy', 'mindy@email.com');
hash.put('Paul', 'paul@email.com');
hash.put('Nathan', 'nathan@email.com');
console.log(hash.table);
hash.print();
处理冲突方法二,线性探查
当想向表中某个位置加入一个新元素的时候,如果索引为index的位置已经被占据了,就尝试index+1的位置。如果index+1的位置也被占据了,就尝试index+2的位置,以此类推。
在一些编程语言中,我们需要定义数组的大小。如果使用线性探查的话,需要注意的一个问题是数组的可用位置可能会被用完。在JavaScript中,我们不需要担心这个问题,因为我们不需要定义数组的大小,它可以根据需要自动改变大小——这是JavaScript内置的一个功能。
代码
function HashTable() {
this.table = [];
var loseloseHashCode = function (key) {
var hash = 0;
for(var i = 0; i < key.length; i++) {
hash += key[i].charCodeAt(0);
}
return hash % 37;
};
var ValuePair = function(key, value){
this.key = key;
this.value = value;
this.toString = function() {
return '[' + this.key + ' - ' + this.value + ']';
}
};
if(typeof this.put != 'function') {
HashTable.prototype.put = function(key, value) {
var position = loseloseHashCode(key);
if(this.table[position] === undefined) {
this.table[position] = new ValuePair(key, value);
} else {
var index = position;
while(this.table[index] !== undefined) {
index++;
}
this.table[index] = new ValuePair(key, value);
}
};
HashTable.prototype.remove = function(key) {
var position = loseloseHashCode(key);
if(this.table[position] !== undefined) {
if(this.table[position].key === key) {
this.table[position] = undefined;
} else {
var index = position++;
while(this.table[index] === undefined || this.table[index].key !== key) {
index++;
}
if(this.table[index].key === key) {
this.table[index] = undefined;
}
}
return true;
}
return false;
};
HashTable.prototype.get = function(key) {
var position = loseloseHashCode(key);
if(this.table[position] !== undefined) {
if(this.table[position].key === key) {
return this.table[position].value;
} else {
var index = position++;
while(this.table[index] === undefined || this.table[index].key !== key) {
index++;
}
if(this.table[index].key === key) {
return this.table[index].value;
}
}
}
return undefined;
};
HashTable.prototype.print = function() {
for (var i = 0; i < this.table.length; ++i) {
if (this.table[i] !== undefined) {
console.log(i + ": " + this.table[i]);
}
}
};
}
}
var hash = new HashTable();
hash.put('Gandalf', 'gandalf@email.com');
hash.put('John', 'johnsnow@email.com');
hash.put('Tyrion', 'tyrion@email.com');
hash.put('Aaron', 'aaron@email.com');
hash.put('Donnie', 'donnie@email.com');
hash.put('Ana', 'ana@email.com');
hash.put('Jonathan', 'jonathan@email.com');
hash.put('Jamie', 'jamie@email.com');
hash.put('Sue', 'sue@email.com');
hash.put('Mindy', 'mindy@email.com');
hash.put('Paul', 'paul@email.com');
hash.put('Nathan', 'nathan@email.com');
console.log(hash.remove('Sue'));
console.log(hash.table);
hash.print();
更好的散列函数
var djb2HashCode = function (key) {
var hash = 5381;
for (var i = 0; i < key.length; i++) {
hash = hash * 33 + key.charCodeAt(i);
}
return hash % 1013;
};
这并不是最好的散列函数但这是最被社区推荐的散列函数