JS实现哈希函数、哈希表

一些知识准备

哈希化:将大数字进行压缩,转化成数组范围内下标的过程

哈希函数:实现哈希化的函数

哈希表: 最终将数据插入到的这个数组, 我们就称之为是一个哈希表

冲突:计算出的下标相同的情况

解决冲突:①链地址法 ②开放地址法

  • 链地址法解决冲突的办法是每个数组单元中存储的不再是单个数据, 而是一个链条。

  • 开放地址法的主要工作方式是寻找空白的单元格来添加重复的数据。

    对于如何寻找空白单元格又分成三种方法:线性探测、二次探测、再哈希法

线性探测:

  • 线性探测就是从index位置+1开始一点点查找合适的位置

二次探测:

  • 线性探测, 我们可以看成是步长为1的探测, 比如从下标值x开始, 那么线性测试就是x+1, x+2, x+3依次探测
  • 二次探测, 对步长做了优化, 比如从下标值x开始, x+1², x+2², x+3²

再哈希法:

  • 把关键字用另外一个哈希函数, 再做一次哈希化, 用这次哈希化的结果作为步长

  • 计算机专家已经设计出一种工作很好的哈希函数:

    stepSize = constant - (key - constant)

    其中constant是质数, 且小于数组的容量

    例如: stepSize = 5 - (key % 5), 满足需求, 并且结果不可能为0

链地址法相对来说效率是好于开放地址法的.

所以在真实开发中, 使用链地址法的情况较多, 因为它不会因为添加了某元素后性能急剧下降.

比如在Java的HashMap中使用的就是链地址法

设计哈希函数:

①把字符串计算成哈希值,这里用霍纳法则(一种多项式的优化算法),可以降低时间复杂度

②实现均匀分布:虽然有办法解决冲突,但最好还是让数据在数组里均匀分布,因此, 我们需要在使用常量的地方, 尽量使用质数(所以要封装一个判断质数的函数)

这里扩展:Java的hashMap采用的是链地址法,一般从Key映射到index的算法用的是取模运算,但hasMap采用了位运算

哈希函数的实现


//1.将字符串转成比较大的数字:hashcode
//2.将hashcode压缩到数组范围之内

function hashFunc(str, size) {
    //1.定义hashCode变量
    let hashCode = 0;
    let index = 0;
    //2.霍纳算法,计算hashCode的值
    for (let i = 0; i < str.length; i++) {
        hashCode = hashCode * 37 + str.charCodeAt(i);
    }
    //3.取余操作
    index = hashCode % size;

    return index;
}

//测试
console.log(hashFunc('fan', 7)); //5

判断质数

//质数:只能被1和他本身整除(也就是不能被2到他自己-1的数整除)

//一个数n可以进行因式分解,分解得到的两个数一定一个<=sqrt(n),一个>=sqrt(n)
//例如16,得到2和8,2<sqrt(16),8>sqrt(16)
//所以不用遍历2到n-1,遍历到sqrt(n)即可、

function isPrime(num) {
    let temp = parseInt(Math.sqrt(num));

    for (let i = 0; i <= temp; i++) {
        if (num % i == 0) {
            return false;
        }
    }

    return true;
}

哈希表的实现

