資料結構全解參考:資料結構 | 部落格園-SRIGT
相關程式碼倉庫檢視:data-struct-js | Github-SR1GT
0x00 前置知識
(1)類
-
使用關鍵字
class
宣告一個類class Person { }
-
JavaScript 的類中透過
constructor
使用構建函式class Person { constructor(name) { this.name = name; } }
-
定義 Getter 與 Setter
class Person { constructor(name) { this.name = name; } getName() { return this.name; } setName(name) { this.name = name; } }
-
使用關鍵字
new
建立物件,並呼叫物件方法class Person { constructor(name) { this.name = name; } getName() { return this.name; } setName(name) { this.name = name; } } var person = new Person("SRIGT"); console.log(person.getName()); // "SRIGT" person.setName("SR1GT"); console.log(person.getName()); // "SR1GT"
(2)指標
-
以下程式碼僅實現值的傳遞,並未涉及指標
var num1 = 1; var num2 = num1; console.log(num1, num2); // 1 1 num1 = 2; console.log(num1, num2); // 2 1
-
當宣告的變數是物件時,實現指標的傳遞
var num1 = { value: 1 }; var num2 = num1; console.log(num1.value, num2.value); // 1 1 num1.value = 2; console.log(num1.value, num2.value); // 2 2
var num2 = num1;
是將變數num2
指向num1
,同時num1
又指向物件{ value: 1 }
-
當某個物件不被任何變數指向時,該物件不可訪問,但依舊佔用記憶體空間,此時 JavaScript 提供的垃圾回收機制很好的解決了這個問題
(3)遞迴
-
遞迴是指在函式中呼叫自身
function func() { if (condition === true) return; func(); }
- 當沒有合適條件停止遞迴,會造成堆疊溢位
-
舉例:階乘
function factorial(n) { if (n === 1) return 1; return n * factorial(n - 1); } console.log(factorial(4)); // 24
0x01 連結串列
(1)單連結串列
-
宣告類並實現建構函式
class Node { constructor(value) { this.value = value; this.next = null; } } class LinkedList { constructor(value) { this.head = new Node(value); this.tail = this.head; this.length = 1; } } var myLinkedList = new LinkedList(1); console.log(myLinkedList);
-
在
LinkedList
類中,新增push
方法,用於在尾部新增節點push(value) { const newNode = new Node(value); if (!this.head) { this.head = newNode; this.tail = this.head; } else { this.tail.next = newNode; this.tail = newNode; } this.length++; return this; }
-
新增
pop
方法,用於刪除尾部最後一個節點pop() { if (!this.head) return undefined; let prev = this.head; let temp = this.head; while (temp.next) { prev = temp; temp = temp.next; } this.tail = prev; this.tail.next = null; this.length--; if (this.length === 0) this.head = this.tail = null; return temp; }
-
新增
unshift
和shift
方法,分別用於在頭部新增和刪除第一個節點unshift(value) { const newNode = new Node(value); if (!this.head) { this.head = newNode; this.tail = this.head; } else { newNode.next = this.head; this.head = newNode; } this.length++; return this; } shift() { if (!this.head) return undefined; let temp = this.head; this.head = this.head.next; temp.next = null; this.length--; if (this.length === 0) this.tail = null; return temp; }
-
新增
get
方法,用於獲取特定索引位置的值get(index) { if (index === 0) return this.head; if (index < this.length * -1 || index >= this.length) throw new Error("Index out of bounds"); if (index < 0) index = this.length + index; let temp = this.head; for (let i = 0; i < index; i++) temp = temp.next; return temp; }
-
新增
set
方法,用於修改指定位置的值set(index, value) { let temp = this.get(index); if (!temp) return false; temp.value = value; return true; }
-
新增
insert
方法,用於在指定位置新增新節點insert(index, value) { if (index === 0) return this.unshift(value); if (index === this.length) return this.push(value); if (index < 0 || index > this.length) throw new Error("Index out of bounds"); let newNode = new Node(value); let prev = this.get(index - 1); newNode.next = prev.next; prev.next = newNode; this.length++; return this; }
-
新增
remove
方法,用於刪除指定位置的節點remove(index) { if (index === 0) return this.shift(); if (index === this.length - 1) return this.pop(); if (index < 0 || index >= this.length) throw new Error("Index out of bounds"); let prev = this.get(index - 1); let temp = prev.next; prev.next = temp.next; temp.next = null; this.length--; return temp; }
-
新增
reverse
方法,用於翻轉連結串列reverse() { let temp = this.head; let next = temp.next, prev = null; [this.head, this.tail] = [this.tail, this.head]; for (let i = 0; i < this.length; i++) { next = temp.next; temp.next = prev; prev = temp; temp = next; } return this; }
-
新增
forEach
方法,用於遍歷連結串列forEach(callback) { if (!this.head) return; let temp = this.head; while (temp) { callback(temp); temp = temp.next; } }
(2)雙向連結串列
-
宣告類並實現建構函式
class Node { constructor(value) { this.value = value; this.prev = null; this.next = null; } } class DoublyLinkedList { constructor(value) { this.head = new Node(value); this.tail = this.head; this.length = 1; } } var myDoublyLinkedList = new DoublyLinkedList(1); console.log(myDoublyLinkedList);
-
push
與pop
方法push(value) { const newNode = new Node(value); if (!this.head) { this.head = newNode; this.tail = this.head; } else { newNode.prev = this.tail; this.tail.next = newNode; this.tail = newNode; } this.length++; return this; } pop() { if (!this.head) return undefined; let temp = this.tail; this.tail = this.tail.prev; this.tail.next = null; temp.prev = null; this.length--; if (this.length === 0) this.head = this.tail = null; return temp; }
-
unshift
與shift
方法unshift(value) { const newNode = new Node(value); if (!this.head) { this.head = newNode; this.tail = this.head; } else { this.head.prev = newNode; newNode.next = this.head; this.head = newNode; } this.length++; return this; } shift() { if (!this.head) return undefined; let temp = this.head; this.head = this.head.next; this.head.prev = null; temp.next = null; this.length--; if (this.length === 0) this.tail = null; return temp; }
-
get
與set
方法get(index) { if (index === 0) return this.head; if (index === this.length - 1 || index === -1) return this.tail; if (index < this.length * -1 || index >= this.length) throw new Error("Index out of bounds"); // 最佳化方向: 折半查詢 if (index < 0) { let temp = this.tail; for (let i = this.length - 1; i > this.length + index; i--) temp = temp.prev; return temp; } else { let temp = this.head; for (let i = 0; i < index; i++) temp = temp.next; return temp; } } set(index, value) { let temp = this.get(index); temp.value = value; return temp; }
-
insert
與remove
方法insert(index, value) { if (index === 0) return this.unshift(value); if (index === this.length) return this.push(value); if (index < 0 || index > this.length) throw new Error("Index out of bounds"); let newNode = new Node(value); let prev = this.get(index - 1); newNode.next = prev.next; prev.next.prev = newNode; prev.next = newNode; newNode.prev = prev; this.length++; return this; } remove(index) { if (index === 0) return this.shift(); if (index === this.length - 1) return this.pop(); if (index < 0 || index >= this.length) throw new Error("Index out of bounds"); let temp = this.get(index); temp.prev.next = temp.next; temp.next.prev = temp.prev; temp.prev = null; temp.next = null; this.length--; return temp; }
(3)棧
-
宣告類並實現建構函式
class Node { constructor(value) { this.value = value; this.next = null; } } class Stack { constructor(value) { this.top = new Node(value); this.length = 1; } } var myStack = new Stack(1); console.log(myStack);
-
壓棧方法
push
push(value) { const newNode = new Node(value); if (!this.top) this.top = newNode; else { newNode.next = this.top; this.top = newNode; } this.length++; return this; }
-
出棧方法
pop
pop() { if (!this.top) return undefined; let temp = this.top; this.top = this.top.next; temp.next = null; this.length--; if (this.length === 0) this.top = null; return temp; }
(4)佇列
-
宣告類並實現建構函式
class Node { constructor(value) { this.value = value; this.next = null; } } class Queue { constructor(value) { this.first = new Node(value); this.last = this.first; this.length = 1; } } var myQueue = new Queue(1); console.log(myQueue);
-
入隊方法
enQueue
enQueue(value) { const newNode = new Node(value); if (!this.first) { this.first = newNode; this.last = this.first; } else { this.last.next = newNode; this.last = newNode; } this.length++; return this; }
-
出隊方法
deQueue
deQueue() { if (!this.first) return undefined; let temp = this.first; this.first = this.first.next; temp.next = null; this.length--; if (this.length === 0) this.first = this.last = null; return temp; }
0x02 樹與表
(1)二叉搜尋樹
-
宣告類並實現建構函式
class Node { constructor(value) { this.value = value; this.left = null; this.right = null; } } class BinarySearchTree { constructor() { this.root = null; } } var myBST = new BinarySearchTree(); console.log(myBST);
-
新增
insert
方法,用於新增節點insert(value) { const newNode = new Node(value); if (!this.root) { this.root = newNode; return this; } let temp = this.root; while (true) { if (temp.value === value) return undefined; else if (temp.value > value) { if (!temp.left) { temp.left = newNode; return this; } temp = temp.left; } else { if (!temp.right) { temp.right = newNode; return this; } temp = temp.right; } } }
-
新增
contains
方法,用於判斷節點是否存在contains(value) { if (!this.root) return false; let temp = this.root; while (true) { if (temp.value === value) return true; else if (temp.value > value) { if (!temp.left) return false; temp = temp.left; } else { if (!temp.right) return false; temp = temp.right; } } }
-
新增
minValueNode
方法,獲取最小節點值minValueNode() { let temp = this.root; while (temp.left) temp = temp.left; return temp; }
(2)雜湊表
-
宣告類並實現建構函式
class HashTable { constructor(size = 10) { this.dataMap = new Array(size); } _hash(key) { let hash = 0; for (let i in key) hash = (hash + key.charCodeAt(i) * 23) % this.dataMap.length; return hash; } } var myHashTable = new HashTable(); console.log(myHashTable);
-
新增
set
方法,用於新增新對映set(key, value) { const index = this._hash(key); if (!this.dataMap[index]) this.dataMap[index] = []; this.dataMap[index].push({ key: key, value: value }); return this; }
-
新增
get
方法,用於獲取鍵值對應的值get(key) { const index = this._hash(key); if (this.dataMap[index]) for (let item of this.dataMap[index]) if (item.key === key) return item.value; return undefined; }
-
新增
keys
方法,用於獲取全部鍵keys() { let allKeys = []; for (let item of this.dataMap) if (item) for (let kv of item) allKeys.push(kv.key); return allKeys; }
0x03 圖
採用鄰接表實現
(1)無向圖
-
宣告類並實現建構函式
class Graph { constructor() { this.adjacencyList = new Object(); } } var myGraph = new Graph(); console.log(myGraph);
-
新增
addVertex
方法,用於新增頂點addVertex(vertex) { if (!this.adjacencyList[vertex]) { this.adjacencyList[vertex] = []; return this; } throw new Error("Vertex already exists"); }
-
新增
addEdge
方法,用於新增邊addEdge(vertex1, vertex2) { if (this.adjacencyList[vertex1] && this.adjacencyList[vertex2]) { this.adjacencyList[vertex1].push(vertex2); this.adjacencyList[vertex2].push(vertex1); return this; } throw new Error("Vertex not found"); }
-
新增
removeEdge
方法,用於刪除邊removeEdge(vertex1, vertex2) { if (this.adjacencyList[vertex1] && this.adjacencyList[vertex2]) { this.adjacencyList[vertex1] = this.adjacencyList[vertex1].filter( (vertex) => vertex != vertex2 ); this.adjacencyList[vertex2] = this.adjacencyList[vertex2].filter( (vertex) => vertex != vertex1 ); return this; } throw new Error("Vertex not found"); }
-
新增
removeVertex
方法,用於刪除頂點removeVertex(vertex) { if (this.adjacencyList[vertex]) { this.adjacencyList[vertex].forEach((item) => { this.removeEdge(vertex, item); }); delete this.adjacencyList[vertex]; return this; } throw new Error("Vertex not found"); }
(2)有向圖
-
宣告類並實現建構函式
class Digraph { constructor() { this.adjacencyList = new Object(); } } var myDigraph = new Digraph(); console.log(myDigraph);
-
新增
addVertex
方法,用於新增頂點addVertex(vertex) { if (!this.adjacencyList[vertex]) { this.adjacencyList[vertex] = []; return this; } throw new Error("Vertex already exists"); }
-
新增
addEdge
方法,用於新增有向邊addEdge(vertexFrom, vertexTo) { if (this.adjacencyList[vertexFrom] && this.adjacencyList[vertexTo]) { this.adjacencyList[vertexFrom].push(vertexTo); return this; } throw new Error("Vertex not found"); }
-
新增
removeEdge
方法,用於刪除邊removeEdge(vertexFrom, vertexTo) { if (this.adjacencyList[vertexFrom] && this.adjacencyList[vertexTo]) { this.adjacencyList[vertexFrom] = this.adjacencyList[vertexFrom].filter( (vertex) => vertex != vertexTo ); return this; } throw new Error("Vertex not found"); }
-
新增
removeVertex
方法,用於刪除頂點removeVertex(vertex) { if (this.adjacencyList[vertex]) { Object.keys(this.adjacencyList).forEach((key) => { this.removeEdge(key, vertex); }); delete this.adjacencyList[vertex]; return this; } throw new Error("Vertex not found"); }
0x04 樹遍歷演算法
構造樹
class Node {
constructor(value) {
this.value = value;
this.left = null;
this.right = null;
}
}
class Tree {
constructor() {
this.root = null;
}
insert(value) {
const newNode = new Node(value);
if (!this.root) {
this.root = newNode;
return this;
}
let temp = this.root;
while (true) {
if (temp.value === value) return undefined;
else if (temp.value > value) {
if (!temp.left) {
temp.left = newNode;
return this;
}
temp = temp.left;
} else {
if (!temp.right) {
temp.right = newNode;
return this;
}
temp = temp.right;
}
}
}
}
本章的演算法均作為類方法寫在 Tree
類中
(1)廣度優先搜尋 BFS
-
廣度優先搜尋(Breadth First Search)指對於一個二叉樹進行層序遍歷
-
舉例:有如下二叉樹
flowchart TB 20-->15 & 26 15-->4 & 19 26-->22 & 102其 BFS 結果為
flowchart LR 20-->15-->26-->4-->19-->22-->102
-
-
演算法實現
BFS() { let temp = this.root, queue = [], result = []; queue.push(temp); while (queue.length) { temp = queue.shift(); result.push(temp.value); if (temp.left) queue.push(temp.left); if (temp.right) queue.push(temp.right); } return result; }
測試
var myTree = new Tree(); myTree.insert(20); myTree.insert(15); myTree.insert(4); myTree.insert(19); myTree.insert(26); myTree.insert(22); myTree.insert(102); console.log(myTree.BFS());
(2)深度優先搜尋 DFS
-
深度優先搜尋(Depth First Search)指對於一個二叉樹進行中序遍歷(或前序遍歷、後序遍歷)
-
舉例:有如下二叉樹
flowchart TB 20-->15 & 26 15-->4 & 19 26-->22 & 102其 DFS 結果為
flowchart LR 4-->15-->19-->20-->22-->26-->102
-
-
演算法實現
DFS(order) { let result = []; const expression = [ (node) => result.push(node.value), (node) => { if (node.left) traverse(node.left); }, (node) => { if (node.right) traverse(node.right); }, ]; const traverse = (node) => { order.forEach((item) => { expression[item](node); }); }; traverse(this.root); return result; }
測試
var myTree = new Tree(); myTree.insert(20); myTree.insert(15); myTree.insert(4); myTree.insert(19); myTree.insert(26); myTree.insert(22); myTree.insert(102); const Order = Object.freeze({ PRE: [0, 1, 2], IN: [1, 0, 2], POST: [1, 2, 0], }); console.log(myTree.DFS(Order.PRE)); console.log(myTree.DFS(Order.IN)); console.log(myTree.DFS(Order.POST));
0x05 排序演算法
(1)氣泡排序
從後出發,比較並交換,使最後一個值為最大值,進入下一個,重複上述操作
Array.prototype.bubble = function () {
for (let i = this.length - 1; i > 0; i--)
for (let j = 0; j < i; j++)
if (this[j] > this[j + 1])
[this[j], this[j + 1]] = [this[j + 1], this[j]];
return this;
};
console.log([4, 2, 3, 1, 5].bubble());
(2)選擇排序
(氣泡排序的逆向)從頭出發,記錄當前位置以後的最小值的索引,比較並交換,進入下一個,重複上述操作
Array.prototype.selection = function () {
let minIndex = this[0];
for (let i = 0; i < this.length - 1; i++) {
minIndex = i;
for (let j = i + 1; j < this.length; j++)
if (this[j] < this[minIndex]) minIndex = j;
if (i !== minIndex) [this[i], this[minIndex]] = [this[minIndex], this[i]];
}
return this;
};
console.log([4, 2, 3, 1, 5].selection());
(3)插入排序
從第二項出發,找到比當前值小的值,該小值以後的值後移一位,小值後面插入到當前值,進入下一個,重複此操作
Array.prototype.insertion = function () {
for (let i = 1; i < this.length; i++) {
let temp = this[i];
for (var j = i - 1; this[j] > temp && j >= 0; j--) this[j + 1] = this[j];
this[j + 1] = temp;
}
return this;
};
console.log([4, 2, 3, 1, 5].insertion());
(4)合併排序
將陣列遞迴二分分組,直至每組只有兩個數字,比較並交換後逐步合併各組至原陣列大小
-
編寫
merge
方法,用於按大小合併兩個陣列const merge = (array1, array2) => { let i = 0, j = 0, result = []; while (i < array1.length && j < array2.length) if (array1[i] < array2[j]) result.push(array1[i++]); else result.push(array2[j++]); while (i < array1.length) result.push(array1[i++]); while (j < array2.length) result.push(array2[j++]); return result; };
-
結合合併方法,完成合並排序方法
Array.prototype.merge = function () { if (this.length <= 1) return this; const merge = (array1, array2) => {/*...*/}; let mid = Math.floor(this.length / 2), left = this.slice(0, mid), right = this.slice(mid); return merge(left.merge(), right.merge()); }; console.log([4, 2, 3, 1, 5].merge());
(5)快速排序
找到中心值,所有小於中心值的值放在中心值以左,其餘的值放在中心值以右,遞迴此操作
-
編寫
pivot
方法,用於獲取陣列中心值的索引const pivot = (array, pivotIndex, endIndex) => { let temp = pivotIndex; for (let i = pivotIndex + 1; i <= endIndex; i++) if (array[i] < array[pivotIndex]) { temp++; [array[temp], array[i]] = [array[i], array[temp]]; } [array[pivotIndex], array[temp]] = [array[temp], array[pivotIndex]]; return temp; };
-
結合中心值索引獲取方法,完成快速排序方法
Array.prototype.quick = function (left = 0, right = this.length - 1) { const pivot = (pivotIndex, endIndex) => {/*...*/}; if (left < right) { const pivotIndex = pivot(left, right); this.quick(left, pivotIndex - 1); this.quick(pivotIndex + 1, right); } return this; };
-End-