k
,则其值存放在f(k)
的存储位置上。由此,不需比较便可直接取得所查记录。称这个对应关系f
为散列函数,按这个思想建立的表为散列表。哈希表有三种常用操作,分别为
put(key, value)
remove(key)
get(key)
JS里面的哈希表结构需要通过数组(array)来实现,因为JS中只有数组才有快速定位的能力。
var HashTable = function(){
var hashTable = [];
//定义散列函数loseloseHashCode
function loseloseHashCode(key){
var hashcode = 0;
for(var i = 0; i < key.length; i++){
hashcode += key[i].charCodeAt();
}
return hashcode % 37;
}
//添加元素
this.put = function(key , value){
hashTable[loseloseHashCode(key)] = value;
}
//移除元素
this.remove = function(key){
hashTable[loseloseHashCode(key)] = undefined;
}
//获取元素值
this.get = function(key){
return hashTable[loseloseHashCode(key)];
}
}
不同关键字的哈希函数值可能相同,这种现象称之为哈希冲突。
有两种解决方法:
分离链接法就是将散列值相同的所有元素保留到同一个链表中。
因为要将哈希函数值相同的所有元素都保存到一个链表中,因此我们需要用到链表这个类。这个类在之前我们已经实现过了,如果有不清楚的同学可以前往JavaScript数据结构——链表(Linked List)。
var HashTable = function(){
//定义辅助类
var Node = function(key , value){
this.key = key;
this.value = value;
}
var hashTable = [];
//定义散列函数
function loseloseHashCode(key){
var hashcode = 0;
for(var i = 0; i < key.length; i++){
hashcode += key[i].charCodeAt();
}
return hashcode % 37;
}
//添加元素
this.put = function(key , value){
var position = loseloseHashCode(key);
if(hashTable[position]){
//哈希表的位置存在元素
hashTable[position].append(new Node(key , value));
}else{
//那个位置没有元素
hashTable[position] = new LinkedList();
hashTable[position].append(new Node(key , value));
}
}
//获取元素值
this.get = function(key){
var position = loseloseHashCode(key);
if(hashTable[position]){
var current = hashTable[position].getHead();
while(current){
if(current.element.key === key){
return current.element.value;
}else{
current = current.next;
}
}
return undefined;
}else{
return undefined;
}
}
//移除元素
this.remove = function(key){
var position = loseloseHashCode(key);
if(hashTable[position]){
var current = hashTable[position].getHead();
//找到key所在的链表节点current
while(current){
if(current.element.key === key){
hashTable[position].remove(current.element);
//移除后如果链表为空,则设为undefined
if(hashTable[position].isEmpty()){
hashTable[position] = undefined;
}
return true;
}else{
current = current.next;
}
}
return false;
}else{
return false;
}
}
}
//链表生成函数
function LinkedList(){
var head = null;
var length = 0;
//定义辅助类,用来生成节点
var Node = function(element){
this.element = element;
this.next = null;
}
//向链表尾部添加元素
this.append = function(element){
//根据传进来的element构建链表节点
var node = new Node(element);
if(head === null){
//如果是空链表就直接设为链表头
head = node;
}else{
//不然将最后一个节点的next属性指向它
var lastNode = head;
while(lastNode.next !== null){
lastNode = lastNode.next;
}
lastNode.next = node;
}
length ++;
}
//插入元素
this.insert = function(position,element){
//解决越界
if(position > -1 && position < length){
var node = new Node(element);
if(position === 0){
var oldHead = head;
head = node;
node.next = oldHead;
}else{
var preNode = null;
var currentNode = head;
index = 0;
while(index !== position){
preNode = currentNode;
currentNode = currentNode.next;
index ++;
}
preNode.next = node;
node.next = currentNode;
}
length ++;
}
}
//移除指定位置元素
this.removeAt = function(position){
//解决越界
if(position > -1 && position < length){
if(position === 0){
var currentNode = head;
head = currentNode.next;
}else{
var preNode = null;
var currentNode = head;
index = 0;
while(index !== position){
preNode = currentNode;
currentNode = currentNode.next;
index ++;
}
preNode.next = currentNode.next;
}
length --;
return currentNode;
}
}
//获取元素索引
this.indexOf = function(element){
var index = 0;
var currentNode = head;
while(currentNode){
if(currentNode.element === element){
return index;
}
currentNode = currentNode.next;
index ++;
}
return -1;
}
//移除指定元素
this.remove = function(element){
//复用
return this.removeAt(this.indexOf(element));
}
//检查链表是否为空
this.isEmpty = function(){
return length === 0;
}
//获取链表长度
this.size = function(){
return length;
}
//获取链表头
this.getHead = function(){
return head;
}
}
线性探查法就是如果位置被占用线性向下移动。比如哈希函数值为N
的位置被占用了,而N+1
为空,那么就将这个元素放置在N+1
的位置上,如果N+1
的位置上也被占用了,则继续向下线性查找。
因为在找到某个key
的对应位置后,可能这个位置存放的不是我们想要的那个value
,因此在存放的时候应该把key
也一起存放进去,而不单单只存放一个value
,好方便进行比较。
function HashTable(){
//定义辅助类
var Node = function(key , value){
this.key = key;
this.value = value;
}
//定义散列函数
function loseloseHashCode(key){
var hashcode = 0;
for(var i = 0; i < key.length; i++){
hashcode += key[i].charCodeAt();
}
return hashcode % 37;
}
var hashTable = [];
//添加元素
this.put = function(key , value){
var position = loseloseHashCode(key);
if(hashTable[position]){
//哈希表的位置存在元素
while(hashTable[position]){
position ++;
}
hashTable[position] = new Node(key , value);
}else{
//那个位置没有元素
hashTable[position] = new Node(key , value);
}
}
//获取元素值
this.get = function(key){
var position = loseloseHashCode(key);
if(hashTable[position]){
//那个位置有元素
while(hashTable[position]){
if(hashTable[position].key === key){
return hashTable[position].value;
}else{
position ++;
}
}
return undefined;
}else{
//那个位置没有元素
return undefined;
}
}
//移除元素
this.remove = function(key){
var position = loseloseHashCode(key);
if(hashTable[position]){
while(hashTable[position]){
if(hashTable[position].key === key){
hashTable[position] = undefined;
return true;
}else{
position ++;
}
}
return false;
}else{
return false;
}
}
}
好的散列函数能让冲突率下降,减少冲突的发生。
var djb2HashCode = function(key){
var hashCode = 5381;
for(var i = 0; i < key.length; i++){
hashCode = hashCode * 33 + key.charCodeAt(i);
}
return hashCode % 1013;
}