JS中的字典与散列

73 阅读4分钟

JS中的字典与散列

1. 字典的概念

  • 字典是以键值对形式存储数据的数据结构,其中键就是你⽤来查找的索引,值就是与键对应的数值。

  • 字典也称做映射,符号表或者关联数组,以[键,值]的形式存储。

  • 数组形式:['11','12','13']

  • 字典形式:{"age":20,"name":"tom",sex:'男'},可以通过key取出value

2.字典的方法

方法说明
set(key,value):向字典中添加新元素。如果 key已经存在,那么已存在的value会被新的值覆盖
remove(key)通过使⽤键值作为参数来从字典中移除键值对应的数据值。
hasKey(key)如果某个键值存在于该字典中,返回 true,否则返回 false
get(key)通过以键值作为参数查找特定的数值并返回。
clear()删除该字典中的所有值
size()返回字典所包含值的数量。与数组的length属性类似。
isEmpty()在size等于零的时候返回true,否则返回false。
keys()将字典所包含的所有键名以数组形式返回。
values()将字典所包含的所有数值以数组形式返回。
keyValues()将字典中所有[键,值]对返回。
forEach(callbackFn)迭代字典中所有的键值对。

3.创建字典

function Dictionary() {
  this.items = {}
  Dictionary.prototype.set = function (key, value) {
    this.items[key] = value
  }
  Dictionary.prototype.keyValues = function () {
    return this.items
  }
  Dictionary.prototype.hasKey = function (key) {
    return this.items.hasOwnProperty(key)
  }
  Dictionary.prototype.remove = function (key) {
    if (!this.items[key]) return
    delete this.items[key]
    return true
  }
  Dictionary.prototype.get = function (key) {
    return this.hasKey(key) ? this.items[key] : undefined
  }
  Dictionary.prototype.keys = function () {
    return Object.keys(this.items)
  }
  Dictionary.prototype.values = function () {
    return Object.values(this.items)
  }
  Dictionary.prototype.size = function () {
    return this.keys().length
  }
  Dictionary.prototype.clear = function () {
    this.items = {}
  }
}

4. 字典的实践

调用并打印调试结果

const dictionary = new Dictionary();
dictionary.set('name', 'Tom');
dictionary.set('age', '12');
dictionary.set('sex', '男');
console.log(dictionary.keyValues()); //{name: 'Tom', age: '12', sex: '男'}
console.log(dictionary.hasKey('name')); //true
console.log(dictionary.hasKey('class')); //false
console.log(dictionary.size()); //3
console.log(dictionary.values()); //['Tom', '12', '男']
console.log(dictionary.keys()); //['name', 'age', 'sex']
console.log(dictionary.get('name')); //Tom
console.log(dictionary.remove('name')); //true
console.log(dictionary.keys()); //['age', 'sex']
console.log(dictionary.clear()); //undefined
console.log(dictionary.keys()); //[]

5. 字典相关的算法

经典案例:两数之和

输⼊:nums = [2,7,11,15], target = 9

输出:[0,1]

解释:我们从数组中找到相加和等于9的值,满⾜直接输出,这个过程中我们直接新建⼀个字典,然后利

⽤差值进⾏⽐对即可选出匹配的值,接下来我们直接上代码

function matching(nums, target) {
  const map = new Map()
  for (let i = 0; i < nums.length; i++) {
    const n = nums[i]
    const n2 = target - n
    if (map.has(n2)) {
      return [map.get(n2), i]
    } else {
      map.set(n, i)
    }
  }
}
const nums = [2, 7, 11, 15]
const res = matching(nums, 9)
console.log('print result', res)
// print result [ 0, 1 ]

6. 散列的概念

6.1 作用

散列算法的作用是尽可能快的在数据结构中找到一个值;

散列函数的作用就是给定一个键值,然后返回在表中的位置

6.2 Hash

散列表也成为Hash表,为什么要用 Hash呢?数组或者链表来存储元素,一旦存储的内容数量很多,就会占用很大的空间,而且在查找某个元素是否存在的过程中,数组和链表都需要挨个比较循环,而通过哈希计算,可以大大减少 比较的次数

