04 Javascript数据结构与算法 之 字典和散列表

1. 定义

字典:使用[键,值]的形式存储数据。存储不重复的值。字典也称作映射

2. 字典实例(Map)

在Es6中,已经包含了一个Map类的实现,也就是所说的字典。这里是自己实现一个Map类。

  • 使用一个Object实例,而不是数组来存储元素
  • set(key, value): 像字典中添加元素
  • remove(key): 通过键值一处键值对应的数据值
  • has(key): 是否包含该key,存在返回true,否则返回false
  • get(key): 根据键,获取对于的值并返回
  • clear(): 将字典中的元素全部删除
  • size(): 获取字典元素数量
  • keys(): 将字典包含的所有键以数组的形式返回
  • values(): 将字典包含的所有值以数组的形式返回
function Map() {
    let items = {};
    
    // 是否存在key
    this.has = (key) => (key in items);

    // 添加或更新:key,value
    this.set = (key, value) => {
        items[key] = value;
    };

    // 删除值
    this.remove =(key) => {
        if (this.has(key))  {
            delete items[key];
            return true;
        }
        return false;
    };

    // 获取值
    this.get = (key) => {
        return this.has(key) ? items[key] : undefined;
    };

    // 清除所有
    this.clear = () => {
        items = {};
    }

    // size(): 获取字典元素数量
    this.size = () => {
        let length = 0;
        for(let item in items) {
            if (items.hasOwnProperty(item)) {
                length++;
            }
        }
        return length;
    };

    // 将字典包含的所有键以数组的形式返回
    this.keys = () => {
        let ret = [];
        for(let item in items) {
            if (items.hasOwnProperty(item)) {
                ret.push(item);
            }
        }
        return ret;
    }

    // 将字典包含的所有值以数组的形式返回
     this.values = () => {
        let ret = [];
        for(let item in items) {
            if (items.hasOwnProperty(item)) {
                ret.push(items[item]);
            }
        }
        return ret;
    }
} 
复制代码

3. HashMap 定义

HashMap 也是一种Dictionary类的一种散列表实现方式。
散列算法: 是为了尽可能快地在数据结构中找到一个值。

3.1 散列函数

使用散列函数,可以知道值的具体位置,能够快速地检索到该值,散列函数的作用是:给定一个键值,然后返回值在表中的地址。
我们有一组数据,某个人的邮箱地址:

{
    Gandalf: 'gandalf@email.com',
    John: 'johnsnow@emal.com',
    Tyrion: 'tyrion@email.com'
}
复制代码
  • 我们使用散列函数,将每个键中的字母相加,计算出散列值(可以想象为地址索引)
  • 在散列表中:以散列值为存储索引,并存储相应的value。那么我们在通过key进行查询value时,就不再需要一个一个循环,而直接根据散列值获取。


4. 散列表实例

和之前一样,我们需要准备数据存储对象,添加相应的方法:

  • 我们将使用数组来表示数据结构。
  • getHashCodeKey(key): 根据key生成散列表的key。该方法不会暴露到外部
  • put(key, value): 向散列表增加一个新的项
  • remove(key): 根据键从散列表中移除值
  • get(key): 返回根据键值检索的特定的值
function HashTable() {
    // 使用数组保存数据
    let table = [];

    // 根据key生成散列表的key
    function getHashCodeKey(key) {
        let hash = 0;
        for (let i = 0; i < key.length; i++) {
            hash += key.charCodeAt(i);
        }
        return hash % 37;
    }

    // 添加散列数据
    this.put = (key, value) => {
        let position = getHashCodeKey(key);
        console.log(position + ' - ' + key);
        table[position] = value;
    };

    // 获取数据
    this.get = function (key) {
        return table[getHashCodeKey(key)];
    };

    // 移除
    this.remove = function(key) {
        table[getHashCodeKey(key)] = undefined;
    };
}
复制代码

5. 散列集合

散列集合是由一个集合构成,但是插入、移除、获取元素时,使用的是散列函数。我们可以重用上面所有的代码,区别在于:我们添加的key值就是插入的值:[value转化为key, value]。和集合相似,散列集合之存储唯一不重复的值。

6. 散列表冲突

按照上面的顺序,会出现相同key的情况,不同的值在散列表中对应相同位置的时候表示冲突了。解决该问题可用以下方法:

  • 分离链接(需要重写get,remove,put方法):


function HashTable() {
    let items = [];

    // 链表每一个对象对应的item类
    function ValuePair(key, value) {
        this.key = key;
        this.value = value;
        this.toString = () => {
            console.log(`[${this.key} - ${this.value}]`)
        };
    }

    this.put = (key, value) => {
        let position = getHashCodeKey(key);
        if (table[position] === undefined) {
            table[position] = new LinkedList();
        }
        table[position].append(new ValuePair(key, value));
    };

    this.get = function(key) {
        var position = getHashCodeKey(key);
        if (table[position] !== undefined){ 
            //遍历链表来寻找键/值
            var current = 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; 
    };
    this.remove = function(key){
        var position = getHashCodeKey(key);
        // 如果位置为空,那么不存在该元素,返回false
        if (table[position] !== undefined){
            // 获取该位置的链表的head
            var current = table[position].getHead();
            // 链表有不只一个item, 通过循环遍历查找后进行移除
            while(current.next){
                if (current.element.key === key){
                    table[position].remove(current.element);
                    // 删除后,如果链表长度为0,则从散列表中移除该key
                    if (table[position].isEmpty()){
                        table[position] = undefined;
                    }
                    return true; 
                }
                current = current.next;
            }
            // 链表只有一个item: 检查是否为第一个或最后一个元素
            if (current.element.key === key){ 
                table[position].remove(current.element);
                if (table[position].isEmpty()){
                    table[position] = undefined;
                }
                return true;
            }
            }
        return false; 
    };
}
复制代码
  • 线性探查(需要重写get,remove,put方法)
    当想向表中某个位置加入一个新元素的时候,如果索引为index的位置已经被占据了,就尝试index+1的位置。如果index+1的位置也被占据了,就尝试index+2的位置,以此类推。


7. 创建更好的散列函数

上面的散列函数会产太多的冲突,应该尽量避免冲突。

var djb2HashCode = function (key) {
    // 初始化一个hash值,并赋值为一个质数
    var hash = 5381; 
    // 迭代key,将hash与33相乘,并和当前迭代到的字符的ASCII码值相加
    for (var i = 0; i < key.length; i++) {
        hash = hash * 33 + key.charCodeAt(i);
    }
    // 最后将hash与另一个随机质数相除的余数
    return hash % 1013; 
    };
复制代码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值