双向链表写不出来?用前任举个例子就明白了

40 阅读5分钟

之前我们知道了 单向链表 不知道的请移步这儿

单向链表具备这些特点

  • 只能从头遍历到尾或者从尾遍历到头(一般从头到尾),也就是链表相连的过程是单向的
  • 实现的原理是上一个链表中有一个指向下一个的引用
  • 我们可以轻松的到达下一个节点,但是回到前一个节点是很难的.但是,在实际开发中,经常会遇到需要回到上一个节点的情况

为了解决这个缺点,就有了 双向链表

双向链表

现在我们来说说 双向链表

  • 既可以从头遍历到尾,又可以从尾遍历到头 就像地铁,可以顺着开,也能倒着开,简直是拿捏!!
  • 一个节点既有向前连接的引用,也有一个向后连接的引用
  • 每次在插入或删除某个节点时,需要处理四个引用,而不是两个,实现起来要困难一些
  • 并且相当于单向链表,必然占用内存空间更大一些.但是这些缺点和我们使用起来的方便程度相比,是微不足道的

image.png

  • 可以使用一个head和一个tail分别指向头部和尾部的节点
  • 每个节点都由三部分组成: 前一个节点的指针(prev) 保存的元素item) 后一个节点的指针(next)
  • 双向链表的第一个节点的prev是null
  • 双向链表的最后的节点的next是null

话不多说 直接开干

// 双向链表的基本结构 类
class BothWayLinkList {
    constructor() {
        this.head = null; // 头
        this.tail = null; // 尾
        this.length = 0;
    };

    // add
    add(data) {
        const newNode = new Node(data);
        // 判断是不是第一个
        if (this.length === 0) {
            // 第一个的时候 这个既是第一个 也是最后一个
            this.head = newNode;
            this.tail = newNode;
        } else {
            /*
              注意添加的逻辑 :
              增加到原来最后一个的后面 即:新元素的上一个(prev)指针指向最后一个
              原本的最后一个(tail)的下一个(next)指针是指向新元素的;  这两步是把新旧元素建立双向连接
              最后一个这个帽子要给新元素(老幺的称呼顺延了);
              */
            newNode.prev = this.tail;
            this.tail.next = newNode;
            this.tail = newNode;
        }
        this.length++;
    };

    // backward
    backWardToString() {
        let result = "",
            current = this.head;
        while (current) {
            result += current.data;
            current = current.next;
        }
        return result;
    };

    // forward
    forWardToString() {
        let result = "",
            current = this.tail;
        while (current) {
            result += current.data;
            current = current.prev;
        }
        return result;
    };

    // toString
    toString = () => {
        return this.backWardToString();
    };

    // insert
    insert(position, data) {
        if (position < 0 || position > this.length) return;
        const newNode = new Node(data);
        if (this.length === 0) {
            this.add();
            return;
        } else {
            if (position === 0) {
                /* 添加到第一个:
                  原来第一个(head)的 prev指针 指向新元素;
                  新元素的 next指针 指向原来的第一个;
                  新元素成了老大(head),享受老大的称呼(head);
                  */
                this.head.prev = newNode;
                newNode.next = this.head;
                this.head = newNode;
            } else if (position === this.length) {
                /*
                  相当于末尾新增:
                  原来的最后一个(tail)的 next指针 指向 新元素;
                  新元素的 prev指针  指向tail;
                  新元素成了老幺(tail),享受老大的称呼(tail);
                  */
                this.tail.next = newNode;
                newNode.prev = this.tail;
                this.tail = newNode;
            } else {
                let current = this.head,
                    index = 0;
                while (index++ < position) {
                    current = current.next;
                }
                /*
                又是熟悉的故事  前任和现任的故事  现任要砍断前任和ta的联系 自己取而代之
                */

                newNode.prev = current.prev; // 获取到ta的联系方式 斩断了前任对ta的联系
                newNode.next = current; //自己的手(next)牢牢牵住ta (current)
                /* 目前为止 你只是斩断了 前任对ta的联系 有了ta的联系方式  但是他还没承认你   也还没斩断 ta对前任的联系 */
                current.prev.next = newNode; // 斩断ta对前任的联系 然后建立新的联系  进入生活
                current.prev = newNode; // ta主动承认你
            }
            this.length += 1;
        }
    };


    // getData
    getData(data) {
        let current = this.head;
        while (current) {
            if (current.data === data) {
                return current.data;
            }
            current = current.next;
        }
        return "恭喜你,你连一根毛也没找到哦";
    };

    // getNum  优化效率
    getNum(position) {
        if (position < 0 || position >= this.length) return;

        // 正常的遍历
        // let current = this.head,
        //   index = 0;

        // while (index++ < position) {
        //   current = current.next;
        // }
        // return current.data;
        /*
        这儿每次查找  要么是从头到尾 要么就是从尾到头  做了很多无用功
        可以判断一下position 在这个号链表中的位置 然后去这个区域  定点捕捞  岂不是更有效果?
        this.length / 2  ====>相当于把链表分成了前后两部分
        this.length / 2 > position  说明position 很小  在前面那部分  那就从前往后遍历
        this.length / 2 < position  说明position 比较大  在后面那部分  那就从后往前遍历
      */
        let median = this.length / 2;
        const flag = median > position ? true : false; // true:从前往后遍历 false:从后往前遍历
        let current = flag ? this.head : this.tail;
        let index = flag ? 0 : this.length - 1;  // 因为 index是从0 开始算  传过来的数字也是从0 开始算  length 没有算0 就少一个 就减去1
        if (flag) {
            console.log("从前往后遍历");
            while (index++ < position) {
                current = current.next;
            }
        } else {
            console.log("从后往前遍历");
            while (index-- > position) {
                current = current.prev;
            }
        }
        return current.data;
    };


