阅读 138

第三期:数据结构(上)——先实现一下简单的数据结构

数据结构篇

js篇没有结束喔,只不过先复习一下数据结构吧

前两天看到一个大佬发的关于数据结构的博客,满满的字一个代码没有,众人都说太干了。然后小白就写一个只实现的吧

注意只是实现只是实现喔,因为这几个简单的数据结构说实话好像也没有什么值得解释的,不像平衡树大小堆那种

希望你有一些数据结构的基础,了解他们的基本结构

图中的图片,一部分是自己画的,一部分是百度图片找的。原来在某c开头的博客上记录笔记故有了上面的水印。此篇是先实现简单的数据结构

函数式编程的下篇毁于掘金更新那段时间了,很痛心,希望后面忍着泪补上

希望与大家共同进步,代码过多难免有疏漏。如有误,望指教,感恩,如若嫌弃本文同样太干,可翻阅一下小白之间的笔记(上半年纯小白时写的放到文章最后了)

额,可能代码太长排不了版

下篇链接

一、栈

一个栈的数据结构应实现一下功能

  • push 出栈
  • pop 入栈
  • peek 返回栈顶元素
  • isEmpty 判空
  • clear 清除栈中元素
  • size 返回栈中元素个数

实现及部分测试代码

 class Stack {
            constructor() {
                this._count = 0 //可记录栈内元素长度也可以当做栈内指针
                this._data = {} //用于存储栈内数据
            }
            isEmpty() {
                return this._count === 0
            }
            push(value) {
                this._data[this._count++] = value
            }
            pop() {
                if (this.isEmpty()) {
                    return undefined
                }
                this._count--;

                let res = this._data[this._count]
                delete this._data[this._count]

                return res
            }
            peek() {
                return this._data[this._count - 1]
            }
            size() {
                return this._count
            }
            clean() {
                this._data = {}
                this._count = 0
            }

        }

        const stack = new Stack()
        stack.push(5)
        stack.push(8)
        stack.push(10)

        console.log(stack.peek())
        console.log(stack.pop())
        console.log(stack)
        console.log(stack.size())
复制代码

二、队列

2.1 普通队列

一个队列的数据结构要实现以下方法

  • enqueue(e)向队尾添加元素
  • dequeue() 从对头出去一个元素
  • peek()返回队列中的第一个元素,也即:返回对头元素
  • isEmpty()判空
  • size()返回队列中元素的个数
  • clear() 清空

实现及部分测试代码

        class Queue {
            constructor() {
                this._data = {}
                this._count = 0
                this._first = 0 //队头指针
            }
            isEmpty() {
                return this._count === this._first
            }
            enqueue(value) {
                this._data[this._count++] = value
            }
            dequeue() {
                if (this.isEmpty()) {
                    return undefined
                }
                let res = this._data[this._first]
                delete this._data[this._first]
                this._first++;
                return res

            }
            peek() {
                if (this.isEmpty()) {
                    return undefined
                }
                return this._data[this._first]
            }
            size() {
                return this._count - this._first
            }
            clean() {
                this._data = {}
                this._count = 0
                this._first = 0
            }
        }
        const q = new Queue()
        q.enqueue(1)
        q.enqueue(2)
        q.enqueue(3)
        q.enqueue(4)
        console.log(q)
        console.log(q.dequeue())
        console.log(q.peek())
        console.log(q.size())
复制代码

2.2 双端队列

一个双端队列的数据结构要实现以下功能

双端队列

  • isEmpty
  • clear
  • size
  • addFront(e) 队头添加元素
  • addBack(e) 队尾添加元素
  • removeFront 队头移出元素
  • removeBack 队尾移出元素
  • peekFront 取队头元素
  • peekBack 取队尾元素

