从零学习数据结构(5)- 双向链表

1,025 阅读3分钟

「这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战

对比单向链表和双向链表

单向链表

只能从头遍历到尾 或者从尾遍历到头(一般都是从头到尾)

  • 也就是说这个链表相连的过程是单向的

  • 实现的原理是上一个链表中有一个指向下一个的引用

单向链表的缺点

- 可以轻松到达下一个节点,但很难回到上一个节点,只能从头开始,再一次遍历一遍

双向链表

  • 既可以从头遍历到尾,又可以从尾遍历到头

  • 实现原理:一个节点既有向后的引用,又有向前的引用

  • 双向链表可以有效解决单向链表中的问题

双向链表的缺点

- 每次在插入或者删除某个节点时,需要处理四个引用,实现起来更困难

- 相对于单向链表,占的内存空间更大

image.png

双向链表的特点

  • 可以使用一个 head 和一个 tail 分别指向头部和尾部的节点

  • 每个节点都由三部分组成:前一个节点的指针(prev),保存的数据(data),下一个节点的指针(next)

  • 双向链表的第一个节点的 prev 是 null

  • 双向链表的最后的节点的 next 是 null

双向链表的常见操作

  • append(element):向列表尾部添加一个新的项

  • insert(position, element):向列表特定位置插入一个新的项

  • get(position):获取对应位置的元素

  • indexOf(element):返回元素在列表中的索引,如果没有,就返回 -1

  • update(position, element):修改某个位置的元素

  • removeAt(position):从列表的特定位置删除一项

  • remove(element):从列表中移除一项

  • isEmpty():判断链表是否为空

  • size():返回链表中的元素个数

  • forwardString():返回从后向前遍历的节点字符串形式

  • backwardString():返回从前向后遍历的节点字符串形式

  • toString()

  • getHead():返回链表的第一项

  • getTail:返回链表的最后一项

双向链表的代码实现

代码

class DoublyLinkedListItem {
  data = undefined;
  prev = null
  next = null;

  constructor(data) {
    this.data = data;
  }
}

class DoublyLinkedList {
  head = null
  tail = null
  length = 0

  append(data) {
    const newItem = new DoublyLinkedListItem(data);
    if(this.length === 0) {
      this.head = newItem
      this.tail = newItem
    } else {
      newItem.prev = this.tail
      this.tail.next = newItem
      this.tail = newItem
    }
    this.length += 1
  }

  insert(position, data) {
    if(position < 0 || position > this.length) {return }

    const newItem = new DoublyLinkedListItem(data);
    if (this.length === 0) {
      this.head = newItem
      this.tail = newItem
    } else {
      if (position === 0) {
        newItem.next = this.head
        this.head.prev = newItem
        this.head = newItem
      } else if (position === this.length) {
        newItem.prev = this.tail
        this.tail.next = newItem
        this.tail = newItem
      } else {
        let currentItem = this.head
        for(let i = 0;i < position; i ++) {
          currentItem = currentItem.next
        }
        newItem.prev = currentItem.prev
        newItem.next = currentItem
        currentItem.prev.next = newItem
        currentItem.prev = newItem
      }
    }
    this.length += 1
  }

  get(position) {
    if(position < 0 || position >= this.length) {return null}
    let currentItem = this.head
    for(let i = 0; i < position; i++) {
      currentItem = currentItem.next
    }
    return currentItem.data
  }

  indexOf(data) {
    let currentItem = this.head
    for (let i = 0;i < this.length; i++) {
      if(currentItem.data === data) {
        return i
      } else {
        currentItem = currentItem.next
        if (currentItem === null) {
          return -1
        }
      }
    }
  }

  update(position, newData) {
    if(position < 0 || position >= this.length) {return null}

    let currentItem = this.head
    for(let i = 0; i < position; i++) {
      currentItem = currentItem.next
    }
    currentItem.data = newData
  }

  removeAt(position) {
    if(position < 0 || position >= this.length) {return }

    let currentItem = this.head
    if (this.length === 1) {
      this.head = null
      this.tail = null
    } else {
      if (position === 0) {
        this.head.next.prev = null
        this.head = this.head.next
      } else if (position === this.length - 1) {
        currentItem = this.tail
        this.tail.prev.next = null
        this.tail = this.tail.prev
      } else {
        for (let i = 0; i < position; i ++) {
          currentItem = currentItem.next
        }
        currentItem.prev.next = currentItem.next
        currentItem.next.prev = currentItem.prev
      }
    }
    this.length -= 1
    return currentItem.data
  }

  remove(data) {
    const index = this.indexOf(data)
    return this.removeAt(index)
  }

  toString() {
    return this.backwardString()
  }

  forwardString() {
    let currentItem = this.tail
    let resultString = ''
    for(let i = 0; i < this.length; i ++ ){
      resultString += currentItem.data + " "
      currentItem = currentItem.prev
    }
    return resultString
  }

  backwardString() {
    let currentItem = this.head
    let resultString = ''
    for(let i = 0; i < this.length; i ++ ){
      resultString += currentItem.data + " "
      currentItem = currentItem.next
    }

    return resultString
  }

  isEmpty() {
    return this.length === 0
  }

  size() {
    return this.length
  }

  getHead() {
    return this.head.data
  }

  getTail() {
    return this.tail.data
  }
}

测试代码

const doublyLinkedList = new DoublyLinkedList()
doublyLinkedList.append('abc');
doublyLinkedList.append('cba');
doublyLinkedList.append('nba');
doublyLinkedList.append('ban');
console.log(doublyLinkedList.backwardString());     //abc cba nba ban
console.log(doublyLinkedList.forwardString());      //ban nba cba abc
console.log(doublyLinkedList.toString());           //abc cba nba ban
console.log(doublyLinkedList.size())                //4
console.log(doublyLinkedList.isEmpty())             //false
doublyLinkedList.update(3, 'bbb')                 
console.log(doublyLinkedList.removeAt(2));          //'nba'
console.log(doublyLinkedList.remove('nba'))         //undefined 
console.log(doublyLinkedList.toString());           //abc cba bbb
doublyLinkedList.insert(0, 'aaa')
doublyLinkedList.insert(5, 'ccc')
doublyLinkedList.insert(2, 'bbb')
console.log(doublyLinkedList.toString());           //aaa abc bbb cba bbb
console.log(doublyLinkedList.get(3));               // cba
console.log(doublyLinkedList.indexOf('abc'))        // 1
console.log(doublyLinkedList.getHead())             //aaa
console.log(doublyLinkedList.getTail())             //bbb