    // indexOf
    indexOf(data) {
        let current = this.head,
            index = 0;
        while (current) {
            if (current.data === data) {
                return index;
            }
            current = current.next;
            index += 1;
        }
        return -1;
    };


    // update
    update(position, data) {
        // if (position < 0 || position >= this.length) return false;
        // let current = this.head,
        //     index = 0;
        // while (index++ < position) {
        //     current = current.next;
        // }
        // current.data = data;
        // return true;

        // 也可以判断从前往后还是从后往前 做优化
        let m = this.length / 2;
        let flag = m > position ? true : false // true  在左边 false 在右边
        let current = flag ? this.head : this.tail;
        let index = flag ? 0 : this.length - 1;
        if (flag) {
            while (index++ < position) {
                current = current.next;
            }
            current.data = data
        } else {
            while (index-- > position) {
                current = current.prev;
            }
            current.data = data
        }
        return true
    };


    // removeAt
    /*
    四种情况:
    1.只有一个
    2.删除的是第一个
    3.删除的是最后一个
    4.删除的是其他的

   删除其实就是指   没有引用指向某一个数据,这个数据会被自动回收 (也就是把指针设置为null) 强调的是  斩断指向关系

    为什么不直接把 某个数据设置为null 不能够啊 直接设置为null  他的引用关系怎么处理呢(某一个成了null  上下关系就走不通了 短路了 所以就只 
    有饶过他 直接从他身上过去)
    所以直接把他孤立出去  让他掉队  就好了 不像新增那么麻烦,切断就联系还需要建立新的联系

    例如;
    this.head.next.prev=null   ===> this.head.next 是head的下一个 this.head.next.prev 是head自己  这儿强调的是  斩断指向关系
    那为什么不直接写this.head==null ? 因为我们的next 和 next都是指针 这个代表指向关系 不代表数据本身
    this.head.next.prev只是指向head,但是并不是head  this.head.next.prev=null 只是斩断连接关系 不是把head设置成了null
    */
    removeAt(position) {
        if (position < 0 || position >= this.length) return null;
        // 只有一个
        let current = this.head;
        if (this.length === 1) {
            this.head = null;
            this.tail = null;
        } else {
            // 删除的是第一个 只需要把第一个抛弃 然后第二个顶上来成为第一个
            if (position === 0) {
                this.head.next.prev = null; // head的下一个(老二) 不再往上指
                this.head = this.head.next; // 老二顶上来
            } else if (position === this.length - 1) {
                // 最后一个  只需要把最后一个抛弃 然后倒数第二个顶上来成为最后一个
                current = this.tail;
                this.tail.prev.next = null; // 拒绝给老幺 供给 切断和他的联系
                this.tail = this.tail.prev; // 在团队成立新的老幺 你的一切都有人代替
            } else {
                // 其他的  那就饶过他 直接通信
                let index = 0;
                while (index++ < position) {
                    current = current.next;
                }
                current.prev.next = current.next; // current.prev :被删除的上一个 current.next:被删除的下一个 这两个直接建立通信 兄弟  你被抛弃了
                current.next.prev = current.prev;

                // 这儿不像增加 因为增加时要切断 之前旧的联系的同时 还需要建立进的联系  这儿只需要切断旧的联系即可!
            }
        }
        this.length -= 1;
        return current.data;
    };


    // remove
    remove(data) {
        const num = this.indexOf(data);
        if (num >= 0) {
            return this.removeAt(num);
        }
    };

}


// 新数据结构 节点类  构造链表数据的构造函数
class Node {
    constructor(data) {
        this.data = data;
        this.prev = null; // 向上的指针
        this.next = null; // 向下的指针
    }
}




const myLinkedList = new BothWayLinkList();
// myLinkedList.add("地");
// myLinkedList.add("势");
// myLinkedList.add("坤");
// myLinkedList.add("君");
// myLinkedList.add("子");
// myLinkedList.add("以");
// myLinkedList.add("厚");
// myLinkedList.add("德");
// myLinkedList.add("载");
// myLinkedList.add("物");
// myLinkedList.insert(0, "N");
// myLinkedList.insert(2, "I");
// myLinkedList.insert(12, "lao");
// console.log("getData", myLinkedList.getData("sad"));
// console.log("getData", myLinkedList.getData("N"));
// console.log("getData", myLinkedList.getData("lao"));
// console.log("getNum", myLinkedList.getNum(0));
// console.log("getNum", myLinkedList.getNum(10));
// console.log("getNum", myLinkedList.getNum(8));
// console.log("indexOf", myLinkedList.indexOf("N"));
// console.log("indexOf", myLinkedList.indexOf("德"));
// console.log("indexOf", myLinkedList.indexOf("以"));
// console.log("update", myLinkedList.update(0, "0"));
// console.log("update", myLinkedList.update(8, "0"));
// console.log("myLinkedList", myLinkedList);
// console.log("getNum", myLinkedList.getNum(0));
// console.log("getNum", myLinkedList.getNum(10));
// console.log("getNum", myLinkedList.getNum(8));
// console.log("backWardToString", myLinkedList.backWardToString());
// console.log("forWardToString", myLinkedList.forWardToString());
// console.log("removeAt", myLinkedList.removeAt(0));
// console.log("removeAt", myLinkedList.remove("I"));
// console.log("removeAt", myLinkedList.removeAt(10));
// console.log("toString", myLinkedList.toString());