实现及部分测试代码

        class Deque {
            constructor() {
                this._data = {}
                this._count = 0
                this._first = 0
            }
            isEmpty() {
                    return this._count === this._first
                }
                //队尾进
            enqueue(value) {
                    this._data[this._count++] = value
                }
                //队头出
            dequeue() {
                    if (this.isEmpty()) {
                        return undefined
                    }
                    let res = this._data[this._first]
                    delete this._data[this._first]
                    this._first++;
                    return res

                }
                //队头进
            addFront(value) {
                    if (this.isEmpty()) {
                        this.enqueue(value)
                    }
                    this._data[--this._first] = value
                }
                //队尾出
            removeBack() {
                if (this.isEmpty()) {
                    return undefined
                }
                this._count--
                    let res = this._data[this._count]
                delete this._data[this._count]
                return res
            }
        }

        const d = new Deque()
        d.enqueue(1)
        d.enqueue(2)
        d.addFront(3)

        //    此时队列的数据从头到尾应是3,1,2
        //    调用队头出应是3
        console.log(d.dequeue())

        // 再掉队尾出应是2
        console.log(d.removeBack())
复制代码

其他比较简单上面已有实现思路,就不一一重复了

2.3 循环队列

我们在这个循环队列数据结构中实现以下功能

  • isEmpty 判空
  • isFull 判满
  • put(e)队尾插
  • poll()队头取

实现及部分测试代码

        class SqQueue {
            constructor() {
                this._data = {}
                this._front = 1 //头部指针
                this._rear = 1 //尾部指针
                this.MAX = 5
            }
            isEmpty() {
                return this._front === this._rear
            }
            isFull() {
                return (this._rear + 1) % this.MAX === this._front
            }
            put(value) {
                if (this.isFull()) {
                    return 'FULL'
                }
                this._data[this._rear] = value

                // 指针后移的同时也要考虑它是个环不能允许它超过最大值
                //即如果此时它在5的位置,假设此时头在2或3等位置未满,那么尾指针后移应该1而不能是6
                this._rear = (this._rear + 1) % this.MAX
            }
            poll() {
                if (this.isEmpty()) {
                    return undefined
                }
                let res = this._data[this._front]
                delete this._data[this._front]
                this._front = (this._front + 1) % this.MAX
                return res
            }
        }
        let sq = new SqQueue()
        sq.put(1)
        sq.put(2)
        sq.put(3)
        sq.put(4)
        console.log(sq.poll()) //1
        console.log(sq.poll()) //2
        console.log(sq.poll()) //3
        sq.put(2)
        sq.put(3)
        sq.put(4)

        // 此时从队尾 - > 队头应是4, 3, 2, 4
        console.log(sq.poll()) //4
        console.log(sq.poll()) //2
        console.log(sq.poll()) //3
        console.log(sq.poll()) //4
复制代码

三、链表

3.1 单链表

节点类

  class LNode {
            constructor(value) {
                this._value = value
                this._next = null //指针域
            }
        }
复制代码

链表类

  //单链表类
        class LinkList {
            constructor() {
                this._head = null //头节点
                this._count = 0
            }
        }
复制代码

我们接下来实现以下方法

  • push(element) 向尾部插一个
  • insert(element, position) 向指定位置插一个
  • getElementAt(index) 获取指定位置的元素
  • remove(element) 移出元素
  • indexOf(element) 返回该元素的位置
  • removeAt(position) 移出指定位置的元素
  • isEmpty() 判空
  • size() 链表长度

push

 push(value) {
                const node = new LNode(value)
                let current = this._head

                if (current === null) {
                    this._head = node
                } else {
                    // 不是空,顺着链条向下找,最后一个节点的指针域为空
                    while (current._next) {
                        current = current._next
                    }
                    current._next = node
                }

                this._count++
            }



//最后的测试代码

      const l = new LinkList()
        l.push(1)
        l.push(2)
        l.push(3)
        l.push(4)
        console.log(l)
复制代码

getElementAt及insert

