数据结构(5)链表之双向链表

149 阅读6分钟

双向链表

单向链表:

  • 只能从头遍历到尾或者从尾遍历到头(一般从头到尾)
  • 也就是链表相连的过程是单向的实现的原理是上一个链表中有一个指向下一个的引用

单向链表缺点

  • 可以轻松的到达下一个节点,但是回到前一个节点是很难的。 但是, 在实际开发中,经常会遇到需要回到上一个节点的情况也就有了双向链表。

双向链表

  • 既可以从头遍历到尾, 又可以从尾遍历到头
  • 也就是链表相连的过程是双向的,一个节点既有向前连接的引用,也有一个向后连接的引用

双向链表缺点

  • 每次在插入或删除某个节点时,需要处理四个节点的引用,而不是两个也就是实现起来要困难一些
  • 并且相当于单向链表,必然占用内存空间更大一些

双向循环链表元素以及双向循环链表的结构

image.png

//双向链表节点的结构
        class DoubleListNode {
            constructor(data) {
                this.data = data;
                this.previous = null;
                this.next = null;
            }
        }
        //双向链表的封装
        class DoubleList {
            constructor() {
                //空的双向链表的结构
                this.head = null;//头部节点
                this.tail = null;//尾部节点
                this.length = 0;//链表长度
            }
        }

双向链表操作方法代码思路

在链表末尾添加元素

在空链表末尾添加元素

image.png

在非空链表末尾添加元素

image.png

在链表指定位置插入元素

需要先判断插入的位置是否合法,不符合法则函数直接返回false,符合法则就创建新节点,再根据情况分类插入。

 //先判断位置是否合法
                if (position < 0 || position > this.length || !Number.isInteger(position)) {
                    //位置不合法返回false
                    return false;
                }
                //位置合法执行插入操作
                //创建新节点
                let newnode = new DoubleListNode(element);

在非空链表头部插入元素

在空链表头部插入元素和在空链表尾部插入元素情况相同,都是让头部和尾部都指向新节点,将代码复用即可

image.png

在链表中间任意位置插入元素

在链表尾部位置插入,直接调用append方法,传入需要插入的数据在append方法中创建新节点。

在链表中间任意位置插入元素需要先找到指定的任意位置的前一个节点

image.png

移除链表指定位置元素

需要先判断插入的位置是否合法,不符合法则函数直接返回false,符合法则再根据情况分类移除。

//需要先判断指定的位置是否合法,this.length-1表示链表最后一个节点的索引
                if (position < 0 || position > this.length - 1 || !Number.isInteger(position)) {
                    //位置不合法返回false
                    return false;
                }
当在空链表中移除元素会直接返回false

移除链表头部位置元素

链表中只有一个节点,就让链表的头部和尾部都指向空

image.png

链表不只有一个元素,让头部节点先指向头部节点的next,再让头部节点的previous指向空

image.png

移除链表尾部节点

image.png

移除链表中间任意位置元素

image.png

双向链表常见操作封装代码