6.3 定义

  • 散列表即HashTable类,也叫做 HashMap类,是字典类的一种散列实现方式
  • 散列算法的作用是尽可能的在数据结构中 找到一个值
  • 常见的散列函数--lose,她是简单地将键值中的每个字母的ASCII值相加

6.4 实现原理

  • 哈希表 就是吧key通过一个固定算法函数,即所谓的哈希函数 转成一个整形数字,然后将该数字对数组长度进行取余,取余的结果就作为数组的下标,将value存储在已改数字为下标的数组空间里。
  • 当使用哈希表进行查询的时候,就是再次使用哈希函数将key专为对应的数组的下标,并定位到该空间获取value,如此一来就可以充分利用到数组定位性能,进行数据定位。

7. 散列的方法

方法名称说明
put(key,value)向散列表增加⼀个新的项
remove(key)根据键值从散列表中移除值。
get(key)返回根据键值检索到的特定的值。

8. 实现散列表与应用

class HashTable {
  constructor() {
    this.table = []
  }
  hashMethod(key) {
    if (typeof key === 'number') return key;
    let hash = 5381
    for (let i = 0; i < key.length; i++) {
      hash = hash * 33 + key.charCodeAt(i)
    }
    return hash % 1031
  }

  put(key, value) {
    const pos = this.hashMethod(key)
    this.table[pos] = value
  }
  get(key) {
    const pos = this.hashMethod(key)
    return this.table[pos]
  }
  remove(key) {
    const pos = this.hashMethod(key)
    delete this.table[pos]
  }
  //将 key 转化为字 符串的函数
  toStringFn(item) {
    if (item === null) {
      return 'NULL'
    } else if (item === undefined) {
      return 'UNDEFINED'
    } else if (typeof item === 'string' || item instanceof String) {
      return `${item}`
    }
    return item.toString()
  }
  // key 的 每个字符 ASCII 码之和
  loseHashCode(key) {
    if (typeof key === 'number') {
      return key
    }
    const tableKey = this.toStringFn(key)
    let hash = 0
    for (let i = 0; i < tableKey.length; i++) {
      hash += tableKey.charCodeAt(i)
    }
    return hash % 37
  }
  hashCode(key) {
    return this.loseHashCode(key)
  }
  print(){
    this.item.forEach((value,key)=>{
      if(value != undefined){
        console.log(`key: ${key} -- value:${value}`)
      }
    })
  }
}

const hash = new HashTable(); 
hash.put('name', 'Tom'); 
hash.put('age', '12'); 
hash.put('sex', '男'); 
console.log(hash.get('name')); //Tom
console.log(hash.toStringFn('name')); //name
console.log(hash.hashCode('age')); //5
hash.remove('age')
console.log(hash.get('age')); //undefined

9. 哈希冲突

哈希是通过 怼数据进行压缩,从而提高效率的一种方法,但是由于哈希函数有限,这个过程就会造成哈希冲突,从而对数据有效压缩存在 一定的阻碍。

9.1 哈希函数常用的构造方法

  • 除留余数法
h(K)=KmodMh(K) = K \mod M

以得到的余数作为哈希地址,这种方法计算简单,适用范围广, 并且映射到m个数字元素下标的概率相等,从而减少冲突的发生

  • 直接地址法
h(K)=K+Ch(K) = K + C

C 为某个设定的数值常量,计算简单,并且不可能发生冲突,但是可能造成内存单元的大量浪费

  • 数字分析法

取数据元素关键字中某些取值较均匀的数字构造哈希函数,他只是用于所有关键字已知的 情况

当已知所有关键字值,可对关键字中每一位取值分布情况作出分析 ,从而可以把一个很大的关键字取值区间转换为较小的关键字取值区间

9.2 哈希冲突

假设采用hash函数:"除留余数法",插入这些值:217、710、19、30、145、

H(K) = 217 % 7 = 0
H(K) = 701 % 7 = 1 
H(K) = 19 % 7 = 2 
H(K) = 30 % 7 = 2 
H(K) = 145 % 7 = 5

9.2.1 解决思路

​ 解决的技术分为两种: 开散列法闭散列法

​ 常见的解决策略:

​ - 线性探测(Linear probing)

​ - 双重哈希(Double hashing)