这俩在一起写的是因为,我们可以在insert方法中借用一下getElementAt

  // 获取指定位置的节点
            getElementAt(index) {
                if (!(index >= 0 && index < this._count)) return undefined
                let current = this._head
                for (let i = 0; i < index; i++) {
                    current = current._next
                }
                return current

            }

            // 在任意位置插入
            insert(value, index) {
                if (!(index >= 0 && index <= this._count)) return undefined

                const node = new LNode(value)
                    // index==0
                if (index === 0) {
                    let current = this._head
                    this._head = node
                    this._head._next = current
                } else {
                    // 还是先找要操作的前一位置
                    let pre = this.getElementAt(index - 1)
                    let current = pre._next

                    pre._next = node
                    node._next = current

                }
                this._count++;

            }


//最后的测试代码
       l.push(1)
        l.push(2)
        l.push(3)
        l.push(4)
        l.insert(5, 1)
        console.log(l)
复制代码

可以看到5被查到index=1的位置了(从0开始的)

removeAt

// 移出指定位置的节点
                      removeAt(index) {
                if (!(index >= 0 && index < this._count)) return undefined
                let current = this._head
                if (index === 0) {
                    this._head = current._next
                } else {
                    let previous;
                    for (let i = 0; i < index - 1; i++) {
                        current = current._next
                    }
                    previous = current //指向要删除节点的前一个
                    current = current._next //指向当前要删除节点
                    previous._next = current._next


                    return current._value
                }
                this._count--


            }
//最后的测试代码
        const l = new LinkList()
        l.push(1)
        l.push(2)
        l.push(3)
        l.push(4)
        l.insert(5, 1)
        l.removeAt(1)
        console.log(l)

复制代码

可以看到上面刚插入的5又被移出了

indexOf

 //返回指定节点元素的位置
            indexOf(value) {
                let current = this._head
                    //遍历一下链表即可
                for (let i = 0; i < this._count && current != null; i++) {
                    if (current._value === value) {
                        return i
                    } else {
                        current = current._next
                    }
                }
            }

//最后的测试代码
const l = new LinkList()
        l.push(1)
        l.push(2)
        l.push(3)
        l.push(4)
        l.insert(5, 1)
        l.removeAt(1)
        console.log(l.indexOf(3));
        console.log(l)

复制代码

remove

  // 移出一个指定元素的节点
            remove(value) {
                let index = this.indexOf(value)
                this.removeAt(index)
            }

复制代码

isEmpty及size()

 //链表长度
            size() {
                return this._count
            }

            // 判空
            isEmpty() {
                return this.size() === 0
            }

复制代码

3.2 双向链表

         class Node {
            constructor(value) {
                this.value = value
                this.pre = null //前驱指针
                this.next = null //后继指针
            }
        }

        class DoubleLinkedList {
            constructor() {
                this.count = 0
                this.head = null
                this.q = null //指向尾节点
            }

            //根据位置返节点
            getElementAt(index) {
                if (index < 0 || index >= this.count) return undefined
                let current = this.head
                for (let i = 0; i < index; i++) {
                    current = current.next
                }
                return current
            }

            //指定位置插入
            insert(value, index) {
                if (index < 0 || index > this.count) return false
                const node = new Node(value)

                // 解析考虑三种情况,在头、在尾、在中间

                //头
                if (index === 0) {
                    // 又分为链表开始为空与不空
                    if (this.head === null) {
                        this.head = node
                        this.q = node
                    } else {
                        node.next = this.head
                        this.head.pre = node
                        this.head = node
                    }

                }

                // 尾部
                else if (index === this.count) {
                    this.q.next = node
                    node.pre = this.q

                    // 再让q指回尾部
                    this.q = node
                }
                // 中间
                else {
                    // 和单链表的操作一样,找到要操作的前一个
                    let pre = this.getElementAt(index - 1)
                    let current = pre.next

                    pre.next = node
                    node.next = current

                    node.pre = pre
                    current.pre = node
                }
                this.count++

            }

            // 指定位置移出
            removeAt(index) {
                if (index < 0 || index >= this.count)
                    return false

                // 处理情况也分为在头在尾在中间

                // 头
                if (index === 0) {
                    this.head = this.head.next

                    // 不能马上将前驱指针设为null,不能保证有没有第二个节点
                    if (this.count === 1) {
                        this.q = null
                    } else {

                        this.head.pre = null
                    }
                }

                //尾
                else if (index == this.count - 1) {
                    let current = this.q.pre
                    current.next = null
                    this.q = current
                }

                // 中间
                else {
                    // 还是先找它的前一个
                    let preNode = getElementAt(index - 1)
                    let current = preNode.next

                    preNode.next = current.next
                    current.next.pre = preNode
                }

                this.count--

            }
        }

        const test = new DoubleLinkedList();
        test.insert(1, 0)
        test.insert(2, 1)
        test.insert(3, 2)
        test.insert(4, 3)
        test.removeAt(3)
        console.log(test)

