数据结构学习笔记-双向链表

302 阅读2分钟

双向链表

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

双向链表的特点

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

1.png

双向链表的封装

 function doublyLinkList() {
    function node(data) {
      this.data = data;
      this.prev = null;
      this.next = null;
    }
    this.head = null;
    this.tail = null;
    this.length = 0; 
}

向末尾添加节点

2.png

3.png

doublyLinkList.prototype.append = function(data) {
  let newNode = new node(data);
  // 如果链表是空
  if (this.length === 0) {
    this.head = newNode;
    this.tail = newNode;
  } else {
    let tail = this.tail; // 旧的最后节点
    newNode.prev = tail; // 新节点的前一个节点指向旧的最后节点
    tail.next = newNode; // 旧的最后节点指向新的节点
    this.tail = newNode; // 新加的最后节点
  }
  this.length += 1;
};

insert添加节点

向末尾添加节点时,链表为空添加节点和末尾添加节点已图解,不再画图

4.png

6.png

doublyLinkList.prototype.insert = function(index, data) {
  if (index < 0 || index > this.length) return false;
  let newNode = new node(data);
  if (this.length === 0) {
    this.head = newNode;
    this.tail = newNode;
  } else {
    // 添加为第一个节点
    if (index === 0) {
      this.head.prev = newNode;
      newNode.next = this.head;
      this.head = newNode;
    } else if (index === this.length) {
      // 添加为末尾节点
      newNode.prev = this.tail;
      this.tail.next = newNode;
      this.tail = newNode;
    } else {
      // 中间插入节点
      let current = this.head;
      let j = 0;
      while (j++ < index) {
        current = current.next; // 找点index下标的current节点
      }
      newNode.next = current; // 新增的节点指向current节点
      newNode.prev = current.prev; // 新增的节点的前一个节点指向current前一个节点
      current.prev.next = newNode; // current前一个节点next指向新增的节点
      current.prev = newNode; // current前一个节点指向新增的节点
    }
  }
  this.length += 1;
  return true;
};

根据下标找到该节点

doublyLinkList.prototype.findNode = function(index) {
  if (index < 0 || index >= this.length) return null;
  /**
    可以根据index的大小优化查找的顺序
    1  this.length / 2  > index   从头向尾遍历
    2  this.length / 2  < index   从尾向头遍历
 */
  //   从头向尾遍历
  let j = 0;
  let current = this.head;
  while (j++ < index) {
    current = current.next;
  }
  return current.data;
  //   从尾向头遍历
  //   let k = this.length - 1;
  //   let current = this.tail;
  //   while (k-- < index) {
  //     current = current.prev;
  //   }
  //   return current.data;
};

根基节点找到下标

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

修改某个节点

7.png

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

根据下标删除某个节点

8.png

9.png

10.png

11.png

doublyLinkList.prototype.removeAt = function(index) {
    if (index < 0 || index >= this.length) return false;
    //   如果链表只有一个节点
    if (this.length === 1) {
    this.head = null;
    this.tail = null;
    } else {
    // 删除第一个节点
    if (index === 0) {
      this.head.next.prev = null; // this.head.next 第二个节点 this.head.next.prev 第一个节点
      this.head = this.head.next; // head节点指向第二个节点
    } else if (index === this.length - 1) {
      // 删除末尾节点
      this.tail.prev.next = null; // this.tail.prev 倒数第二个节点 this.tail.prev.next 尾部节点
      this.tail = this.tail.prev; // tail节点指向倒数第二个节点
    } else {
      //  删除中间节点
      let j = 0;
      let current = this.head;
      while (j++ < index) {
        current = current.next;
      }
      current.prev.next = current.next; // current的前一个节点指向current的后一个节点
      current.next.prev = current.prev; //  current的后面的节点指向current的前一个节点
    }
    }
    this.length -= 1;
    return true;
};

根据节点删除

doublyLinkList.prototype.remove = function(data) {
  let index = this.indexOf(data);
  return this.removeAt(index);
};

从头到尾字符串排列

doublyLinkList.prototype.headToTailString = function() {
  let current = this.head;
  let res = "";
  while (current) {
    res += current.data + " ";
    current = current.next;
  }
  return res;
};

从尾到头字符串排列

doublyLinkList.prototype.tailToHeadString = function() {
  let current = this.tail;
  let res = "";
  while (current) {
    res += current.data + " ";
    current = current.prev;
  }
  return res;
};
let list = new doublyLinkList();
list.append(10);
list.append(200);
list.append(3000);
list.append(40000);

// let res = list.headToTailString();
// console.log(res) // 10 200 3000 40000

// let res1 = list.tailToHeadString();
// console.log(res1) //  40000 3000 200  10

//   list.insert(2, "abc");
//   let node = list.findNode(2);
//   console.log(node, "node"); // abc
//   let index = list.indexOf(400020);
//   console.log("index", index); // -1

//   list.removeAt(1);
//   let res3 = list.headToTailString();
//   console.log(res3); // 10 3000 40000

list.remove(40000);
let res4 = list.headToTailString();
console.log(res4); // 10 200 3000
console.dir(list, {
depth: 10
});