//双向链表节点的结构
        class DoubleListNode {
            constructor(data) {
                this.data = data;
                this.previous = null;
                this.next = null;
            }
        }
        //双向链表的封装
        class DoubleList {
            constructor() {
                //双向链表的结构
                this.head = null;//头部节点
                this.tail = null;//尾部节点
                this.length = 0;//链表长度
            }
            //尾部添加节点
            append(element) {
                //创建新的节点
                let newnode = new DoubleListNode(element);
                if (this.length == 0) {
                    //空链表中尾部添加节点,让头部节点和尾部节点指向新节点
                    this.head = newnode;
                    this.tail = newnode;
                } else {
                    //非空链表中尾部添加新节点,让链表尾部节点的next指向新节点,新节点的previous指向尾部节点,最后断开原来的指向让尾部节点指向新节点
                    this.tail.next = newnode;
                    newnode.previous = this.tail;
                    this.tail = newnode;
                }
                //最后无论执行if语句还是else语句链表长度都应该加1
                this.length++;
            }
            //向指定位置插入元素,在任意位置插入数据
            insert(position, element) {
                //先判断位置是否合法
                if (position < 0 || position > this.length || !Number.isInteger(position)) {
                    //位置不合法返回false
                    return false;
                }
                //位置合法执行插入操作
                //创建新节点
                let newnode = new DoubleListNode(element);
                //在链表头部位置插入
                if (position == 0) {
                    if (this.length == 0) {
                        //空链表头部插入,让头部和尾部都指向新节点
                        this.head = newnode;
                        this.tail = newnode;
                    } else {
                        //非空链表头部插入,先让头部节点的previous指向新节点,新节点的next指向头部,再断开原来头部节点的指向让头部节点指向新节点
                        this.head.previous = newnode;//让头部节点的previous指向新节点
                        newnode.next = this.head;//新节点的next指向头部
                        this.head = newnode;//让头部节点指向新节点
                    }
                    //最后无论执行if语句还是else语句链表长度都应该加1
                    this.length++;
                } else if (position == this.length) {
                    //在链表尾部位置插入,直接调用append方法,传入需要插入的数据在append方法中创建新节点
                    this.append(element);
                } else {
                    //在链表中间任意位置插入
                    //需要先找到指定的任意位置的前一个节点,指定的任意位置的前一个位置position-1
                    let current = this.head;
                    let index = 0;
                    while (index < position - 1) {
                        current = current.next;
                        index++;
                    }
                    //循环结束就是index=position-1,index就是指定的任意位置,current就是指定的任意位置的前一个节点,先连接新节点到链表中,即将新节点previous指向指定的任意位置的前一个节点和新节点的next指向指定位置的下一个节点即,最后断开指定位置的前一个节点与之前的下一个节点的连接,将current的next指向新节点,再让新节点的下一个节点的previous指向新节点
                    newnode.previous = current//将新节点previous指向指定的任意位置的前一个节点
                    newnode.next = current.next;//新节点的next指向指定位置的下一个节点
                    current.next = newnode;//current的next指向新节点
                    newnode.next.previous = newnode;//新节点的下一个节点的previous指向新节点
                    //最后链表长度加1
                    this.length++;
                }
            }
            //通过下标值删除某个元素
            removeAt(position) {
                //需要先判断指定的位置是否合法,this.length-1表示链表最后一个节点的索引
                if (position < 0 || position > this.length - 1 || !Number.isInteger(position)) {
                    //位置不合法返回false
                    return false;
                }
                //指定位置合法
                // 定义变量, 保存信息
                let current = this.head;
                let index = 0;
                let pre = null;//pre用于保存移除的指定位置的节点
                if (position == 0) {
                    //移除头部的节点
                    if (this.length == 1) {
                        //链表中只有一个节点,就让链表的头部和尾部都指向空
                        pre = this.head;//移除的就是头部节点
                        this.head = null;
                        this.tail = null;
                    } else {
                        //链表不只有一个元素,让头部节点先指向头部节点的next,再让头部节点的previous指向空
                        pre = this.head;//移除的就是头部节点
                        this.head = this.head.next;//让头部节点先指向头部节点的next
                        this.head.previous = null;//让头部节点的previous指向空
                    }
                    //最后链表长度都需要减1
                    this.length--;
                } else if (position == this.length - 1) {
                    //移除尾部的节点,让尾部节点指向尾部节点的previous,再让尾部节点的next指向空
                    pre = this.tail;//移除的就是尾部节点
                    this.tail = this.tail.previous;//让尾部节点指向尾部节点的previous
                    this.tail.next = null;//让尾部节点的next指向空
                    //最后链表长度都需要减1
                    this.length--;
                } else {
                    //移除任意位置的节点,需要先找到指定的任意位置的前一个节点
                    while (index < position - 1) {
                        current = current.next;
                        index++;
                    }
                    //循环结束current就是指定的任意位置的前一个节点,就让current的next指向指定的任意位置的后一个节点就是current.next.next,再让指定的任意位置的后一个节点的previous指向指定的任意位置的前一个节点
                    pre = current.next;//pre用于保存移除的指定位置的节点就是current.next
                    current.next = current.next.next;//让指定的任意位置的前一个节点的next指向指定的任意位置的后一个节点
                    current.next.previous = current;//让指定的任意位置的后一个节点的previous指向指定的任意位置的前一个节点
                    //最后链表长度都需要减1
                    this.length--;
                }
                //无论执行哪一个条件语句,都需要返回删除的数据
                return pre.data;
            }
            //查找指定元素的位置
            indexOf(ele) {
                let current = this.head;
                let index = 0;
                //先遍历链表的所有元素,当索引大于等于链表的长度时while循环就结束表示遍历完了链表所有元素
                while (index < this.length) {
                    //当前遍历到的链表元素和指定元素相等就返回当前索引,不相等就继续遍历让current变量指向当前遍历道德元素的下一个元素,索引加1.
                    if (current.data == ele) {
                        return index;
                    } else {
                        current = current.next;
                        index++;
                    }
                }
                //当遍历完链表中所有元素都没有与指定元素相等的就返回-1
                return -1;
            }
            //移除指定元素
            remove(el){
                //找到指定元素的索引
                let index=this.indexOf(el);
                //根据索引移除指定元素
                return this.removeAt(index);
            }
            //正向遍历链表转换为字符串
            toAfterString(){
                let current=this.head;
                let index=0;
                let arr=[];//用于保存链表遍历到的每一个元素
                //遍历链表
                while(index<this.length){
                    arr.push(current.data);//current就是当前遍历到的元素
                    current=current.next;//保存当前遍历到的元素的下一个元素
                    index++;
                }
                return arr.join("-");//讲数组中的元素转换为字符串返回
            }
            //反向遍历链表转换为字符串
            toBeforeString(){
                let current=this.tail;
                let index=this.length-1;
                let arr=[];//用于保存链表遍历到的每一个元素
                //遍历链表
                while(index>=0){
                    arr.push(current.data);//current就是当前遍历到的元素
                    current=current.previous;//保存当前遍历到的元素的下一个元素
                    index--;
                }
                return arr.join("-");//讲数组中的元素转换为字符串返回
            }
            //如果链表中不包含任何元素,返回`true`,如果链表长度大于0则返回`false`
            isEmpty(){
                //length为0做布尔判定是false执行else语句
                if(this.length){
                    return true;
                }else{
                    return false;
                }
            }
            //返回链表包含的元素个数。与数组的`length`属性类似
            size(){
                return this.length;
            }
        }