复制代码

3.3 循环链表

上面实现了双向链表,而循环链表则是对链表的进一步改进。使得它们可以头连尾,尾连头。构成一个闭环。

同样,它的很多操作是和单链表是一样的。在这我们只看它们任意位置插入和任意位置删除两个方法

insert

 class Node {
            constructor(value) {
                this.value = value
                this.pre = null //前驱指针
                this.next = null //后继指针
            }
        }

        class LinkedList {
            constructor() {
                    this.count = 0
                    this.head = null
                    this.q = null //指向尾节点
                }
                //根据位置返节点
            getElementAt(index) {
                if (index < 0 || index >= this.count) return undefined
                let current = this.head
                for (let i = 0; i < index; i++) {
                    current = current.next
                }
                return current
            }

            insert(value, index) {
                if (index < 0 || index > this.count) return false
                const node = new Node(value)

                // 头
                if (index === 0) {
                    if (this.head === null) {
                        this.head = node
                        this.q = node

                        node.pre = node
                        node.next = node
                    } else {
                        node.next = this.head
                        this.head.pre = node
                        this.head = node

                        this.head.pre = this.q
                        this.q.next = this.head
                    }
                }
                // 尾
                else if (index == this.count) {
                    this.q.next = node
                    node.pre = this.q
                    this.q = node
                    this.q.next = this.head
                    this.head.pre = this.q
                }
                // 中间
                else {
                    // 和原来一样
                    let preNode = this.getElementAt(index - 1)
                    let current = preNode.next

                    preNode.next = node
                    node.pre = preNode
                    current.pre = node
                    node.next = current

                }
                this.count++

            }
        }
const test = new LinkedList()
        test.insert(1, 0)
        test.insert(2, 1)
        test.insert(3, 2)

        console.log(test)

复制代码

1-2-3-1-2-3,最后的指针又指回了第一个节点实现循环

removeAt

remove(index) {
                if (index < 0 || index > this.count - 1) return false

                // 头
                if (index === 0) {

                    // 又分只有一个元素和多个
                    if (this.count === 1) {
                        this.head = null
                        this.q = null
                    } else {
                        this.head = this.head.next
                        this.head.pre = this.q
                        this.q.next = this.head
                    }

                }
                // 尾
                else if (index === this.count - 1) {
                    let current = this.q.pre
                    this.q = current
                    this.q.next = this.head
                    this.head.pre = this.q
                }
                // 中间
                else {
                    // 和原来一样,取到前一个
                    let preNode = this.getElementAt(index - 1)
                    let current = preNode.next

                    preNode.next = current.next
                    current.next.pre = preNode

                }
                this.count--

            }


//最后的测试

复制代码

看图删掉之后上一个是3,下个也是3

四、集合、字典、哈希表

4.1 集合

这里的集合基本和数学中的集合一致

可这么理解:它就由一组无序其唯一的项组成(这不就是set吗?)

集合的实现

开始实现,还是用对象作为存储数据的容器(容易保证唯一)

