JavaScript数据结构——链表(3)

133 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第24天,点击查看活动详情

前言

自上次写完链表实现已过去一周左右。今天我们一起来实现链表的进阶之双向链表。什么是双向链表?

双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表。——百度百科

简单讲,我们上次实现的链表中有一个next指针指向下一个节点,双向链表就是每个节点不止有next指针指向后继节点,还有prev指针指向前驱节点。由这种节点组成的链表就是双向链表。

说了这么多,我们来编码实现一下。

实现

创建一个双向指针的节点类

class DoubNode extends Node {
    // 继承自链表的节点Node
    constructor(data, next, prev){
        super(data, next)
        this.prev = prev
    }
}

创建一个双向链表类

class DoubList extends LinkedList {
    constructor(){
        super()
        // 保存链表最后一个元素的引用
        this.tail = null
    }
}

既然是继承自链表类,那么我们就只需要重写不同于链表的方法就行。

首先来看看任意位置添加元素

pushAnyWhere() and push()

pushAnyWhere(data, index){
    if(index >= 0 && index <= this.count) {
        const node = new DoubNode(data)
        let current = this.head
        
        // 第一种情况,头部添加
        if(index === 0) {
            if(this.head === null) {
                this.head = node
                this.tail = node
            }else {
                node.next = this.head
                current.prev = node
                this.head = node
            }
        }else if(index === this. count) {
            // 第二种情况,尾部添加
            current = this.tail
            current.next = node
            node.prev = current
            this.tail = node
        }else{
            // 第三种情况,中间
            
            // 获取要插入位置的前驱节点
            const prevNode = this.getData(index - 1)
            current = prevNode.next
            node.next = current
            prevNode.next = node
            current.prev = node
            node.prev = prevNode
        }
        this.count++
    }else{
        console.log(`index输入不合法,请输入0-${this.count}之间的值`)
    }
}

push(data) {
    const node = new DoubNode(data);
    if (this.head == null) {
        this.head = node;
        this.tail = node; 
    } else {
        this.tail.next = node;
        node.prev = this.tail;
        this.tail = node;
    }
    this.count++;
}

有添加就又删除,现在来实现任意位置删除

removeAnyWhere()

removeAnyWhere(index) {
    if(index >= 0 && index < this.count) {
        let current = this.head
        
        // 删除头节点
        if(index === 0) {
            this.hed = current.next
            
            // 特殊情况处理,如果只有一个头节点
            if(this.count === 1) {
                this.tail = undefined
            }else {
                this.head.prev = undefined
            }
        }else if(index === this.count - 1){
            // 尾节点
            current = this.tail
            this.tail = current.prev
            this.tail.next = undefined
        }else {
            current = this.getData(index)
            const prevNode = current.prev
            prevNode.next = current.next
            current.next.prev = prevNode
        }
        this.count--
    }else{
        console.log(`index输入不合法,请输入0-${this.count}之间的值`)
    }
}

总结

通过实现双向链表,我们发现,由于双向链表比普通链表多了prev指针,所以我们需要一只tail指针指向尾节点,帮助我们简化对其尾部的操作。同时,我们也需要对普通链表的插入与删除做一些调整,使prev指针指向其对应的前驱节点。getData(),indexOf()等其他方法都是公用的,所以我们创建的类要继承普通类。简化我们的代码量。下次我们用同样的方法来实现循环链表。