js版数据结构_07散列表

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;

            }
        }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值