JavaScript数据结构——散列表(Hash table,也叫哈希表)

概念和结构

  • 散列表是根据关键码值(Key value)而直接进行访问的数据结构。
  • 若关键字为k,则其值存放在f(k)的存储位置上。由此,不需比较便可直接取得所查记录。称这个对应关系f为散列函数,按这个思想建立的表为散列表。

哈希表的操作

哈希表有三种常用操作,分别为

  1. 添加元素put(key, value)
  2. 通过键移除元素 remove(key)
  3. 由键获取值 get(key)

JS实现

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)];
    }

}

哈希冲突

不同关键字的哈希函数值可能相同,这种现象称之为哈希冲突

有两种解决方法:

  1. 分离链接法
  2. 线性探查法
分离链接法

分离链接法就是将散列值相同的所有元素保留到同一个链表中。

分离链接法的JS实现

因为要将哈希函数值相同的所有元素都保存到一个链表中,因此我们需要用到链表这个类。这个类在之前我们已经实现过了,如果有不清楚的同学可以前往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的位置上也被占用了,则继续向下线性查找。

线性探查法的JS实现

因为在找到某个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;
}

你可能感兴趣的:(数据结构,数据结构,JavaScript,哈希表,哈希冲突)