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

全部评论

相关推荐

咦哟,从去年八月份开始长跑,两处实习转正都失败了,风雨飘摇,终于拿到offer了更新一下面试记录:秋招:多部门反复面试然后挂掉然后复活,具体问了啥已经忘了,只是被反复煎炸,直至焦香😋春招:base北京抖音hr打来电话说再次复活,准备面试,gogogo北京抖音一面:六道笔试题:1.promise顺序2.定义域问题3.flat展开4.并发请求5.岛屿数量算法(力扣)深度,广度都写6.忘记了,好像也是算法,难度中等其他问题多是框架底层设计,实习项目重难点~~~秒过😇北京抖音二面:三道笔试题:(为什么只有三道是因为第三道没做出来,卡住了)1.中等难度算法(忘记啥题了,应该是个数组的)2.认识js的继承本质(手写继承模式,深入js的面相对象开发)3.手写vue的响应式(卡在了watch,导致挂掉)---后知后觉是我的注册副作用函数写得有问题,有点紧张了其他题目多是项目拷打,项目亮点,对实习项目的贡献~~~第二天,挂,but立马复活转战深圳客服当天约面深圳客服一面:六道笔试题,由于面过太多次字节,面试官叫我直接写,不用讲,快些写完😋,具体都是些继承,深拷贝(注意对数组对象分开处理,深层次对象,循环引用),加中等难度算法题~~~秒过深圳客服二面:口诉八股大战:大概囊括网络,浏览器渲染原理,动画优化,时间循环,任务队列等等(你能想到的简单八股通通拉出来鞭尸😋)算法题:笔试题6道:1:找出数组内重复的数,arr[0]-arr[n]内的数大小为[1-n],例如[1,2,2,3,3]返回[2,3],要求o(n),且不使用任何额外空间(做到了o(n),空间方面欠佳,给面试官说进入下一题,做不来了)2:原滋原味的继承(所以继承真滴很重要)3:力扣股票购买时机难度中等其他滴也忘记了,因为拿到offer后鼠鼠一下子就落地了,脑子自动过滤掉可能会攻击鼠鼠的记忆😷~~~秒过深圳客服三面:项目大战参与战斗的人员有:成员1:表单封装及其底层原理,使用成本的优化,声明式表单成员2:公司内部库生命周期管理成员3:第三方库和内部库冲突如何源码断点调试并打补丁解决成员4:埋点的艺术成员5:线上项目捷报频传如何查出内鬼成员6:大文件分片的风流趣事成员7:设计模式对对碰成员8:我构建hooks应对经理的新增的小需求的故事可能项目回答的比较流利,笔试题3道,都很简单,相信大家应该都可以手拿把掐😇~~~过过过无hr面后续煎熬等待几天直接hr打电话发offer了,希望大家也可以拿到自己心仪的offer
法力无边年:牛哇,你真是准备得充分,我对你没有嫉妒,都是实打实付出
查看19道真题和解析
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务