//封装哈希表
function HashTable() {

    //存放数据的数组
    this.storage = [];
    //当前哈希表已经存放元素的个数
    this.count = 0;
    //哈希表数组当前总长度
    this.limit = 7;

    //哈希函数
    HashTable.prototype.hashFunc = function(str, size) {
        //1.定义hashCode变量
        let hashCode = 0;
        let index = 0;
        //2.霍纳算法,计算hashCode的值
        for (let i = 0; i < str.length; i++) {
            hashCode = hashCode * 37 + str.charCodeAt(i);
        }
        //3.取余操作
        index = hashCode % size;

        return index;
    }

    //插入或修改
    HashTable.prototype.put = function(key, value) {
        //1.根据key获取索引
        let index = this.hashFunc(key, this.limit);

        //2.根据索引取出对应位置的桶bucket(也是一个数组)
        let bucket = this.storage[index];

        //2.1判断桶存不存在,不存在就创建,并放回索引位置
        if (bucket == null) {
            bucket = [];
            this.storage[index] = bucket;
        }

        //3.判断传入的key是新增的还是要修改的
        //3.1遍历桶,看看key是否存在
        for (let i = 0; i < bucket.length; i++) {
            let tuple = bucket[i];
            if (tuple[0] == key) {
                tuple[1] = value;
                return;
            }
        }

        //3.2不存在就添加进桶
        bucket.push([key, value]);
        //3.3存放个数加一
        this.count += 1;

        //4.判断是否需要扩容
        if (this.count > this.limit * 0.75) {
            //4.1获得新的容量,判断这个新容量是不是质数,不是的话获取一个质数作为新容量
            let newLimit = this.limit * 2;
            let newSize = this.getPrime(newLimit);
            this.resize(newSize);
        }

    }

    //获取
    HashTable.prototype.get = function(key) {
            //1.根据key获取索引
            let index = this.hashFunc(key, this.limit);

            //2.根据索引获取对应位置的bucket
            let bucket = this.storage[index];

            //3.判断桶是否存在
            //3.1不存在返回null
            if (bucket == null) {
                return null;
            }

            //3.2存在就遍历桶,查找key
            for (let i = 0; i < bucket.length; i++) {
                let tuple = bucket[i];
                //3.3找到key就返回对应value
                if (tuple[0] == key) {
                    return tuple[1];
                }
            }
            //3.4没找到返回null
            return null;
        }
        //删除
    HashTable.prototype.remove = function(key) {
        //1.根据key获取索引
        let index = this.hashFunc(key, this.limit);

        //2.根据索引获取对应位置的bucket
        let bucket = this.storage[index];

        //3.判断桶是否存在
        //3.1不存在返回null
        if (bucket == null) {
            return null;
        }

        //3.2存在就遍历桶,查找key
        for (let i = 0; i < bucket.length; i++) {
            let tuple = bucket[i];
            //3.3找到key就删除,存放个数-1
            if (tuple[0] == key) {
                bucket.splice(i, 1);
                this.count--;

                //4.判断是否要缩小容量
                if (this.limit > 7 && this.count < this.count.limit * 0.25) {
                    //4.1获取为质数的新容量
                    let newLimit = Math.floor(this.limit / 2);
                    let newSize = this.getPrime(newLimit);
                    this.resize(newSize);
                }

                return tuple[1];
            }
        }
        //3.4没找到返回null
        return null;
    }

    //扩容\缩容
    HashTable.prototype.resize = function(newLimit) {
        //1.保存旧的表
        var oldstorage = this.storage;
        //2.重置数据
        this.storage = [];
        this.count = 0;
        this.limit = newLimit;

        //3.遍历旧的表
        for (let i = 0; i < oldstorage.length; i++) {
            let bucket = oldstorage[i];

            //3.1判断bucket是否为空
            if (bucket == null) {
                continue;
            }

            //3.2将bucket的元素取出放入新的表
            for (let j = 0; j < bucket.length; j++) {
                let tuple = bucket[j];
                this.put(tuple[0], tuple[1]);
            }

        }
    }

    //判断质数
    HashTable.prototype.isPrime = function(num) {
            let temp = parseInt(Math.sqrt(num));

            for (let i = 0; i <= temp; i++) {
                if (num % i == 0) {
                    return false;
                }
            }

            return true;
        }
        //获取质数
    HashTable.prototype.getPrime = function(num) {
        //不知道循环次数,使用while
        while (!isPrime(num)) {
            num++;
        }

        return num;
    }
}

全部评论

相关推荐

已经烂了:算法去制造业最少也要211,双非搞算法就是死路一条。至少我在的部门,算法工程师最低都是211毕业的,而且岗位极少。
点赞 评论 收藏
分享
01-07 07:54
已编辑
门头沟学院 前端工程师
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务