散列算法的作用是尽可能快的在数据结构中找到一个值。在链表等数据结构中获得一个值,需要遍历整个数据结构来找到他。但是散列表中,使用散列函数,就可以知道值的具体位置,因此能够快速检索到值。散列函数的作用是给定一个键值,然后返回值在表中的地址。
代码实现:
function HashTable(){
var table = [];
// 实现一个散列函数
var hashCode = function(key){
var hash = 0;
for(var i = 0; i < key.length; i++){
hash += key.charCodeAt(i);
}
return hash % 37;
}
// 向散列表中增加一个新的项
this.put = function(key, value){
var pos = hashCode(key);
console.log(pos + "---" + key);
table[pos] = value;
}
// 根据键值检索到特定的值
this.get = function(key){
console.log(table[hashCode(key)])
return table[hashCode(key)];
}
// 根据键值从散列表中移除值
this.remove = function(key){
table[hashCode[key]] = undefined;
}
// 打印散列表
this.print = function(){
for(var i = 0; i < table.length; i++){
if(table[i] !== undefined){
console.log(i + ':' +table[i]);
}
}
}
}
测试:
var hash = new HashTable();
hash.put('abc', '110@qq.com'); //35---abc
hash.put('aab', '123@qq.com'); //33---aab
hash.get('abc'); //110@qq.com
处理散列表中的冲突
有时候,一些键会有相同的散列值。不同的值在散列表中对应相同位置的时候,我们称其为冲突。
处理冲突的方法:
(1)分离链接
(2)线性探查
(3)双散列法
分离链接
分离链接法包括为散列表的每一个位置创建一个链表并将元素存储在里面。它是解决冲突的最简单的方法,但是它需要额外的存储空间。
代码实现:
// 实现LinkList类
function LinkList(){
let Node = function(ele){
this.ele = ele;
this.next = null;
}
let length = 0;
let head = null;
// 向链表尾部追加元素
this.append = function(ele){
let node = new Node(ele);
let current;
if(head == null){
head = node;
}else{
current = head;
while(current.next){
current = current.next;
}
current.next = node;
}
length++;
}
// 从链表中移出元素(从特定位置移出)
this.removeAt = function(pos){
// 检查越界值
if(pos >-1 && pos< length){
let current = head,
pre,
index = 0;
if(pos === 0){
head = current.next;
}else{
while(index++ < pos){
pre = current;
current = current.next;
}
pre.next = current.next;
}
length--;
return current.ele;
}else{
return null;
}
}
// 在任意位置插入元素
this.insert = function(pos, ele){
if(pos >= 0 && pos <= length){
let node = new Node(ele),
current = head,
pre,
index = 0;
if(pos == 0){
node.next = current;
head = node;
}else{
while(index++ < pos){
pre = current;
current = current.next;
}
node.next = current;
pre.next = node;
}
length++;
return true;
}else{
return false;
}
}
// 将LinkList对象转换成一个字符串
this.toString = function(){
let current = head,
string = '';
while(current){
string += current.ele;
current = current.next;
}
console.log(string);
return string;
}
// 该方法接收一个值,如果在列表中能找到它,就返回元素的位置,否则返回-1
this.indexOf = function(ele){
let current = head,
index = 0;
while(current){
if(current.ele == ele){
return index;
}
index++;
current = current.next;
}
return -1;
}
// 从链表中移出元素(通过特定的元素)
this.remove = function(ele){
let index = this.indexOf(ele);
return this.removeAt(index);
}
this.isEmpty = function(){
if(length == 0){
console.log('空')
}else{
console.log('不为空')
}
return length == 0;
}
this.size = function(){
console.log(length)
return length;
}
this.getHead = function(){
console.log(head);
return head;
}
}
function HashTable(){
var table = [];
var hashCode = function(key){
var hash = 0;
for(var i = 0 ; i<key.length; i++){
hash += key[i].charCodeAt();
}
return hash % 37;
};
// 辅助类表示将要加入的LinkList实例的元素
var ValueList = function(key, value){
this.key = key;
this.value = value;
this.toString = function(){
return '[' + this.key +'-'+ this.value +']';
}
}
// 实现put方法
// 在这个方法中,将要验证新元素的位置是否被占据,如果这个位置是第一次被加入,我们会在这个位置上初始化一个ValueList类的实例。
this.put = function(key, value){
var pos = hashCode(key);
if(table[pos] == undefined){
table[pos] = new LinkList();
}
table[pos].append(new ValueList(key, value));
}
// 实现get方法,用来获取特定的值
this.get = function(key){
var pos = hashCode(key);
if(table[pos] !== undefined){
// 遍历链表寻找键值
var current = table[pos].getHead();
while(current.next){
if(current.ele.key === key){
console.log(current.ele.value);
return current.ele.value;
}
current = current.next;
}
// 检查元素在链表第一个或最后一个节点的情况
if(current.ele.key == key){
console.log(current.ele.value)
return current.ele.value;
}
}
console.log('undefined');
return undefined;
};
// 实现remove方法
// 我们需要从链表中移除一个元素
this.remove = function(key){
var pos = hashCode(key);
if(table[pos] !== undefined){
var current = table[pos].getHead();
while(current.next){
if(current.ele.key === key){
table[pos].remove(current.ele);
if(table[pos].isEmpty()){
table[pos] = undefined;
}
return true;
}
current = current.next;
}
// 检查是否为第一个或最后一个元素
if(current.ele.key === key){
table[pos].remove(current.ele);
if(table[pos].isEmpty()){
table[pos] = undefined;
}
return true;
}
}
return false;
}
// 打印散列表
this.print = function(){
for(var i = 0; i < table.length; i++){
if(table[i] !== undefined){
console.log(i + ':' +table[i]);
}
}
}
}
测试:
var hash = new HashTable();
hash.put('Jamie', '110');
hash.put('Tom','112');
hash.put('Sue', '119');
hash.get('Sue');
结果:
线性探查
线性探查:当想向表中某个位置加入一个新元素的时候,如果索引为index的位置已经被占据了,就尝试index+1的位置。如果index+1的位置也被占据了,就尝试index+2的位置,以此类推.
代码实现:
function HashTable(){
var table = [];
var hashCode = function(key){
var code = 0;
for(var i = 0; i<key.length;i++){
code += key[i].charCodeAt();
}
return code % 37;
}
var ValueHash = function(key, value){
this.key = key;
this.value = value;
this.tostring = function(){
console.log(this.key + '----'+ this.value)
}
}
this.put = function(key, value){
var pos = hashCode(key);
if(table[pos] == undefined){
table[pos] = new ValueHash(key ,value);
}else{
var index = ++pos;
while(table[index] != undefined){
index++;
}
table[index] = new ValueHash(key, value);
}
}
this.get = function(key){
var pos = hashCode(key);
if(table[pos] !== undefined){
if(table[pos].key === key){
console.log(table[pos].value);
return table[pos].value;
}else{
var index = ++pos;
while(table[index] === undefined || table[index].key !== key){
index++;
}
if(table[index].key === key){
console.log(table[index].value);
return table[index].value;
}
}
}
return undefined;
}
this.remove = function(key){
var pos = hashCode(key);
if(table[pos] !== undefined){
if(table[pos].key === key){
console.log('移出的元素为:',table[pos].value);
table[pos].value = undefined;
}else{
var index = ++pos;
while(table[index] === undefined || table[index].key !== key){
index++;
}
if(table[index].key === key){
console.log('移出的元素为:',table[index].value);
table[index] = undefined;
}
}
}
return undefined;
}
this.print = function(){
for(var i = 0; i<table.length; i++){
if(table[i] !== undefined){
console.log(i + '-----' +table[i].key + '------' + table[i].value);
}
}
}
}
测试:
var hash = new HashTable();
hash.put('Jamie','110');
hash.put('Sue','112');
hash.put('Tom','114');
hash.put('Jonathan','119');
hash.print();
hash.get('Sue');
hash.remove('Sue');
hash.print();
结果:
创建更好的散列函数
一个表现良好的散列函数是由几个方面构成的:
(1)插入和检索元素的时间
(2)较低的冲突可能性
djb2散列函数:
// 一个更好的散列函数
var djb2HashCode = function(key){
var hash = 5381;
for(var i = 0; i < key.length; i++){
hash = hash * 33 + key[i].charCodeAt();
}
return hash % 1013;
}