https://labuladong.online/algo/data-structure-basic/linkedlist-implement/
今天是學習的第 5 天,主要學習了鏈表程式碼實現:
實作鏈表有幾個關鍵點需要注意:
雙鏈表一般會同時持有頭尾節點的引用,因為在容器尾部添加元素是很頻繁的操作,持有尾部節點的引用,就可以在 O(1) 的時間複雜度內完成尾部添加元素的操作。
對於單鏈表來說,持有尾部節點的引用也有優化效果。比如要在單鏈表尾部添加元素,如果沒有尾部節點的引用,就需要遍歷整個鏈表找到尾部節點,時間複雜度是 O(n);如果有尾部節點的引用,就可以在 O(1) 的時間複雜度內完成尾部添加元素的操作。
head -> 1 -> 2 -> 3 -> 4 -> null
↑ ↑ ↑
指標 頭節點 尾節點
它的原理是在創建雙鏈表的時候,就創建一個虛擬頭節點和一個虛擬尾節點,無論雙鏈表是否為空,這兩個節點都存在。
舉個例子,假設虛擬頭尾節點分別是 dummyHead
和 dummyTail
,那麼一條空的雙鏈表長這樣:
dummyHead <-> dummyTail
如果添加 1,2,3
幾個元素,那麼雙鏈表長這樣:
dummyHead <-> 1 <-> 2 <-> 3 <-> dummyTail
現在有了虛擬頭尾節點,都只需要考慮在中間插入元素的情況就可以了。
在刪除節點的時候,最好把被刪除節點的指針都設為 null,養成好習慣,這可以避免一些潛在的問題。
// 假設單鏈表頭節點 head = 1 -> 2 -> 3 -> 4 -> 5
// 記錄要刪除的節點
var toDelete = head;
// 刪除單鏈表頭節點
head = head.next;
// 此時 head = 2 -> 3 -> 4 -> 5
// 清理被刪除節點的指針
toDelete.prev = null;
toDelete.next = null;
class Node {
constructor(val) {
this.val = val;
this.next = null;
}
}
class MyLinkedList {
constructor() {
this.head = new Node(null);
this.tail = this.head;
this.size = 0;
}
addFirst(e) {
const newNode = new Node(e);
newNode.next = this.head.next;
this.head.next = newNode;
if (this.size === 0) {
this.tail = newNode;
}
this.size++;
}
addLast(e) {
const newNode = new Node(e);
this.tail.next = newNode;
this.tail = newNode;
this.size++;
}
add(index, element) {
this.checkPositionIndex(index);
if (index === this.size) {
this.addLast(element);
return;
}
let prev = this.head;
for (let i = 0; i < index; i++) {
prev = prev.next;
}
const newNode = new Node(element);
newNode.next = prev.next;
prev.next = newNode;
this.size++;
}
removeFirst() {
if (this.isEmpty()) {
throw new Error("NoSuchElementException");
}
const first = this.head.next;
this.head.next = first.next;
if (this.size === 1) {
this.tail = this.head;
}
this.size--;
return first.val;
}
removeLast() {
if (this.isEmpty()) {
throw new Error("NoSuchElementException");
}
let prev = this.head;
while (prev.next !== this.tail) {
prev = prev.next;
}
const val = this.tail.val;
prev.next = null;
this.tail = prev;
this.size--;
return val;
}
remove(index) {
this.checkElementIndex(index);
let prev = this.head;
for (let i = 0; i < index; i++) {
prev = prev.next;
}
const nodeToRemove = prev.next;
prev.next = nodeToRemove.next;
// 刪除的是最後一個元素
if (index === this.size - 1) {
this.tail = prev;
}
this.size--;
return nodeToRemove.val;
}
// ***** 查 *****
getFirst() {
if (this.isEmpty()) {
throw new Error("NoSuchElementException");
}
return this.head.next.val;
}
getLast() {
if (this.isEmpty()) {
throw new Error("NoSuchElementException");
}
return this.tail.val;
}
get(index) {
this.checkElementIndex(index);
const p = this.getNode(index);
return p.val;
}
// ***** 改 *****
set(index, element) {
this.checkElementIndex(index);
const p = this.getNode(index);
const oldVal = p.val;
p.val = element;
return oldVal;
}
// ***** 其他工具函式 *****
size() {
return this.size;
}
isEmpty() {
return this.size === 0;
}
isElementIndex(index) {
return index >= 0 && index < this.size;
}
isPositionIndex(index) {
return index >= 0 && index <= this.size;
}
// 檢查 index 索引位置是否可以存在元素
checkElementIndex(index) {
if (!this.isElementIndex(index)) {
throw new Error(`Index: ${index}, Size: ${this.size}`);
}
}
// 檢查 index 索引位置是否可以添加元素
checkPositionIndex(index) {
if (!this.isPositionIndex(index)) {
throw new Error(`Index: ${index}, Size: ${this.size}`);
}
}
// 返回 index 對應的 Node
// 注意:請保證傳入的 index 是合法的
getNode(index) {
let p = this.head.next;
for (let i = 0; i < index; i++) {
p = p.next;
}
return p;
}
}
// 範例
const list = new MyLinkedList2();
list.addFirst(1);
list.addFirst(2);
list.addLast(3);
list.addLast(4);
list.add(2, 5);
console.log(list.removeFirst()); // 2
console.log(list.removeLast()); // 4
console.log(list.remove(1)); // 5
console.log(list.getFirst()); // 1
console.log(list.getLast()); // 3
console.log(list.get(1)); // 3
class Node {
constructor(val) {
this.val = val;
this.next = null;
this.prev = null;
}
}
class MyLinkedList {
// 虛擬頭尾節點
constructor() {
this.head = new Node(null);
this.tail = new Node(null);
this.head.next = this.tail;
this.tail.prev = this.head;
this.size = 0;
}
// ***** 增 *****
addLast(e) {
const x = new Node(e);
const temp = this.tail.prev;
temp.next = x;
x.prev = temp;
// temp <-> x
x.next = this.tail;
this.tail.prev = x;
// temp <-> x <-> tail
this.size++;
}
addFirst(e) {
const x = new Node(e);
const temp = this.head.next;
// head <-> temp
temp.prev = x;
x.next = temp;
this.head.next = x;
x.prev = this.head;
// head <-> x <-> temp
this.size++;
}
add(index, element) {
this.checkPositionIndex(index);
if (index === this.size) {
this.addLast(element);
return;
}
// 找到 index 對應的 Node
const p = this.getNode(index);
const temp = p.prev;
// temp <-> p
// 要插入的新 Node
const x = new Node(element);
p.prev = x;
temp.next = x;
x.prev = temp;
x.next = p;
// temp <-> x <-> p
this.size++;
}
// ***** 刪 *****
removeFirst() {
if (this.size < 1) {
throw new Error("No elements to remove");
}
// 虛擬節點的存在是我們不用考慮空指針的問題
const x = this.head.next;
const temp = x.next;
// head <-> x <-> temp
this.head.next = temp;
temp.prev = this.head;
// head <-> temp
this.size--;
return x.val;
}
removeLast() {
if (this.size < 1) {
throw new Error("No elements to remove");
}
const x = this.tail.prev;
const temp = x.prev;
// temp <-> x <-> tail
this.tail.prev = temp;
temp.next = this.tail;
// temp <-> tail
this.size--;
return x.val;
}
remove(index) {
this.checkElementIndex(index);
// 找到 index 對應的 Node
const x = this.getNode(index);
const prev = x.prev;
const next = x.next;
// prev <-> x <-> next
prev.next = next;
next.prev = prev;
this.size--;
return x.val;
}
// ***** 查 *****
get(index) {
this.checkElementIndex(index);
// 找到 index 對應的 Node
const p = this.getNode(index);
return p.val;
}
getFirst() {
if (this.size < 1) {
throw new Error("No elements in the list");
}
return this.head.next.val;
}
getLast() {
if (this.size < 1) {
throw new Error("No elements in the list");
}
return this.tail.prev.val;
}
// ***** 改 *****
set(index, val) {
this.checkElementIndex(index);
// 找到 index 對應的 Node
const p = this.getNode(index);
const oldVal = p.val;
p.val = val;
return oldVal;
}
// ***** 其他工具函式 *****
size() {
return this.size;
}
isEmpty() {
return this.size === 0;
}
getNode(index) {
this.checkElementIndex(index);
let p = this.head.next;
// TODO: 可以優化,通過 index 判斷從 head 還是 tail 開始遍歷
for (let i = 0; i < index; i++) {
p = p.next;
}
return p;
}
isElementIndex(index) {
return index >= 0 && index < this.size;
}
isPositionIndex(index) {
return index >= 0 && index <= this.size;
}
// 檢查 index 索引位置是否可以存在元素
checkElementIndex(index) {
if (!this.isElementIndex(index)) {
throw new Error(`Index: ${index}, Size: ${this.size}`);
}
}
// 檢查 index 索引位置是否可以添加元素
checkPositionIndex(index) {
if (!this.isPositionIndex(index)) {
throw new Error(`Index: ${index}, Size: ${this.size}`);
}
}
display() {
console.log(`size = ${this.size}`);
let p = this.head.next;
let str = "";
while (p !== this.tail) {
str += `${p.val} <-> `;
p = p.next;
}
console.log(str + "null\n");
}
}
// 範例
const list = new MyLinkedList();
list.addLast(1);
list.addLast(2);
list.addLast(3);
list.addFirst(0);
list.add(2, 100);
list.display();
// size = 5
// 0 <-> 1 <-> 100 <-> 2 <-> 3 <-> null
在實作鏈表的時候,有幾個關鍵點要注意: