JavaScript模拟散列表

214 阅读5分钟

1 散列表

1.1 概念

散列表(Hash table,也叫哈希表),是根据键(Key)而直接访问在内存存储位置的数据结构

主要将键传递一个函数(散列函数),进行一定的计算,获取值存放的位置,通过位置获取数据

1.2 代码实现

   function HashTable () {
        //存储数据
        this.table = [];

        //散列函数:将键的每个字母转换为ASCII码,ASCII码再相加,最后把相加的结果与一个数据求余
        // HashTable.prototype.hashFunc = function ( key ) {
        //     let num = 0;
        //     for(let i=0;i<key.length;i++){
        //         num+=key.charCodeAt(i);
        //     }
        //     return num%37;
        // }
        //最受社区推崇的散列函数之一djb2HashCode,插入和查询较快,冲突几率小
        HashTable.prototype.hashFunc = function (key) {
            var hash = 5381; //赋值一个质数
            for (var i = 0; i < key.length; i++) {
                hash = hash * 33 + key.charCodeAt(i);
            }
            return hash % 1013; //{4}
        };

        //向散列表增加一个新的项
            //通过散列函数计算的位置有可能出现相同的情况,后面的数据会把之前的覆盖掉
            //解决冲突
            //方式一:分离链接法
            //核心:每一个位置放置一个链表,数据(键,值)存储到链表中
        HashTable.prototype.put = function ( key,value ) {
            //通过散列函数计算出存储位置
            let position = this.hashFunc(key);
            //判断给位置是否有链表存在,如果没有就创建一个放到position位置
            if(this.table[position]===undefined){
                let listLinked = new LinkedList();
                this.table[position] = listLinked;
            }
            //根据键值创建一个对象,放到链表中
            this.table[position].append(new ValuePair(key,value));
        }

        //根据键值从散列表中移除值
        HashTable.prototype.remove = function ( key ) {
            let position = this.hashFunc(key);
            if(this.table[position]!=undefined){
                let current = this.table[position].getHead();
                while(current.next){
                    if(current.element.key===key){
                        //从链表中移除
                        this.table[position].remove(key);
                        //当链表为空了,就把散列表该位置设为undefined
                        if(this.table[position].isEmpty()){
                            this.table[position] = undefined;
                        }
                        return true;
                    }
                    current = current.next;
                }
                //检查是否为第一个或最后一个元素
                if(current.element.key===key){
                    //从链表中移除元素
                    this.table[position].remove(key);
                    //当链表为空了,就把散列表该位置设为undefined
                    if(this.table[position].isEmpty()){
                        this.table[position] = undefined;
                    }
                    return true;
                }
            }

            //如果没有要移除的元素
            return false;
        }


        //返回根据键值检索到的特定的值
        HashTable.prototype.get = function ( key ) {
            let position = this.hashFunc(key);
            //如果该位置有链表,查询链表元素
            if(this.table[position]!=undefined){
                let 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 ( let i = 0 ;i<this.table.length;i++ ) {
                //判断是否为undefined
                if(this.table[i]!=undefined){
                    console.log (i+"-" +this.table[ i ] );
                }
            }
        }
 
        //定义一个函数,存储键和值
       let ValuePair = function ( key,value ) {
            this.key = key;
            this.value = value;
            //重写toString方法
            this.toString = function (  ) {
                return '['+this.key+'-'+this.value+']';
            }
        }
    }

1.3 验证

    //测试
    var hash = new HashTable()
    hash.put('Gandalf', 'gandalf@email.com')
    hash.put('John', 'john@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')
    hash.print();

1.4 实现过程中借助的链表

因为通过散列函数计算的位置有可能出现相同的情况,后面的数据会把之前的覆盖掉 解决:每一个位置放置一个链表,数据(键,值)存储到链表中

作用:解决冲突

function LinkedList() {
    //封装一个node类,保存每个节点信息
    function Node( element ) {
        this.element = element;//当前节点的值
        this.next = null;//下一个节点的引用/指针
    }
    this.length = 0;//链表的长度,bug1 let length
    this.head = null;  //链表的第一个节点

    //链表尾部添加元素
    LinkedList.prototype.append = function ( element ) {
        //1 根据元素创建节点
        let newNode = new Node(element);

        //2 判断链表是否为空
        if(this.head==null){//链表为空,newNode作为头节点添加进去,
            this.head = newNode;
        }else{
            //2.1 链表不为空,定义变量存储当前节点
            let current = this.head;
            //2.2 遍历链表,找最后一个节点
            while ( current.next ) {
                current = current.next;
            }
            //2.3 新节点添加到当前节点(最后一个节点)的后面
            current.next = newNode;
        }
        //3 链表长度增加1
        this.length++;
    }
    //任意位置插入元素
    LinkedList.prototype.insert = function ( position,element ) {
        //1 检测边界:如果插入位置无效,插入失败,返回false
        if(position<0||position>this.length){
            return false;
        }
        //2 根据元素创建新节点
        let newNode = new Node(element);

        //3 判断插入位置
        if(position==0){
            //插入链表的开始位置,newNode.next指向head;并且head指向newNode
            newNode.next = this.head;
            this.head = newNode;
        }else{
            //3.1 插入非开始位置
            //定义变量存储当前节点和当前节点的位置
            let current = head;
            let index = 0;
            let previous = null;//存储当前节点的上一个节点

            //查找position位置的节点
            while(index<position){
                previous = current;
                current = current.next;
                index++;
            }
            //将newNode.next指向current节点,previous.next指向newNode节点
            newNode.next = current;
            previous.next = newNode;
        }
        //4 链表长度增加1,并返回true,添加成功
        this.length++;
        return true;
    }

    //移除任意位置的元素
    LinkedList.prototype.removeAt = function ( position ) {
        //1 检测越界:越界不做操作,返回null
        if(position<0||position>=this.length){
            return null;
        }

        //定义变量存储当前节点和当前节点的位置
        let index = 0;
        let current = head;
        let previous = null;//存储当前节点的上一个节点

        //2 判断移除的位置
        if(position==0){
            //2.1 移除第一个节点,设置head指向head.next即可
            this.head = head.next;
        }else{
            //2.2 移除非第一个节点
            //查找position位置的节点
            while(index<position){
                previous = current;
                current = current.next;
                index++;
            }
            //移除当前节点:previous.next指向current.next
            previous.next = current.next;
        }
        //3 链表长度减1,并返回被移除的元素
        this.length--;
        return current.element;
    }

    //获取元素在链表中的位置
    LinkedList.prototype.indexOf = function ( element ) {
        //1 定义变量存储信息
        let current = this.head;
        let index =0;//current节点在链表的位置

        //2 链表中查找元素
        while(current){
            //判断当前节点的元素是否等于目标元素
            if(current.element==element){
                //找到元素,返回位置
                return index;
            }
            current=current.next;
            index++;
        }

        //3 遍历链表都没有找到元素,返回-1
        return -1;
    }
    //删除元素
    LinkedList.prototype.reomve = function ( element ) {
        let index = this.indexOf(element);//获取节点在链表中的位置
        return this.removeAt(index);//根据节点位置移除节点,返回节点的值
    }
    //获取第一个节点
    LinkedList.prototype.getHead = function (  ) {
        return this.head;
    }
    //获取链表的长度
    LinkedList.prototype.size = function (  ) {
        return this.length;
    }
    //判断链表是否有节点
    LinkedList.prototype.isEmpty = function (  ) {
        return this.length==0;
    }
    //链表的toString方法
    LinkedList.prototype.toString = function (  ) {
        //定义变量,存储当前节点和节点的值
        let current = this.head;
        let str = "";
        while(current){
            str+= current.element+(current.next?",":"");
            current = current.next;
        }
        return str;
    }
}/*
** Create by caohai on 2018/10/29
*/