連結串列-迴圈連結串列

致于数据科学家的小陈發表於2024-06-08

迴圈連結串列可以像連結串列一樣只有單向引用,也可以像雙向連結串列一樣有雙向引用。迴圈連結串列和連結串列之間唯一的區別在於

最後一個元素指向下一個元素的指標(tail.next)不是引用undefined,而是指向第一個元素(head).

  • 單連結串列: this.tail.next = this.head;
  • 雙向連結串列: this.tail.next = this.head; this.head.prev = this.tail;

這裡簡化演示就單向連結串列的方式來操作即可;

連結串列初始化

// 節點類
class Node {
  constructor(element) {
    this.element = element; 
    this.next = undefined; 
  }
}


// 連結串列類
class CicularLinkedList {
  constructor() {
    this.count = 0; 
    this.head = null; 
  }

  size() {
    return this.count
  }

  toString() {
    if (this.head == null) return undefined
    let str = `${this.head.element}`
    let current = this.head.next 
    for (let i = 1; i < this.count; i++) {
      str = `${str}, ${current.element}`
      current = current.next 
    }
  }

  getElementAt(index) {
    if (index < 0 || index > this.count) return undefined
    let node = this.head; 
    for (let i = 0; i < index; i++) {
      node = node.next;
    }
    return node;
  }

  indexOf(element) {
    let current = this.head;
    for (let i = 0; i < this.count; i++) {
      if (current.element == element) return i;
      current = current.next; 
    }
    return -1;
  }
  
}


在任意位置插入元素

  • 頭結點插入
    • 空連結串列, 自迴圈
    • 非空連結串列, 記得透過 getElement() 方法將 tail 元素指向 head
  • 中間或尾部插入, 調整 previous, current 即可
// 在任意位置插入元素
  insert(index, element) {
    if (index < 0 || index > this.count) return false
    const node = new Node(element)
    let current = this.head 

    if (index == 0) {
      // 空連結串列插入元素, 則自迴圈唄
      if (this.head == null) {
        this.head = node
        node.next = this.head 
      } else {
        // 記得要將最後一個元素的 next 指向頭結點元素
        node.next = current
        this.head = node 

        const tail = this.getElementAt(this.size())
        tail.next = this.head 
      }
    } else {
      // 從中間或者尾部插入, 調整 previous, current  指標即可
      const previous = this.getElementAt(index - 1)
      current = previous.next 

      previous.next = node 
      node.next = current
    }
    // 記得更新連結串列長度
    this.count++
    return true
  }

和普通連結串列一樣的, 唯一要考慮的就是要將尾節點的 next 連線到頭節點

在任意位置刪除元素

  • 刪除頭節點
    • 空連結串列, 返回 undefined
    • 連結串列僅一個元素, 讓 head ->null 即可
    • 連結串列有多個元素, 讓 head -> 原第二; tail -> 原第二 (現第一)
  • 中間或尾部刪, 調整 previous , current 即可
  // 從任意位置刪除元素
  removeAt(index) {
    // 越界和空連結串列檢查
    if (index < 0 || index > this.count) return undefined
    if (this.head == null) return undefined

    let current = this.head 
    if (index == 0) {
      if (this.count == 1) {
        // 刪除頭節點, 且當只有一個元素時, 讓 head -> null 即可
        this.head = undefined
      } else {
        // 刪除頭結點, 後面原來還有多個元素, head -> 原第二, tail -> 原第二(現第一 )
        current = this.head 
        this.head = this.head.next 

        const tail = this.getElementAt(this.count)
        tail.next = this.head.next 
      }
    } else {
      // 刪除中間或者尾部元素, 不用常更新 tail
      const previous = this.getElementAt(index - 1)
      current = previous.next 

      previous.next = current.next 
    }
    // 記得更新連結串列長度
    this.count--
    return current.element
  }

  // 刪除元素
  remove(element) {
    const index = this.indexOf(element)
    return this.removeAt(index)
  }

}

迴圈連結串列-完整實現

// 節點類
class Node {
  constructor(element) {
    this.element = element
    this.next = undefined
  }
}


// 連結串列類
class CicularLinkedList {
  constructor() {
    this.count = 0
    this.head = null
  }

  size() {
    return this.count
  }

