1

《Javascript数据结构和算法》笔记-「字典和散列表」

集合、字典、散列表存储的都是「不重复」的数据结构

  • 集合:我们更关注每一个元素的值,并把其作为主要元素
  • 字典:我们用[键,值]的形式来存储数据
  • 散列表: 跟字典类似,也会是用[键,值]的形式来存储数据

但是「字典」和「散列表」的实现方式略有不同。

字典

字典也称作映射。我觉得这种数据结构,其实在业务代码中的应用很常见。

与Set类似,ES6同样实现了一个Map类,即我们所说的字典

字典的大致骨架

    function Dictionary(){
        var items = {}
    }

    this.set = function(key,value){}
    this.get = function(key){}
    this.delete = function(key){}
    this.has = function(key){}
    this.clear = function(){}
    this.size = function(){}
    this.keys = function(){}  // 将字典所包含的所有键,以一个数组返回
    this.values = function(){}  // 将字典所包含的所有值,以一个数组返回

字典类的实现,十分简单就直接粘贴全部代码了。

我觉得在JS中,对象本身就可以作为字典来使用。我经常在业务代码中把数据处理成这种字典的数据结构

        function Dictionary() {
            var items = {}

            this.has = function ( key ) {
                return items.hasOwnProperty( key )
            }

            this.set = function ( key, value ) {
                items[ key ] = value
            }

            this.delete = function ( key ) {
                if ( this.has( key ) ) {
                    delete items[ key ]
                    return true
                }
                return false
            }

            this.get = function ( key ) {
                if ( this.has( key ) ) {
                    return items[ key ]
                } else {
                    return undefined
                }
            }

            this.values = function(){
                return Object.values(items)
            }

            this.keys = function(){
                return Object.keys(items)
            }

            this.clear = function(){
                this.items = {}
            }

            this.size = function(){
                return this.keys().length
            }

            this.getItems = function(){   // 获取items的方法
                return items
            }
        }

        var dictionary = new Dictionary()

        dictionary.set('Gandalf','gandalf@email.com')
        dictionary.set('John','johnsnow@email.com')
        dictionary.set('Tyrion','tyrion@email.com')

        console.log(dictionary.has('Gandalf'))
        console.log(dictionary.size())
        console.log(dictionary.keys())
        console.log(dictionary.values())
        console.log(dictionary.get('Tyrion'))
        dictionary.delete('John')
        console.log(dictionary.keys())
        console.log(dictionary.values())
        console.log(dictionary.getItems())

哈希表

在学习了Dictionary类之后,我们会学习散列表,也就是哈希表。它是Dictionary类的另外一种实现方式

我在读到这里时,对于「字典」和「哈希表」2个慨念之间的区别还没弄清,当然书中也没有太多解释。

所以这里,我写记录一下自己查阅资料后的个人理解

「字典」指的是Key-Value这种存储数据的结构

那么哈希表也是字典,但是它是用另外的一种实现方式来做的。

那么我们我为什么要使用这种方式来实现「字典」呢,是不是字典存在某种缺点呢?

是的,比如说这样的一组字典数据

k1, k2, k3, k4, k4 ....
v1, v2, v3, v4, v5 ....

当你输入key,命令电脑查询key对应的value时,电脑其实是没法立刻找到key所对应的value呢

看上去是直接根据key得到value,但实际上电脑还是需要遍历k1、k2、k3去比较kn是否等于key。如果是的话就取出对应的值

那么为了省去这个遍历的过程,才想到用哈希表来实现「字典」

哈希表利用数组实现「字典」这种数据结构,那么用数组来实现字典的话,用户传入的key对应哪个索引呢?所以我们需要一个「散列算法」

「散列算法」,可以求出这个key在数组里对应的位置,用哈希表实现的目的就是尽可能快地在字典中找到一个值。


function HashTable(){
    let table = []

    // 散列算法
    var loseloseHashCode = function(key){
        var hash = 0;
        for(var i = 0 ; i<key.length ; i++){
            hash += key.charCodeAt(i)
        }
        return hash % 37   // 这里37是个随意选择的数值,把余数作为将来存储在数组的索引
    }
    this.put = function(key,value){
        var position = loseloseHashCode(key)
        console.log(position + '-' + key)
        table[position] = value
    }

    this.get = function(key){
        var position = loseloseHashCode(key)
        return table[position]
    }

    this.remove = function(key){
        var position = loseloseHashCode(key)
        table[position] = undefined
    }
}

哈希表的key值冲突

我们哈希表的散列算法,是为求出用户传入的key所对应的一个索引

那么这个索引是有可能重复的,一旦重复就会导致后面的值覆盖前面的值。

这种情况,叫做哈希表的冲突。所以我们了解2种处理冲突的方法原理

  • 分离链接: 把数组的每一个元素都实例成链表结构。这样key相同的值,也可以用单链表的形式不断存储。不会覆盖
  • 线性探查: 当用散列算法求出的这个索引index重复时,就index++,直到index不重复为止。

实现更好的散列函数

之前实现的'lose lose'散列函数,用来求值其实是非常容易产生key冲突的。

如果经常冲突的话,那么插入和查询一个元素的时间就会变慢(性能变差)

所以一个优秀的散列算法,应该是可以尽可能少出现冲突的

这里给出一个比较好的社区的实现。至于为什么这种算法,可以减少冲突我也不太理解,

可能是hash是质数,然后质数*33,最后在跟1013取模的话,不太会产生一样的余数,这个感觉是数学问题。

var djb2HashCode = function(key){
    var hash = 5381

    for(var i = 0 ; i < key.length ; i++){
        hash = hash*33 + key.charCodeAt(i)
    }

    return hash % 1013
}

简单回顾一下,集合、字典、哈希表吧

首先他们的元素都是不重复的

  • 集合: 是无序的,[value value]的形式的一组数据结构
  • 字典: 是由一对对 [key-value] 组成的数据结构
  • 哈希表: 是字典的另外一种实现方式,优点在于可能更快的根据key取到value

记录一下书中没搞清楚的小疑问:

《javascript数据结构和算法》中多次提到「顺序数据结构」和「非顺序数据结构」的慨念,并表示

- 「顺序数据结构」:栈、队列、数组、链表、集合
- 「非顺序数据结构」: 散列表 、 树

可是我觉得链表、集合是无序的,数组是有序的呀

这个「顺序数据结构」似乎并不是指的在内存中存储形式。问题先记下,回头再查把

Ziwei
4.1k 声望420 粉丝

前端程序员