​ - 随机散列(Random hashing)

​ - 分离链接(Separate chaining)

9.2.2 线性探测

使⽤的⽅法是,使⽤散列函数H(K)在⼤⼩为M的表中插⼊密钥K ;

优点缺点
易于实施;在表不满的情况下,总是找到⼀个位置;表的相邻插槽中会形成“集群”或“集群”键;
当表不是很满时,平均情况下的性能⾮常好。当填满整个阵列的⼤部分时,性能会严重下降,因为探针序列执⾏的⼯作实际上是对⼤部分阵列的穷举

案例测试:

H(K) **=** 701 **%** 7 **=** 1 

H(K) **=** 145 **%** 7 **=** 5 

H(K) **=** 217 **%** 7 **=** 0 

H(K) **=** 19 **%** 7 **=** 2 

H(K) **=** 13 **%** 7 **=** 1(冲突) **-->** 2(已经有值) **-->** 3(插⼊位置3) 

H(K) **=** 749 **%** 7 **=** 2(冲突) **-->** 3(已经有值) **-->** 4(插⼊位置4) 

9.2.3 双重哈希

双重哈希的思想:

  • 使偏移到下⼀个探测到的位置取决于键值,因此对于不同的键可以不同。

  • 需要引⼊第⼆个哈希函数 H 2(K),⽤作探测序列中的偏移量,将线性探测视为 H 2(K)== 1 的双重哈

希。

解决冲突

设置 indx =(indx + offset)mod M.

9.2.4 随机散列

  • 随机哈希通过使探测序列取决于密钥来避免聚类

  • 探测序列是由密钥播种的伪随机数⽣成器的输出⽣成的,在这个执⾏过程中,可能出现种⼦组件⼀起使⽤,该组

件对于每个键都是相同的,但是对于不同的表是不同的。

解决冲突

发⽣碰撞,设置 indx = RNG.next() mod M.

9.3 线性探测法实现

对与线性探查法的实现原理,就是当想向表中某个位置加⼊⼀个新元素的时候,如果索引为 index 的位置已经被占据了,就尝试 index+1 的位置。如果 index+1 的位置也被占据了,就尝试 index+2 的位置,以此类推。

下⾯我们来看看是实现的⽅法

function HashTable() {
  let table = [];
  // 散列函数
  this.loseloseHashCode = function (key) {
    let hash = 0;
    for (let i = 0; i < key.length; i++) {
      hash += key.charCodeAt(i);
    }
    return hash % 37; // 为了得到⼀个较⼩的数值,会使⽤hash值和任意⼀个数做除法的余数;
  };
  function ValuePair(key, value) {
    this.key = key;
    this.value = value;
    this.toString = function () {
      return `[${key}-${value}]`;
    };
  }
  this.put = function (key, value) {
    let position = this.loseloseHashCode(key);
    if (table[position] === undefined) {
      table[position] = new ValuePair(key, value);
    } else {
      let index = ++position;
      while (table[index] !== undefined) {
        index++;
      }
      table[index] = new ValuePair(key, value);
    }
  };
  this.get = function (key) {
    const position = this.loseloseHashCode(key);
    if (table[position] !== undefined) {
      if (table[position].key === key) {
        return table[position].value;
      } else {
        let index = ++position;
        while (table[index] === undefined || table[index].key !== key) {
          index++;
        }
        if (table[index].key === key) {
          return table[index].value;
        }
      }
    }
    return undefined;
  };
  this.hashCode = function (key) {
    return this.loseloseHashCode(key);
  };
  this.remove = function (key) {
    const position = this.loseloseHashCode(key);
    if (table[position] !== undefined) {
      if (table[position].key === key) {
        table[position] = undefined;
      } else {
        let index = ++position;
        while (table[index] === undefined || table[index].key !== key) {
          index++;
        }
        if (table[index].key === key) {
          table[index] = undefined;
        }
      }
      return true;
    }
    return false;
  };
}

const hash = new HashTable();
hash.put("name", "Tom");
hash.put("age", "12");
hash.put("sex", "男");
console.log(hash.get("name")); //Tom
console.log(hash.hashCode("age")); //5
hash.remove("age");
console.log(hash.get("age")); //undefined