一个集合的数据结构通常有以下方法

  • add(element):向集合添加一个新元素。
  • delete(element):从集合移除一个元素。
  • has(element):如果元素在集合中,返回 true,否则返回 false。
  • clear():移除集合中的所有元素。
  • size():返回集合所包含元素的数量。它与数组的 length 属性类似。
  • values():返回一个包含集合中所有值(元素)的数组

比较简单直接看实现代码

 class Set {
            constructor() {
                this.container = {}
            }
            has(value) {
                return value in this.container
            }
            add(value) {
                if (this.has(value)) return false
                this.container[value] = value
            }
            delete(value) {
                if (!this.has(value)) return false
                delete this.container[value]
            }
            clear() {
                this.container = {}
            }
            size() {
                return Object.keys(this.container).length
            }
            value(){
                return Object.values(this.container)
            }
        }

复制代码

集合的运算

我们要实现以下运算

  • 并集
  • 交集
  • 差集
  • 子集
并集

实现思路:把创建一个新集合,将集合a和集合b的数据放进去

        function union(set01, set02) {
            const newSet = new Set()
            set01.value().forEach(item => newSet.add(item))
            set02.value().forEach(item => newSet.add(item))
            return newSet
        }

复制代码
交集

实现思路,还是借用一个新集合。然后随便拿一个a或b的数据进行操作,遍历操作时发现对方也有此数据就放到新集合

function intersection(set01, set02) {
            const newSet = new Set()
            set01.value().forEach(item => {
                if (set02.has(item)) {
                    newSet.add(item)
                }
            })
            return newSet
        }

复制代码
差集A-B

实现思路:借助一个新集合,遍历a集合数据时,判断b中有无,有就剔除

        function difference(set01, set02) {
            const newSet = new Set()
            set01.value().forEach(item => {
                if (!set02.has(item)) {
                    newSet.add(item)
                }
            })
        }

复制代码
子集

我们主要实现的方法是判断一个集合是否是另一个集合的子集

实现思路:如判断a集合是否是b集合的子集,我们要做的就是看a集合的所有元素是不是b中都有

       function isSubsetOf(a, b) {
            if (a.size() > b.size()) return false

            let flag = a.value().every(item => {
                return b.has(item)
            })

            return flag
        }

复制代码

4.2 字典

上面我们实现了集合,知道集合就是存放了一组互不重复的元素。字典和它相差不多,只不过集合里面只是保存了值,而在这里是以键值对的方式保存的

我们接下来要实现以下功能

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

这里都比较简单就直接上代码吧(简单的自己也没有怎么写测试代码,看思路就完事了)

        class Dictionary {
            constructor() {
                this.container = {}
            }

            // 根据指定键,判字典中有无
            hasKey(key) {
                return key in this.container
            }
            set(key, value) {
                if (key !== null && value !== null) {
                    this.container[key] = value
                }
            }
            remove(key) {
                if (this.hasKey(key)) {
                    delete this.container[key]

                }
            }
            get(key) {
                if (this.hasKey(key)) {
                    return this.container[key]
                } else {
                    return undefined
                }
            }
            keys() {
                return Object.keys(this.container)
            }
            values() {
                return Object.values(this.container)
            }
            keyValues() {
                return Object.entries(this.container)
            }
            size() {
                return this.keys().length
            }
            isEmpty() {
                return this.size() === 0
            }
            clear() {
                this.container = {}
            }

            forEach(fn) {
                const keys = this.keys()

                keys.forEach(item => fn(item, this.container[item]))
            }
        }

        const dic = new Dictionary();
        dic.set(1, "a");
        dic.set(2, "b");
        dic.set(3, "c");
        dic.set(4, "d");
        dic.set(5, "e");
        console.log(dic.hasKey(1));
        dic.remove(2);
        console.log(dic.get(3));
        console.log(dic.keyValues());
        console.log(dic.keys());
        dic.forEach((key, val) => {
            console.log(`key:${key} val:${val}`);

        })

复制代码

4.3 哈希表

散列表的实现,拿获取值来说。它需要我们提供一个哈希哈数处理我们的key。使其能够快速定位到我们存储数据的存储单元。

