一邊學習前端,一邊通過部落格的形式自己總結一些東西,當然也希望幫助一些和我一樣開始學前端的小夥伴。
如果出現錯誤,請在評論中指出,我也好自己糾正自己的錯誤
author: thomaszhou
1-單項鍊表
連結串列定義:
function LinkedList() {
let Node = function(element) { // 輔助類,表示要新增到連結串列中的項
this.element = element;
this.next = null; //next屬性是隻想連結串列的下一個節點的指標
};
let length = 0,
head = null; // 頭指標
this.append = function(element) {}; // 向連結串列尾部新增一個新的項
this.insert = function(element, position) {}; // 向指定位置插入一個新的項
this.removeAt = function(position) {}; // 移除連結串列中的指定項
this.remove = function(element) {}; // 移除值為element的項
this.indexOf = function(element) {}; // 返回元素在連結串列中的索引,如果沒有則返回-1
this.isEmpty = function() {}; // 判斷連結串列中是否為空
this.size = function() {}; // 連結串列的長度
this.clear = function() {}; // 清空連結串列
this.print = function() {}; //輸出連結串列的值
}
複製程式碼
append實現:向連結串列尾部新增一個新的項
this.append = function(element) { //向連結串列尾部新增一個新的項
let node = new Node(element),
current;
if (head === null) { // 連結串列中的第一個節點
head = node;
} else {
current = head;
while (current.next) { // 迴圈連結串列,直到找到最後一項
current = current.next;
}
current.next = node; // 找到最後一項,將next賦值為node,建立連線
}
length += 1; //更新連結串列長度
};
複製程式碼
print實現:輸出連結串列的值
this.print = function() {
let current = head;
for (let i = 0; i < length; i++) {
console.log(`第${i+1}個值:${current.element}`);
current = current.next;
}
};
複製程式碼
insert實現:向指定位置插入一個新的項
向指定位置插入一個新的項,步驟如下:
- 先進行position的越界判斷!!
- case1: 當position=0的時候,那就是直接將當前head指向的節點變成新建node的next指向,head指向新建的node上
- case2: 除case1外的情況,先遍歷到如下圖所示的情況,我們設定兩個指標,每次遍歷都是current將值賦給previous,然後current向後推進,直到current指向position的位置,previous指向position的前一個
- 然後將新建的node,插入其中,然後改變其中的指標指向,具體看程式碼
- 最後將length加一
this.insert = function(element, position) { // 向指定位置插入一個新的項
if (position >= 0 && position <= length) { // 檢查越界值
let node = new Node(element),
current = head, // 設定兩指標
previous;
if (position === 0) { // 在第一個位置新增
node.next = head;
head = node;
}else {
for (let i = 0; i < position; i++) {
// 使得current定在要插入的位置,previous定在要插入位置的前一個位置
previous = current;
current = current.next;
}
// 將新項node插入進去
node.next = current;
previous.next = node;
}
}else {
return 0;
}
length += 1;
};
複製程式碼
對於函式引數含有position這類的位置引數,一定要注意越界判斷
removeAt實現:移除連結串列中的指定位置項
removeAt的思想和前面的insert方法很相似,唯一的不同就是移除指定項時,改變指標指向的程式碼不同previous.next = current.next;
,
- 具體思想如圖:將current指向的項(其實也就是position指向的項)移除,那就將圖中紅色叉的指標指向取消,然後用新的代替
- 然後將lenght減一
只有
previous.next = current.next;
和insert方法不同,其他思想和做法都一樣
this.removeaAt = function(position) { // 移除連結串列中的指定項
if (position >= 0 && position <= length) {
let current = head,
previous;
if (position === 0) {
head = head.next;
}else {
for (let i = 0; i < position; i++) { //此處迴圈到current指向要移除的位置,同insert方法
previous = current;
current = current.next;
}
// 通過將previous的next變化到current.next,將指定項移除
previous.next = current.next;
}
}else {
return 0;
}
length -= 1;
};
複製程式碼
indexOf實現:返回元素在連結串列中的索引,如果沒有則返回-1
this.indexOf = function(element) { // 返回元素在連結串列中的索引,如果沒有則返回-1
let current = head; //
for (let i = 0; i < length; i++) {
if (current.element === element) {
return i;
}
current = current.next;
}
return -1;
};
複製程式碼
remove實現:移除值為element的項
this.remove = function(element) { // 移除值為element的項
// remove方法可以直接通過複用 this.indexOf(element) 和 this.removeAt(position) 方法實現
let index = this.indexOf(element);
return this.removeaAt(index);
};
複製程式碼
isEmpty、size、clear實現
this.isEmpty = function() { // 判斷連結串列中是否為空
return length === 0;
};
this.size = function() { // 連結串列的長度
return length;
};
this.clear = function() { // 清空連結串列
let current = head;
for (let i = 0; i < length; i++) {
this.removeAt(i);
}
length = 0;
head = null;
};
複製程式碼
測試函式
(function LinkedListTest() {
let linked = new LinkedList();
linked.append(2);
linked.append(3);
linked.append(4);
linked.print(); // 第1個值:2 第2個值:3 第3個值:4
linked.insert(5, 1); // 位置從0開始
linked.print(); // 第1個值:2 第2個值:5 第3個值:3 第4個值:4
linked.removeAt(1); // 相當於將上面插入的5再刪除
linked.print(); // 第1個值:2 第2個值:3 第3個值:4
console.log(linked.indexOf(3)); // 1
console.log(linked.indexOf(10)); // -1
console.log(linked.size()); // 3
console.log(linked.isEmpty()); // false
linked.clear();
console.log(linked.isEmpty()); // true
})();
複製程式碼
2-雙向連結串列
定義雙向連結串列
function DoublyLinkedList() {
let Node = function(element) {
this.element = element;
this.next = null;
this.prev = null; // 指向前一項的指標
};
let length = 0,
head = null,
tail = null; // 尾指標
//此處就是方法,都在下面詳細說明
}
複製程式碼
insert實現
相比較於單向連結串列,雙向連結串列比較複雜,它有後驅指標next還有前驅指標prev,那插入元素就要考慮的情況也多了
- 區別一:我們多了一個prev,也多一個尾部指標tail(永遠指向連結串列的尾部)
insert思路:
- case1: 插入時當position為0時要分兩種情況
- 連結串列為空:那就直接將head和tail都指向新建項node
- 連結串列不為空:那就將新建項node的next指標指向head,然後將head的prev指向node,以後將head指向node。
- case2: 當position等於length時(從尾部插入):
- 其實尾部插入比較簡單,直接就是將新建項node的prev指向tail指向的項,將tail直線給的項的next指向新建項node,最後將tail指向node
- case3: 當position從中間插入時
- 這個有點難理解,我們標記1234步,按照順序看程式碼,相信能理解其中的意思(綠色的表示要插入的項)
此處還有一個不一樣的地方,就是多了一個函式find,由於雙向連結串列,我們時可以通過一個指標current來找到前驅結點和後驅結點,(單向連結串列只能訪問後驅結點,所以才需要previous指標來作為前驅結點的指標)所以我們在這裡取消了previous指標,然後根據前面的學習,我們可以發現有一部分程式碼在插入和移除方法中都有,所以我們將這段程式碼抽離出來建立為find方法
<script>
this.find = function(position) { // 遍歷到position位置
let current = head;
for (let i = 0; i < position; i++) {
current = current.next;
}
return current;
}
this.insert = function(element, position) { // 指定位置插入值
if (position >= 0 && position <= length) {
let node = new Node(element);
let current = head,
previous = null;
if (position === 0) { // case1: 從連結串列頭部插入
if (!head) {
// 連結串列為空
head = node;
tail = node;
}else {
// 連結串列不為空
node.next = head;
head.prev = node;
head = node;
}
}else if (position === length) { // case2: 從連結串列尾部插入
node.prev = tail;
tail.next = node;
tail = node;
}else { // case3: 從連結串列的中間插入
current = this.find(position);
// 插入元素
node.next = current;
node.prev = current.prev;
current.prev.next = node;
current.prev = node;
}
length += 1;
}else {
return 0;
}
};
複製程式碼
removeAt實現
思路同insert,分三個階段進行移除項的操作,我重點說一下中間移除項的操作,有些地方是通過current指標和previous指標一起來遍歷,然後進行操作,其實previous可以用current.prev來代替,因為這是雙向連結串列,所以完全不需要多加一個previous指標。
/*
removeAt思路解析:
同insert,分三個階段進行移除項的操作
*/
this.removeAt = function(position) {
if (position >= 0 && position < length) {
let node = new Node(),
current = head;
if (position === 0) { // 移除第一項
head = head.next;
head.prev = null;
if (length === 1) { // 連結串列只有一項
tail = null;
}
}else if (position === length - 1) { // 移除最後一項
tail = tail.prev;
tail.next = null
}else { // 移除中間的項
current = this.find(position);
current.prev.next = current.next;
current.next.prev = current.prev;
}
length -= 1;
return current.element; // 返回被移除的項
}else {
return null;
}
};
複製程式碼
其他的方法
關於其他的方法,我只列出部分,因為其實單向和雙向的主要區別就在於有一個prev的前驅指標,還有一個尾指標tail,在其他的方法中,我們只需要注意這兩個點就好,以下程式碼均有註釋
this.append = function(element) { // 尾部新增項
let node = new Node(element),
current = head;
if (head === null) {
head = node;
tail = node;
}else {
// 這裡是和單連結串列不同的地方,也就是新增。
tail.next = node;
node.prev = tail;
tail = node;
}
length += 1;
};
複製程式碼
this.print = function() { // 輸出連結串列的值-同單向連結串列
let current = head;
for (let i = 0; i < length; i++) {
console.log(`第${i+1}個值:${current.element}`);
current = current.next;
}
};
this.clear = function() { // 清空連結串列
let current = head;
for (let i = 0; i < length; i++) {
this.removeAt(i);
}
length = 0;
head = null;
tail = null; // 此處需要將tail指標也賦值為null
};
this.size = function() { // 連結串列的長度-同單向連結串列
return length;
};
複製程式碼
3-迴圈連結串列
關於迴圈連結串列,和單向連結串列和雙向連結串列最大的不同就是尾部的next指向head
- 迴圈雙向連結串列
- 迴圈單向連結串列