数据结构与算法——哈希表,数组加强哈希表,双链表加强哈希表
文章目录
- 哈希表
- 1. 数组实现hash表
- 2. 双链表实现hash表
哈希表
key是唯一的,value可以重复
哈希表和我们常说的 Map(键值映射)不是同一个东西。
【Map】是一个 Java 接口,仅声明了若干个方法,并没有给出方法的具体实现;HashMap 这种数据结构根据自身特点实现了这些操作。
可以说hashmap的get、put、remove等方法复杂度为O(1),但是map接口的复杂度不一定,需要看他底层数据结构的实现方法。
【哈希函数】的作用是把任意长度的输入(key)转化成固定长度的输出(索引),类比数组。增删查改时都会用到哈希函数,所以哈希函数的算法设计与复杂度紧紧相关。
任意 Java 对象都有一个 int hashCode()
方法,在实现自定义的类时,如果不重写这个方法,返回值可以认为是该对象的内存地址。一个对象的内存地址显然是全局唯一的一个整数。
所以调用 key
的 hashCode()
方法就相当于把 key
转化成了一个整数,且这个整数是全局唯一的。
【哈希冲突】两个不同的key通过哈希函数得到了相同的索引。因为 hash
函数相当于把一个无穷大的空间映射到了一个有限的索引空间,所以必然会有不同的 key
映射到同一个索引上。
【拉链法和开放寻址法】用于解决哈希冲突。
【负载因子】= size/table.length
,需要避免负载因子过大,会导致哈希表性能下降。理论上拉链法负载因子能达到无穷大,开放寻址法负载因子不超过1。
【扩容】当哈希表内元素达到负载因子时,哈希表会扩容。
1. 数组实现hash表
数组keys存储所有键,indexMap存储键在数组中的索引,valueMap存储键值对,增删查改时实现时间复杂度为O(1)
编写randomKey()
,在hash表中实现等概率随机生成一个键,解决了hash表中,因为哈希冲突产生的“空洞”导致不能等概率读取的问题。
class RandomizedHashTable {
constructor() {
this.keys = []; //存储所有键
this.indexMap = new Map(); //键:索引,存储键在数组中的索引
this.valueMap = new Map(); //键:值,存储键值对
}
//添加元素
mySet(key, value) {
//如果键不存在,则新建
if (!this.valueMap.has(key)) {
this.indexMap.set(key, this.keys.length);
this.keys.push(key);
}
//如果键已经存在,直接修改值value
this.valueMap.set(key, value);
}
//获取元素
myGet(key) {
return this.valueMap.get(key);
}
//删除元素
myDelete(key) {
//如果key不存在
if (! this.valueMap.has(key)) { return false;}
//如果key存在
//通过键:索引map获取要删除元素的索引
const index = this.indexMap.get(key);
//获取最后一个元素的键
const lastKey = this.keys[this.keys.length - 1];
//将最后一个元素移动到当前删除位置
this.keys[index] = lastKey;
this.indexMap.set(lastKey, index);
//删除最后一个元素
this.keys.pop();
this.indexMap.delete(key);
this.valueMap.delete(key);
return true;
}
//均匀随机生成一个键
randomKey() {
if (this.keys.length === 0) { return false;}
const randomIndex = Math.floor(Math.random() * this.keys.length);
return this.keys[randomIndex];
}
}
const example =new RandomizedHashTable();
example.mySet(1, 'a');
example.mySet(2, 'b');
example.mySet(3, 'c');
console.log(example.randomKey());
2. 双链表实现hash表
【算法实现:】
注意:hash表与hash链表同步操作
class ListNode {
constructor(key, value) {
this.key = key;
this.value = value;
this.prev = null;
this.next = null;
}
}
class HashLinkedList {
constructor(maxsize = 10) {
this.maxsize = maxsize;
this.hashMap = new Map();
this.head = new ListNode(0, 0);
this.tail = new ListNode(0, 0);
this.head.next = this.tail;
this.tail.prev = this.head;
}
//移动到头部
moveToHead(node) {
//先断开原本连接
node.prev.next = node.next;
node.next.prev = node.prev;
//插入到头部
node.prev = this.head;
node.next = this.head.next;
this.head.next.prev = node;
this.head.next = node;
}
// 增加到头部操作
addToHead(node) {
node.prev = this.head;
node.next = this.head.next;
this.head.next.prev = node;
this.head.next = node;
}
//删除尾部元素
removeTail() {
const lastNode = this.tail.prev;
//从hash表中删除
this.hashMap.delete(lastNode.key);
//从hash链表中删除
lastNode.prev.next = this.tail;
this.tail.prev = lastNode.prev;
return lastNode;
}
//查询操作
myGet(key) {
if(!this.hashMap.has(key)) { return -1;}
const node = this.hashMap.get(key);
this.moveToHead(node);
return node.value;
}
//插入/更新
myPut(key, value) {
//如果key已经存在则更新value
if(this.hashMap.has(key)) {
const node = this.hashMap.get(key);
node.value = value;
//移动到头节点处
this.moveToHead(node);
return;
}
//如果key不存在则添加新key和value
const newnode = new ListNode(key, value);
//注意是添加一个节点对象newnode
this.hashMap.set(key,newnode);
this.addToHead(newnode);
//如果超过容量则删除尾部元素
if(this.hashMap.size > this.maxsize) {
this.removeTail();
}
}
//删除操作
myDelete(key) {
if(!this.hashMap.has(key)) { return false;}
const node = this.hashMap.get(key);
//从链表删除
node.prev.next = node.next;
node.next.prev = node.prev;
//从hash表删除
this.hashMap.delete(key);
return true;
}
}
const example = new HashLinkedList(2);
example.myPut(1, 'a');
example.myPut(2, 'b');
console.log(example.myGet(1));