这里我们会介绍两种哈希函数,当然好理解的有缺陷,不好理解的最好用。

djb2HashCode(目前最好用的哈希函数)

      function 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取余)

        function 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;
        }

复制代码

哈希表的基本实现

 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, value) {
                if (key != null && value != null) {
                    let position = this._loseloseHashCode(key)
                    this.table[position] = value
                }
            }
            get(key) {
                let position = this._loseloseHashCode(key)
                if (this.table[position]) {
                    return this.table[position]
                } else {
                    return undefined
                }
            }
            remove(key) {
                let position = this._loseloseHashCode(key)
                if (this.table[position]) {
                    delete this.table[position]
                    return true
                } else {
                    return false
                }
            }
        }

复制代码

hash表完善之分离链接

如上丑图,键key1key2经过loseloseHashCode处理之后定位到的资源空间是同一块,为了防止数据覆盖的问题,我们将使用链表存放它们。虽然这里在链表中查找key1或key2时还是使用了线性查找。但是在这里也是没有法子的我们只能牺牲一下。不过注意的是我们这是在链表中保存的不止是val了。为了方便定位之后的线性查询我们要连key,val一块存在链表中。

实现

先拿过一个单链表来

        // 节点类
        class LNode {
            constructor(value) {
                this._value = value
                this._next = null //指针域
            }
        }

        //单链表类
        class LinkList {
            constructor() {
                this._head = null //头节点
                this._count = 0
            }
            push(value) {
                const node = new LNode(value)
                let current = this._head

                if (current === null) {
                    this._head = node
                } else {
                    // 不是空,顺着链条向下找,最后一个节点的指针域为空
                    while (current._next) {
                        current = current._next
                    }
                    current._next = node
                }

                this._count++
            }

            // 移出指定位置的节点
            removeAt(index) {
                if (!(index >= 0 && index < this._count)) return undefined
                let current = this._head
                if (index === 0) {
                    this._head = current._next
                }
                let previous;
                for (let i = 0; i < index - 1; i++) {
                    current = current._next
                }
                previous = current //指向要删除节点的前一个
                current = current._next //指向当前要删除节点
                previous._next = current._next

                this._count--;
                return current._value


            }

            // 获取指定位置的节点
            getElementAt(index) {
                if (!(index >= 0 && index < this._count)) return undefined
                let current = this._head
                for (let i = 0; i < index; i++) {
                    current = current._next
                }
                return current

            }

            // 在任意位置插入
            insert(value, index) {
                if (!(index >= 0 && index < this._count)) return undefined

                const node = new LNode(value)
                    // index==0
                if (index === 0) {
                    let current = this._head
                    this._head = node
                    this._head._next = current
                } else {
                    // 还是先找要操作的前一位置
                    let pre = this.getElementAt(index - 1)
                    let current = pre._next

                    pre._next = node
                    node._next = current

                }
                this._count++;

            }

            //返回指定节点元素的位置
            indexOf(value) {
                let current = this._head
                    //遍历一下链表即可
                for (let i = 0; i < this._count && current != null; i++) {
                    if (current._value === value) {
                        return i
                    } else {
                        current = current._next
                    }
                }
            }

            // 移出一个指定元素的节点
            remove(value) {
                let index = this.indexOf(value)
                this.removeAt(index)
            }

            //链表长度
            size() {
                return this._count
            }

            // 判空
            isEmpty() {
                return this.size === 0
            }

        }

复制代码

