js版数据结构_07散列表
ps:今天晚上写之前没考虑这一节代码量的问题,为保证时间没有测试。但是主要都是逻辑问题,看逻辑便ok
知识点
- 散列表是什么
- hash表的基本实现
- hash表完善之使用分离链接
- hash表完善之使用线性探查
1. 散列表(哈希表)
找的这两个图比较能体现散列表的特点,我们至今已经总结了不少的数据结构。但是回想一样在他们的取值实现中,我们为取到一个值往往是要对整个结构进行遍历。这种操作效率是非常差的,我们希望可能有一种数据结构的数据获取十分迅速。最好时间复杂度为O(1)。于是散列表这种数据结构出现了。
散列表的实现,拿获取值来说。它需要我们提供一个哈希哈数处理我们的key。使其能够快速定位到我们存储数据的存储单元。
这里我们会介绍两种哈希函数,当然好理解的有缺陷,不好理解的最好用。
djb2HashCode(目前最好用的哈希函数)
djb2HashCode(key) {
const tableKey = this.toStrFn(key);
let hash = 5381;
for (let i = 0; i < tableKey.length; i++) {
hash = (hash * 33) + tableKey.charCodeAt(i);
}
return hash % 1013;
}
loseloseHashCode(好理解,它只是将字符串的ascii码与37取余)
loseloseHashCode(key) {
if (typeof key === 'number') {
return key;
}
let hash = 0;
for (let i = 0; i < key.length; i++) {
hash += tableKey.charCodeAt(i);
}
return hash % 37;
}
2. hash表的基本实现
它的基本实现很简单,不做解释了。先说一个它的漏洞也是loseloseHashCode的不足,使用loseloseHashCode这种哈希函数,总会使得一些字符串的ascii码值是相同的。这便会产生数据覆盖的问题。显然这不是我们希望遇见到。下面的两节便是为了解决这个问题。先来看它的基本实现
class HashTable {
constructor() {
this.table = {};
}
// 先创建散列函数
loseloseHashCode(key) {
if (typeof key === "number") {
return key
} else {
let hash = 0;
for (let i = 0; i < key.length; i++) {
hash = hash + key.charCodeAt(i);
}
return hash % 37;
}
}
// 新增一条到散列表
put(key, val) {
if (key != null && val != null) {
const position = this.loseloseHashCode(key);
this.table[position] = val;
return true;
}
return false;
}
// 取值
get(key) {
const position = this.loseloseHashCode(key);
if (this.table[position]) {
return this.table[position];
} else {
return undefined;
}
}
// 移出值
remove(key) {
const position = this.loseloseHashCode(key);
if (this.table[position]) {
delete this.table[position];
return true;
} else {
return false;
}
}
}
3. hash表完善之使用分离链接
如上丑图,键key1key2经过loseloseHashCode处理之后定位到的资源空间是同一块,为了防止数据覆盖的问题,我们将使用链表存放它们。虽然这里在链表中查找key1或key2时还是使用了线性查找。但是在这里也是没有法子的我们只能牺牲一下。不过注意的是我们这是在链表中保存的不止是val了。为了方便定位之后的线性查询我们要连key,val一块存在链表中。
看实现:
// 创建一个节点类
class LNode {
constructor(e) {
this.element = e;
this.next = null;
}
}
//链表类
class LinkList {
constructor() {
this.count = 0;
this.head = null;
}
getHead() {
return this.head;
}
push(e) {
const node = new LNode(e);
let current = this.head;
// 先看链表是不是空的
if (current == null) {
this.head = node;
} else {
// 想一下最后一个节点的特点,最后一个节点的next为null
while (current.next) {
current = current.next;
}
current.next = node;
}
this.count++;
}
removeAt(index) {
// 先判读这个index是否越界
if (index >= 0 && index < this.count) {
let current = this.head;
// 看index是不是head位置
if (index === 0) {
this.head = current.next;
} else {
let previous;
// 找位置
for (let i = 0; i < index; i++) {
previous = current;
current = current.next;
}
previous.next = current.next;
}
this.count--;
return current.element;
} else {
return undefined;
}
}
getElementAt(index) {
// 判断是否越界
if (index >= 0 && index < this.count) {
let node = this.head;
for (let i = 0; i < index && node != null; i++) {
node = node.next;
}
return node;
} else {
return undefined;
}
}
// 在任意位置插入
insert(e, index) {
// 还是先判断是否越界
if (index >= 0 && index < this.count) {
let node = new LNode(e);
// 0号位置判断
if (index == 0) {
let current = this.head;
node.next = current;
this.head = node;
} else {
// 先找到索引的前一号位置,一截两瓣
let pre = this.getElementAt(index - 1);
let current = pre.next;
pre.next = node;
node.next = current;
}
this.count++;
return true;
} else {
return undefined;
}
}
// 返回一个元素的位置
indexOf(e) {
// 到循环遍历链表就ok
let current = this.head;
for (let i = 0; i < this.count && current != null; i++) {
if (current.element == e) {
return i;
} else {
current = current.next;
}
}
// 循环完链表仍然没有找到
return -1;
}
// 删除指定元素
remove(e) {
// 我们前面实现一个根据位置删除
// 还实现了一个根据指定元素返回位置
let index = this.indexOf(e);
return this.removeAt(index);
}
size() {
return this.count;
}
isEmpty() {
return this.count == 0;
}
}
class LinkListNode {
constructor(key, val) {
this.key = key;
this.val = val;
}
}
class HashTable {
constructor() {
this.table = {};
}
// 先创建散列函数
loseloseHashCode(key) {
if (typeof key === "number") {
return key
} else {
let hash = 0;
for (let i = 0; i < key.length; i++) {
hash = hash + key.charCodeAt(i);
}
return hash % 37;
}
}
// 新增一条到散列表
put(key, val) {
if (key != null && val != null) {
const position = this.loseloseHashCode(key);
if (!this.table[position]) {
this.table[position] = new LinkList();
}
this.table[position].push(new LinkListNode(key, val));
return true;
} else {
return false;
}
}
// 取值
get(key) {
const position = this.loseloseHashCode(key);
if (this.table[position]) {
// 遍历链表取值
if (!this.table[position].isEmpty()) {
let current = this.table[position].getHead();
while (current != null) {
if (current.element.key === key) {
return current.element.val;
}
current = current.next;
}
return undefined;
}
} else {
return undefined;
}
}
// 移出值
remove(key) {
const position = this.loseloseHashCode(key);
let linkList = this.table[position];
if (linkList != null && !linkList.isEmpty()) {
let current = linkList.getHead();
// 链表我们实现两种删除元素1根据元素值2根据索引
// 显然这里用值好
while (current) {
if (current.element.key === key) {
linkList.remove(current.element);
// 删完如链表为空则删除此位置
if (linkList.isEmpty()) {
delete this.table[position]
}
return true;
}
current = current.next;
}
}
return false;
}
}
4. hash表完善之使用线性探查
这个比分离链表还要简单一些,它是key定位到15的位置时发现为空就把它的信息节点保存到这,key也是定位15的,但是它来的晚一看已经被人占了。就向下找,找到一个空地就把自己的信息节点保存下来。
信息节点中的东西还是key和val。(方便查询)
逻辑实现还是很顺的,只不过有一个新的点要考虑。就是在节点删除的时候,比如上图我把key1删掉了。那么key2就会很尴尬。因为我们定位都是定位到key1的位置的,一判断key1的位置是空那我们下面实现的get和remove还怎么玩。我们连key2的信息都拿不到。因此在数据移出的时候我们要对它下面的元素位置进行修复
实现:
class Node {
constructor(key, val) {
this.key = key;
this.val = val;
}
}
class LineHashTable {
constructor() {
this.table = {};
}
// 先创建散列函数
loseloseHashCode(key) {
if (typeof key === "number") {
return key
} else {
let hash = 0;
for (let i = 0; i < key.length; i++) {
hash = hash + key.charCodeAt(i);
}
return hash % 37;
}
}
verifyRemoveSideEffect(key, removedPosition) {
const hash = this.hashCode(key);
let index = removedPosition + 1;
while (this.table[index] != null) {
const posHash = this.hashCode(this.table[index].key);
if (posHash <= hash || posHash <= removedPosition) {
this.table[removedPosition] = this.table[index];
delete this.table[index];
removedPosition = index;
}
index++;
}
}
// 新增一条到散列表
put(key, val) {
if (key != null && val != null) {
const position = this.loseloseHashCode(key);
if (this.table[position] == null) {
// 为空放入节点
this.table[position] = new Node(key, val);
} else {
// 向下找空的储存空间
let index = position + 1;
while (!this.table[index]) {
index++;
}
this.table[index] = new Node(key, val)
}
return true;
}
return false;
}
// 取值
get(key) {
const position = this.loseloseHashCode(key);
// 先直接到lost函数所能快速定位的地方
if (this.table[position] != null) {
// 再判是不是我们要的值
if (this.table[position].key === key) {
return this.table[position].val;
}
// 不是则需要在此地的基础上向下寻找了
let index = position + 1;
while (this.table[index] != null && this.table[index].key != key) {
index++;
}
// 上面出来了有两种可能
// 1.找到目标
// 2.有一个空的地址
if (this.table[index] != null && this.table[index].key === key) {
return this.table[index].val;
}
}
return undefined;
}
// 移出值
remove(key) {
const position = this.loseloseHashCode(key);
if (this.table[position] != null) {
// 判此时这个位置上的使我们要的吗
if (this.table[position].key === key) {
delete this.table[position];
// 修复下面的位置
this.verifyRemoveSideEffect(key, position);
return true;
}
let index = position + 1;
while (this.table[index] != null && this.table[index].key !== key) {
index++;
}
if (this.table[index] != null && this.table[index].key === key) {
delete this.table[index];
// 修复下面的位置
this.verifyRemoveSideEffect(key, index);
return true;
}
}
return false;
}
}