資料結構分為
- 線性資料結構
- 二維資料結構
線性資料結構
線性資料結構強調的儲存和順序。
一維陣列
陣列特性
- 非常重要的一點,也是被前端程式設計師忽視的一點,陣列是定長的。也就是陣列的長度是固定的,因為在作業系統上來說,陣列的儲存必須是一段連續的空間。這也是為什麼資料量一大,通常不會用陣列來儲存。
- 為什麼JS的陣列可以隨意改變長度?那是JS引擎在底層實現的,當改變陣列長度時,會對陣列進行擴容操作,而擴容又是比較消耗效能的(因為擴容時,會新在記憶體中宣告一個空間,再把之前的陣列拷貝過去)。也就是說在宣告陣列的時候,可以大概給定一個長度,避免頻繁的擴容操作。
- 陣列的變數,指向了陣列第一個元素的位置。
var a = {1,2,3,4,5}; // a 指向 1 的位置
優點:查詢效能好,指定查詢某個位置;在作業系統中,通過偏移查詢資料效能好
缺點:
- 因為陣列的空間必須是連續的,所以陣列比較大的情況,當系統的空間碎片較多的時候,容易存不下;
- 因為陣列的長度是固定的,所以陣列的內容難以被新增和刪除;
陣列的操作
-
排序
排序的本質就是比較和交換,而不是比較大小。
// 比較 (其實Array.prototype.sort傳遞的函式,就是這個比較函式)
function compare(a, b) {
if (a > b) return true
else return false
}
// 交換
function exchange(arr, idxa, idxb) {
var temp = arr[idxa]
arr[idxa] = arr[idxb]
arr[idxb] = temp
}
複製程式碼
氣泡排序
- 迴圈,比較,交換
- 每一次迴圈,將最大的數推到最後面
- 迴圈這步操作
// 迴圈
function sort(arr) {
if (arr == null || arr.length <= 1) return arr
for (var i = 0; i < arr.length; i++) {
// 不用比較已經比較了的位置,比較j, j+1,所以取到j—1
for (var j = 0; j < arr.length - 1 - i; j++) {
if (compare(arr[j], arr[j + 1])) {
exchange(arr, j, j + 1)
}
}
}
}
複製程式碼
選擇排序
- 第一次從待排序的資料元素中選出最小(或最大)的一個元素,存放在序列的起始位置
- 然後再從剩餘的未排序元素中尋找到最小(大)元素,然後放到已排序的序列的末尾
- 迴圈直到全部待排序的資料元素的個數為零
// 選擇排序
function choose(arr) {
for (var i = 0; i < arr.length; i++) {
var maxIndex = 0;
for(var j = 0; j < arr.length - i; j++) {
if (compare(arr[j], arr[j + 1])){
maxIndex = j
}
}
exchange(arr[maxIndex], arr[arr.length - i])
}
}
複製程式碼
快速排序
- 簡單快排
function quickSort (arr) {
if(arr == null || arr.length <= 1) return arr;
// 1. 選出一個標準位置
var location = arr[0];
// 2. 比較,比標準位置大的放一邊,小的放另一邊
var left = [], right = [];
// 因為 0 已經做標準位置了,所以從 1 開始迴圈
for (var i = 1; i < arr.length; i++) {
if (arr[i] < location) left.push(arr[i]);
else right.push(arr[i]);
};
quickSort(left);
quickSort(right);
left.push(location);
return left.concat(right);
}
複製程式碼
簡單快速排序會不斷的建立新的陣列,犧牲了大量的空間,但是便於理解。
- 標準快排
不建立新的陣列,用陣列的index作為指標,對原陣列進行操作
不會做gif圖 很尷尬...
// 2. 標準快速排序
function realyQuickSort (arr, begin = 0, end = arr.length) {
if(arr == null || arr.length <= 1) return arr;
if (begin = end -1) return; // 兩個指標相差為1就不用比較了 挨著了
// 建立左 右指標
var left = begin, right = end;
do {
// 移動指標 當遇到左指標對應的數大於標準位置時,暫時停止一次
// 當遇到右指標小於標準位置時,暫時停止一次
// 交換兩個指標對應的陣列值
do left++;
while(left < right && arr[left] < arr[begin]);
do right--;
while(right > left && arr[right] > arr[begin]);
if (left < right) exchange(arr, left, right);
} while (left < right);
var exchangePoint = left == right ? right - 1 : right;
exchange(arr, begin, exchangePoint)
// 交換完一次後得到標準位置在中間的結果陣列,遞迴操作
realyQuickSort(arr, begin, exchangePoint)
realyQuickSort(arr, exchangePoint + 1, end)
}
複製程式碼
連結串列
單連結串列
想要傳遞一個連結串列,必須傳遞連結串列的根節點,而每一個節點都認為自己的根節點
一般討論連結串列,都指單連結串列,雙連結串列實現的功能,都可以用單連結串列實現
- 上一個物件 存著下一個物件的引用
var b = {
value: 1,
next: null,
}
var a = {
value: 2,
next: b
}
console.log(a.next === b) // true
複製程式碼
-
連結串列的特點
1). 空間上不是連續的
2). 每存放一個值,都需要多開銷一個引用空間
- 優點:
- 只要記憶體足夠大,就能存的下,不用擔心空間碎片的問題;
- 連結串列的新增和刪除非常的容易;
- 缺點:
- 查詢速度慢(查詢某個位置)
- 連結串列每一個節點都需要建立一個指向next的引用,浪費了一定記憶體空間,當儲存的資料越多時,這部分開銷的記憶體影響越小。
function Node (value) {
this.value = value;
this.next = null;
}
var a = new Node(1);
var b = new Node(2);
var c = new Node(3);
a.next = b;
b.next = c;
console.log(a.next.value); // 2
複製程式碼
雙向連結串列
- 沒有根節點的概念 雙向連結串列一般不使用
- 優點: 可以雙向查詢,方便
- 缺點: 多開銷一個引用空間,構建麻煩
function Node(val) {
this.value = value
this.next = null
this.prev = null
}
var node1 = new Node(1)
var node2 = new Node(2)
var node3 = new Node(3)
node1.next = node2
node2.prev = node1
node2.next = node3
node3.prev = node2
複製程式碼
連結串列的操作
- 連結串列的遍歷
// 迴圈遍歷
function bian(root) {
var temp = root;
while (true) {
if (temp != null) {
console.log(temp.value);
} else {
break;
};
temp = temp.next;
}
}
// 遞迴遍歷
function digui (root) {
if (root == null) return;
console.log(root.value);
digui(root.next);
}
複製程式碼
一般在遍歷時,都使用遞迴遍歷。
- 連結串列的逆置
連結串列的逆置就是將連結串列倒轉過來
/* 1. 找到連結串列的倒數第二個節點
* 2.倒數第一個節點就是倒數第二個節點的next
* 3. 將倒數第一個節點的next指向倒數第二個節點
* 4. 現在倒數第一個節點就是新的根節點
*/
function reverseLink(root) {
if (root.next.next == null){
root.next.next = root;
return root.next;
} else {
var res = reverseLink(root.next)
root.next.next = root
root.next = null
return res
}
}
const reverse = reverseLink(a)
複製程式碼
棧
棧是一種後進先出的資料結構,也就是說最新新增的項最早被移出;
它是一種運算受限的線性表,只能在表頭/棧頂進行插入和刪除操作。
棧有棧底和棧頂。入棧是把新元素放入到棧頂的上面,成為新的棧頂;出棧之後相鄰的成為新棧頂;也就是說棧裡面的元素的插入和刪除操作,只在棧頂進行。
小擴充:JS函式裡面有作用域的概念,有GO AO 的概念,而上面有一個只供系統使用的[[scop]]屬性,裡面存的就是這些GO AO, 底層實現原理估計就是棧資料結構,看來什麼時候得深入瞭解一下。
得益於JS的陣列特性,JS實現棧和佇列資料結構都非常的方便
var arr = []
// 棧 先入後出
class MyStack {
arr = []
// 入棧
push(val){
this.arr.push(val)
return this.arr
}
// 出棧
pop(){
return this.arr.pop()
}
}
const stack = new MyStack()
stack.push(1)
stack.push(12)
stack.push(14)
stack.push(15)
console.log(stack.pop()) //15
console.log(stack.pop()) //14
console.log(stack.push(19)) // [1, 12, 19]
console.log(stack.pop()) // 19
複製程式碼
佇列
佇列是一種先進先出的資料結構。
佇列在列表的末端增加項,在首端移除項。
它允許在表的首端(佇列頭)進行刪除操作,在表的末端(佇列尾)進行插入操作。
眾所周知:佇列是實現多工的重要機制!
// 佇列 先入先出
class Queen {
arr = []
// 入隊
push (val) {
this.arr.push(val)
return this.arr
}
// 出隊
pop () {
return this.arr.shift()
}
}
const queen = new Queen()
queen.push(1)
queen.push(12)
queen.push(14)
queen.push(15)
console.log(queen.pop()) // 1
console.log(queen.pop()) // 12
console.log(queen.push(19)) // [ 14, 15, 19 ]
console.log(queen.pop()) // 14
複製程式碼
二維資料結構
二維陣列
這個應該沒什麼好說的了,就是陣列
var arr = [ [12, 34], [21, 2123], [111, 876] ]
for(var i = 0; i < arr.length; i++){
for(var j = 0; j < arr[i].length, j++){
console.log(arr[i][j])
}
}
複製程式碼
螺旋矩陣問題
螺旋矩陣就是給定一個二維陣列,需要像貪吃蛇一樣繞圈往內層旋轉,將這個二維陣列變成一維陣列。
處理這種問題 主要就是要處理邊界問題,每一次讀取了一層之後,下一次就要往內層移動一層。
const arr: number[][] = [
[1, 2, 3, 4],
[12, 13, 14, 5],
[11, 16, 15, 6],
[10, 9, 8, 7],
];
// 這個陣列按照螺旋矩陣旋轉之後,返回的陣列就應該是[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]
// 從左往右,再往下,在往上,再往右迴圈。
function luoxuan(arr: number[][]): number[] {
// tslint:disable-next-line: curly
if (arr.length === 0 || !arr) return [];
const result: number[] = [];
let left = 0,
right = arr[0].length - 1,
top = 0,
bottom = arr.length - 1,
direction = "right";
while (left <= right && top <= bottom) { // 相等時也需要再迴圈一次,否則會漏掉一層
if (direction === "right") { // 從左往右
for (let i = left; i <= right; i++) {
result.push(arr[top][i]); // 從上方左往右的過程中,高度是不變的
}
top++; // 迴圈完之後,相當於高度削減了一層,下一次不走這一層了
direction = "bottom";
}
if (direction === "bottom") {
for (let i = top; i <= bottom; i++) {
result.push(arr[i][right]);
}
right--;
direction = "left";
}
if (direction === "left") {
for (let i = right; i >= left; i--) {
result.push(arr[bottom][i]);
}
bottom--;
direction = "top";
}
if (direction === "top") {
for (let i = bottom; i >= top; i--) {
result.push(arr[i][left]);
}
left++;
direction = "right";
}
}
return result;
}
複製程式碼
二維拓撲結構
二維拓撲結構,專業術語上也稱圖
- 二叉樹,多叉樹其實都是拓撲結構
// 拓撲結構
function Node (value) {
this.value = value
this.neighbor = []
}
var node1 = new Node(1)
var node2 = new Node(2)
var node3 = new Node(3)
var node4 = new Node(4)
node1.neighbor.push(node2, node3)
node2.neighbor.push(node1, node4)
node3.neighbor.push(node1, node4)
node4.neighbor.push(nod2, node3)
複製程式碼
樹形結構
- 樹是圖的一種
- 樹有一個根節點
- 樹沒有環形結構(迴路)
- 度: 樹的最多叉的節點有多少叉,就有多少度
- 深度: 樹最深有多少層,就是多少深度
在生活中樹形結構的資料應用,多的不能再多了吧:dom樹,目錄樹,關係樹...
最後
資料結構,大概先記這麼多,前端中這些資料結構的演算法、二叉樹、紅黑樹、斐波那契數列、動態規劃啥的,
且聽下回分解