改造哈希表,在借一个类用来存数据的key和value

  class Node {
            constructor(key, value) {
                this.key = key
                this.value = value
            }
        }
        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, value) {
                if (key != null && value != null) {
                    let position = this._loseloseHashCode(key)
                        // 事先判断一下此位置是否有数据
                    if (!this.table[position]) {
                        this.table[position] = new LinkList()
                    }
                    this.table[position].push(new Node(key, value))
                }
            }
            get(key) {
                let position = this._loseloseHashCode(key)
                if (this.table[position]) {

                    let current = this.table[position].getHead()
                    while (current) {
                        if (current._value.key === key) {
                            return current._value.value
                        }
                        current = current.next
                    }
                    //没找到
                    return undefined
                } else {
                    return undefined
                }
            }
            remove(key) {
                let position = this._loseloseHashCode(key)
                if (this.table[position]) {
                    let current = this.table[position].getHead()

                    while (current) {
                        if (current._value.key === key) {
                            this.table[position].remove(current._value)

                            // 删除完如此时链表空了则删除它
                            if (this.table[position].isEmpty()) {
                                delete this.table[position]
                            }
                            return true
                        }
                        current = current._next
                    }
                    // 在链表中没有找到
                    return false

                } else {
                    return false
                }
            }
        }

        const h = new HashTable()
        h.put('name', 'gongxiaobai')
        h.put('name', 'zhangsan')
        h.put('age', '18')
        h.remove('age')
        console.log(h)
        console.log(h.get('name'))

复制代码

如上的测试代码,用两个key一样的测试一下

hash表完善之线性探查

这个比分离链表还要简单一些,它是key定位到15的位置时发现为空就把它的信息节点保存到这,key也是定位15的,但是它来的晚一看已经被人占了。就向下找,找到一个空地就把自己的信息节点保存下来。

信息节点中的东西还是key和val。(方便查询)

逻辑实现还是很顺的,只不过有一个新的点要考虑。就是在节点删除的时候,比如上图我把key1删掉了。那么key2就会很尴尬。因为我们定位都是定位到key1的位置的,一判断key1的位置是空那我们下面实现的get和remove还怎么玩。我们连key2的信息都拿不到。因此在数据移出的时候我们要对它下面的元素位置进行修复(难点)

这里的难点就是移出节点还得修复

其实仔细想一下也不能,也就是把下面的经过哈希函数处理之后key相同的那些东西往上移动位置。

具体什么意思呢?就是这个线性探查首先是个数组一样,如上图左边的key是从上往下一次增大的(或者相等)。移出一个节点之后,我们要看看下面是否有与当前key的哈希值相同或者小于它的,有则需要向上一个坑一个坑的移动

  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
                }
            }

            // 新增一条到散列表
            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) {
                let 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].key === key) {
                        return this.table[index].val
                    } else {
                        return undefined
                    }

                }
                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._move(key, position)
                        return true
                    } else {
                        let index = position + 1
                        while (this.table[index] != null && this.table[index].key !== key) {
                            index++
                        }
                        if (this.table[index].key === key) {
                            delete this.table[index]

                            // 修复下面的位置
                            this._move(key, index)
                            return true
                        } else {
                            return false
                        }
                    }

                }
                return false

            }

            //难点
            _move(key, removedPosition) {
                let hash = this._loseloseHashCode(key)
                let index = removedPosition + 1
                while (this.table[index] != null) {

                    let posHash = this._loseloseHashCode(this.table[index].key)
                        // 如果当前元素的 hash 值小于或等于原始的 hash 值(行{5})或者当前元素的 hash 值小于或等于 removedPosition(也就是上一个被移除 key 的 hash 值),表示我们需要将当前元素移动至 removedPosition 的位置
                    if (posHash <= hash || posHash <= removedPosition) {
                        this.table[removedPosition] = this.table[index]
                        delete this.table[index]
                        removedPosition = index
                    }
                    index++
                }
            }
        }
        const h = new LineHashTable()
        h.put('name', 'gongxiaobai')
        h.put('name', 'zhangsan')
        h.put('age', '18')
        h.remove('age')
        console.log(h)
        console.log(h.get('name'))

复制代码

参考书籍:

王道考研数据结构

严奶奶的那本(数据结构(C语言版) 第2版 (严蔚敏等))

学习js数据结构与算法

写到最后

小白原来的笔记链接(有一些错误没有进行更改)

星光不问赶路人,时光不负有心人 期待着我们的下一次邂逅