  toString() {
    if (this.head == null) return undefined
    let str = `${this.head.element}`
    let current = this.head.next 
    for (let i = 1; i < this.count; i++) {
      str = `${str}, ${current.element}`
      current = current.next 
    }
    return str 
  }

  getElementAt(index) {
    if (index < 0 || index > this.count) return undefined
    let node = this.head; 
    for (let i = 0; i < index; i++) {
      node = node.next
    }
    return node
  }

  indexOf(element) {
    let current = this.head;
    for (let i = 0; i < this.count; i++) {
      if (current.element == element) return i
      current = current.next
    }
    return -1
  }

  // 在任意位置插入元素
  insert(index, element) {
    if (index < 0 || index > this.count) return false
    const node = new Node(element)
    let current = this.head 

    if (index == 0) {
      // 空連結串列插入元素, 則自迴圈唄
      if (this.head == null) {
        this.head = node
        node.next = this.head 
      } else {
        // 記得要將最後一個元素的 next 指向頭結點元素
        node.next = current
        this.head = node 

        const tail = this.getElementAt(this.size())
        tail.next = this.head 
      }
    } else {
      // 從中間或者尾部插入, 調整 previous, current  指標即可
      const previous = this.getElementAt(index - 1)
      current = previous.next 

      previous.next = node 
      node.next = current
    }
    // 記得更新連結串列長度
    this.count++
    return true
  }

  // 從任意位置刪除元素
  removeAt(index) {
    // 越界和空連結串列檢查
    if (index < 0 || index > this.count) return undefined
    if (this.head == null) return undefined

    let current = this.head 
    if (index == 0) {
      if (this.count == 1) {
        // 刪除頭節點, 且當只有一個元素時, 讓 head -> null 即可
        this.head = undefined
      } else {
        // 刪除頭結點, 後面原來還有多個元素, head -> 原第二, tail -> 原第二(現第一 )
        current = this.head 
        this.head = this.head.next 

        const tail = this.getElementAt(this.count)
        tail.next = this.head.next 
      }
    } else {
      // 刪除中間或者尾部元素, 不用常更新 tail
      const previous = this.getElementAt(index - 1)
      current = previous.next 

      previous.next = current.next 
    }
    // 記得更新連結串列長度
    this.count--
    return current.element
  }

  // 刪除元素
  remove(element) {
    const index = this.indexOf(element)
    return this.removeAt(index)
  }

}


// test 
const list = new CicularLinkedList()

list.insert(0, '111')
console.log('連結串列是: ', list.toString());

list.insert(1, '222')
console.log('連結串列是: ', list.toString());

list.insert(2, '333')
console.log('連結串列是: ', list.toString());

list.insert(list.size(), '444')
console.log('連結串列是: ', list.toString());

list.insert(0, '555')
console.log('連結串列是: ', list.toString());
console.log('連結串列長度是: ', list.size());

console.log('尾元素的 next 是: ', list.getElementAt(list.size() - 1).next.element);

console.log('刪除頭部元素: ', list.removeAt(0));
console.log('連結串列是: ', list.toString());
console.log('連結串列長度是: ', list.size());

console.log('刪除位置 1 的元素: ', list.removeAt(1));
console.log('連結串列是: ', list.toString());
console.log('連結串列長度是: ', list.size());

console.log('刪除尾部部元素: ', list.removeAt(list.size() - 1 ));
console.log('連結串列是: ', list.toString());
console.log('連結串列長度是: ', list.size());

測試結果如下:

PS E:\Front_Mook\projects\shop-admin> node test.js
連結串列是:  111
連結串列是:  111, 222
連結串列是:  111, 222, 333
連結串列是:  111, 222, 333, 444
連結串列是:  555, 111, 222, 333, 444
連結串列長度是:  5
尾元素的 next 是:  555
刪除頭部元素:  555
連結串列是:  111, 222, 333, 444
連結串列長度是:  4
刪除位置 1 的元素:  222
連結串列是:  111, 333, 444
連結串列長度是:  3
刪除尾部部元素:  444
連結串列是:  111, 333
連結串列長度是:  2

至次, 迴圈連結串列也基本理解差不多了, 還是比較容易實現的, 透過形象化的想象出來.

相關文章