JS數據結構與算法讀書筆記

##數組
1.幾乎所有的編程語言都原生支持數組類型,數組是最簡單的內存數據結構
2.創建和初始化數組
let daysOfWeek = [];
3.訪問元素和迭代元素
for (let i = 0;i < daysOfWeek;i++){
console.log(daysOfWeek[i]);
}
4.添加元素
numbers[numbers.length] = 10;
push:數組末尾添加
unshift:數組開頭插入
js裏,數組是一個可以修改的對象,會動態增長,在其他語言中,改變數組要創建一個全新的數組
5.刪除元素
pop:刪除數組最靠後的元素
shift:刪除數組第一個元素
6.任意位置添加和刪除
使用splice指定位置/索引
splice第一個參數表示要刪除/插入的元素的索引值,第二個參數是刪除的個數
第三個參數往後是需要添加進去的值
7.二維和多維數組
1.二維數組
數組嵌套數組
let averageTemp = [];
averageTemp[0] = [72,75,76];
averageTemp[1] = [81,54,56];
#迭代二維數組
function printMatrix(myMatrix){
for (let i = 0;i < mymatrix.length;i++){
for (let j = 0;j < myMatrix[i].length;j++){
console.log(myMatrix[i][j])
}
}
}
2.多維數組
同理創建更多的維度
8.JS數組方法
1.concat:連接2個或者更多數組,並返回結果
every:對每一個元素都運行指定函數,如果每一個元素都返回true,則返回true
filter:對每一個元素都運行指定函數,返回該函數會返回true的元素組成的數組
forEach:對每一個元素都運行指定函數,無返回值
join:將所有數組元素連接成一個字符串
indexOf:返回第一個與給定參數相等的元素的索引,木有則返回-1
lastIndexOf:返回在數組中搜索到的與給定參數相等的元素的最後一個元素的索引
map:對數組中的元素運行給定函數,返回每次函數調用的結果組成的數組
reverse:倒置
slice:傳入索引值,將對應索引範圍內的元素作爲新數組返回
some:對每一個元素都運行指定函數,如果有一個元素返回true,則返回true
sort:按照字母順序對數組進行排序,支持傳入指定排序方法的函數作爲參數
toString:作爲字符串返回
valueOf:與toString類似
reduce:累加器
2.ES6新增數組方法
@@iterator:返回一個包含數組鍵值對的迭代對象,可以通過同步調用得到數組元素的鍵值對
copyWithin:賦值數組中一系列元素到同一數組指定的起始位置
entries:返回包含數組所有鍵值對的@@iterator
includes:如果數組中存在某個元素則返回true否則返回false
find:根據回調函數給定的條件從數組中查找元素,找到了就返回該元素
findIndex:根據回調函數給定的條件從數組中查找元素,找到了就返回索引
fill:用靜態值填充數組
from:根據已有數組創建一個新數組
keys:返回包含數組所有索引的@@iterator
of:根據傳入的參數創建一個新數組
values:返回包含數組中所有值的@@iterator
1.使用for…of循環迭代
for (const n of numbers){
console.log(n % 2 === 0 ? ‘even’ : ‘odd’);
}
2.使用@@iterator對象
ES5新增,需要Symbol.iterator訪問
let iterator = numbersSymbol.iterator;
console.log(iterator.next().value);
當數組中所有值都迭代完後,返回undefined
3.數組的entries、keys、values
1.entries:返回包含鍵值對的@@iterator
let aEntries = numbers.entries();
console.log(aEntries.next().value);
2.keys:返回包含數組索引的@@iterator
一旦沒有可以迭代的值,返回一個value爲undefined,done爲true的對象
3.values:返回包含數組的值
4.from
根據已有數組創建一個新數組,複製、過濾值
5.Array.of
根據傳入的參數創建一個新數組
let numberCopy = Array.of(…numbers4)
(…)將數組的值全部展開成參數
6.fill
填充,第一個參數爲填充值,第二個爲開始填充索引,第三個參數爲結束填充索引
7.copyWithin
複製一系列元素到指定位置
3.排序元素
function compare(a,b){
if (a<b){
return -1;
}
if (a>b){
return 1;
}
return 0;
}
numbers.sort(compare)
可以對任何對象類型的數組排序
在對字符串進行比較時,根據字符對應的ASCII來比較
4.搜索
5.類型數組
JS數組不是強類型,可以存儲任何類型的數據
類型數組用於存儲單一類型的數組,聲明數組類型
##棧(stack)
有時需要能在刪除或添加進行更多控制的數據結構
1.棧數據結構
棧遵從後進先出(LIFO)原則的有序集合,新添加或待刪除的都保存在棧的同一端,稱爲棧頂,另一端叫棧底
新元素都靠近棧頂,就元素都靠近棧底
2.創建一個基於數組的棧
class Stack{
constructor(){
this.items = [];
}
}
棧方法
push:添加一個或幾個新元素到棧頂
pop:移除棧頂的元素,返回被移除的元素
peek:返回棧頂的元素,不對棧做任何修改
isEmpty:棧裏沒有任何元素就返回true,否則返回false
clear:移除棧裏的所有元素
size:返回棧的元素個數
1.push(element){//因爲使用數組來保存棧裏的元素,所有可以使用push方法
this.items.push(element);
}
2.pop(){
return this.items.pop();
}
3.peek(){
return this.items[this.items.length - 1];
}
4.isEmpty(){
return this.items.length === 0;
}
5.clear(){
this.items = [];
}
6.使用stack類
const stack = new Stack();
console.log(stack.isEmpty());
3.創建一個基於JS對象的stack類
1.聲明一個stack類
class Stack{
constructor(){
this.count = 0;
this.items = {};
}
}
2.向棧插入元素
將使用count變量作爲items對象的鍵名,插入的元素則是它的值
3.檢驗一個棧是否爲空
通過檢驗count的值
4.從棧中彈出元素
pop(){
if (this.isEmpty()){
return undefined;
}
this.count–;
const result = this.items[this.count];
delete this.items[this.count];
return result;
}
5.查看棧頂的值並將棧清空
6.創建toString方法
toString(){
if (this.isEmpty()){
return ‘’;
}
let objString = ${this.items[0]};
for (let i = 1;i < this.count;i++){
objString = ${objString},${this.items[i]};
}
return objString;
}
7.保護數據結構內部元素
希望保護內部的元素,只有我們暴露的方法才能修改內部結構
對於stack類來說,確保元素只會被添加到棧頂,而不是其他地方
8.使用JS實現私有屬性的方法
一部分開發者喜歡使用下劃線約定一格私有屬性(並不是實現了私有屬性)
1.用ES5的WeakMap實現類
有一種數據類型可以確保屬性是私有的,WeakMap,可以存儲鍵值對,鍵是對象,值可以使任何數據類型
聲明一個WeakMap類型的變量items
const items = new WeakMap();
class Stack{
constructor(){
items.set(this, []);
}
}
9.用棧解決問題
##隊列和雙端隊列
1.隊列遵循先進先出(FIFO),隊列在尾部添加新元素,並從頂部移除元素,新元素必須添加到隊列末尾
2.創建隊列
class Queue{
constructor(){
this.count = 0;
this.lowestCount = 0;
this.items = {};
}
}
首先需要一個存儲隊列中的元素的數據結構,聲明一些隊列可用的方法
enqueue():向隊列尾部添加一個或多個新的項
dequeue:移除隊列第一個元素,返回被移除的元素
peek:返回隊列第一個元素,隊列不做變化
isEmpty:檢測隊列是否爲空
size:大小
1.向隊列添加元素
enqueue(element){
this.items[this.count] = element;
this.count++;
}
2.從隊列移除元素
dequeue(){
if (this.isEmpty()){
return undefined;
}
const result = this.items[this.lowestCount];
delete this.items[this.lowestCount];
this.lowestCount++;
return result;
}
3.查看隊列頭元素peek
4.檢測隊列是否爲空並獲取它的長度
5.清空隊列:clear
6.創建toString方法
3.雙端隊列數據結構
雙端隊列:允許我們同時從前端和後端添加和移除元素的特殊隊列
1.創建Deque類
class Deque{
constructor(){
this.count = 0;
this.lowestCount = 0;
this.items = 0;
}
}
1.方法
addFront:前端添加新元素
addBack:後端添加新元素
removeFront:前端移除新元素
removeBack:後端移除新元素
peekFront:返回前端第一個新元素
peekBack:返回後端第一個新元素
2.向雙端對列的前端添加元素
addFront(element){
if (this.isEmpty()){
this.addBack(element);
}else if (this.lowestCount > 0){
this.lowestCount–;
this.items[this.lowestCount] = element;
}else{
for (let i = this.count; i > 0;i–){
this.items[i] = this.items[i-1];
}
this.count++;
this.lowestCount = 0;
this.items[0] = element;
}
}
3.擊鼓傳花 檢測迴文
##鏈表
數組大小固定,從數組的起點或中間插入或移除的成本很高,因爲需要移動元素
鏈表動態數據結構,可以隨意添加和移除項,按需進行擴容
1.鏈表數據結構
鏈表存儲有序的元素集合,但不同於數組,鏈表中的元素在內存中並不是連續放置
每個元素由一個存儲元素本身的節點和一個指向下一個元素的引用(也稱爲指針或鏈接)組成
數組:可以直接訪問任何位置的任何元素
鏈表:訪問鏈表中的元素,需要從起點(表頭)開始迭代鏈表直到找到所需的元素
2.創建鏈表
import {defaultEquals} from ‘…/util’;
import {Node} from ‘./models/linked-list-models’;

        export default class LinkedList{
            constructor(equalsFn = defaultEquals){
                this.count = 0;
                this.head = undefined;
                this.equalsFn = equalsFn;
            }
        }
        註釋:indexOf方法可以幫我們在鏈表找到一個特定的元素
             要比較鏈表中的元素是否相等,需要一個內部調用的函數,名爲equalsFn
             head用來保存第一個元素的引用
             要保存鏈表中第一個以及其他元素,需要一個助手類,叫做Node
             Node表示我們想要添加到鏈表的項,包含一個element屬性,該屬性表示要加入鏈表元素的值
             以及一個next屬性,還屬性就是指向下一個元素的指針
        方法:
            push:鏈表尾部添加
            insert:鏈表插入
            getElementAt:返回特定位置的元素,不存在則返回undefined
            remove:鏈表移除
            indexOf:返回元素在鏈表的索引,不存在返回-1
            removeAt:從特定位置移除
            isEmpty:
            size:
            toString:
        1.push方法
            push(element){
                const node = new Node(element);傳入element創建Node項
                let current;
                if (this.head == null){
                    this.head = node;
                }else{
                    current = this.head;
                    while(current.next != null){
                        current = current.next;
                    }
                    current.next = node;
                }
                this.count++;
            }
        2.removeAt/remove
            removeAt(index){
                if (index >= 0 && index < this.count){
                    let current = this.node;
                    if (index === 0){
                        this.head = current.next;
                    }else{
                        let previous;
                        for (let i = 0;i < index;i++){
                            previous = current;
                            current = current.next;
                        }
                        previous.next = current.next;
                    }
                    this.count--;
                    return current.element;
                }
                return undefined;
            }
        3.循環迭代鏈表直到目標位置
            getElementAt(index){
                if (index >= 0 && index <= this.count){
                    let node = this.head;
                    for (let i = 0;i < index && node != null;i++){
                        node = node.next;
                    }
                    return node;
                }
                return undefined;
            }
        4.insert
            insert(element,index){
                if (index >= 0 && index <= this.count){
                    const node = new Node(elelment);
                    if (index === 0){
                        const current = this.head;
                        node.next = current;
                        this.head = node;
                    }else{
                        const previous = this.getElementAt(index - 1);
                        const current = previous.next;
                        node.next = current;
                        previous.next = node;
                    }
                    this.count++;
                    return true;
                }
                return false;
            }
        5.indexOf
            indexOf(element){
                let current = this.head;
                for (let i = 0;i < this.count && current != null;i++){
                    if (this.equalsFn(element,current.element)){
                        return i;
                    }
                    current = current.next;
                }
                return -1;
            }
        6.移除元素
            創建完indexOf方法,可以實現其他方法,比如remove
                remove(element){
                    const index = this.indexOf(element);
                    return this.removeAt(index);
                }
    3.雙向鏈表
        鏈接是雙向的,一個鏈向下一個元素,一個鏈向上一個元素,同時控制next和prev兩個指針
    4.循環鏈表
        最後一個元素指向下一個元素的指針(tail.next)不是引用undefined,而是指向第一個元素(head)
    5.有序鏈表
        保持元素有序的鏈表,將元素插入到正確位置實現鏈表的有序性

##集合
一種不允許值重複的順序數據結構
1.構建數據集合
集合由一組無序且唯一的項組成{}
2.構建集合類
class Set{
constructor(){
this.items = {};
}
}
方法:
add:添加新元素
delete:移除元素
has:元素是否在集合中
clear:清除所有元素
size:返回集合包含的數目
values:返回一個包含集合中所有值的數組
##字典和散列表
##遞歸
特殊的方法操作樹和圖數據結構,那就是遞歸
1.要理解遞歸,首先要理解遞歸
2.遞歸是解決問題的一種方法,解決問題的各個小部分開始,直到解決最後的大問題,通常涉及函數調用自身
3.每個遞歸函數都必須有基線條件,即一個不再遞歸調用的條件
4.迭代階乘
function factorialIterative(number){
if (number < 0) return undefined;
let tatal = 1;
for (let n = number;n > 1;n–){
tatal = tatal * n;
}
return total;
}
console.log(factorialIterative(5));
5.遞歸階乘
function factorial(n){
if (n === 1 || n === 0){
return 1;
}
return n * factorial(n - 1);
}
console.log(factorial(5));
6.調用棧
每當一個函數被一個算法調用時,該函數會進入調用棧的頂部,當使用遞歸的時候,每個函數調用都會堆疊在調用棧的頂部
每個調用都可能依賴前一個調用的結果
7.斐波那契數列
function fibonacci(n){
if (n < 1) return 0;
if (n <= 2) return 1;
return fibonacci(n - 1) + fibonacci(n - 2);
}
8.迭代比遞歸版本快得多,但是遞歸更容易理解,需要的代碼也更少,解決問題更簡單
##樹
非順序數據結構–樹,對存儲需要快速查找的數據非常有用
1.樹數據結構
樹結構包含一系列存在父子關係的節點,每個節點都有一個父節點(除了頂部的第一個節點),以及零個或多個子節點
1.根節點:位於樹頂部的節點叫做根節點,沒有父節點
2.樹中每一個元素都叫做節點,節點分爲內部節點和外部節點
1.內部節點:至少有一個子節點的節點
2.外部節點:沒有子節點的稱爲外部節點或葉節點
3.一個節點可以有祖先和後代
4.子樹:子樹由節點和它的後代構成
5.深度:節點的深度取決於他的祖先節點的數量
6.高度:取決於所有節點深度的最大值,層級從0開始,根節點在第0層
2.二叉樹和二叉搜索樹
1.二叉樹中的節點最多隻能有兩個子節點,一個左側子節點,一個右側子節點,有利於我們進行高效的進行查找、插入、刪除節點
2.二叉搜索樹(BST):是二叉樹的一種,只允許在左側節點存儲(比父節點)小的值,在右側節點存儲(比父節點)大的值
3.創建BinarySearchTree類
export class Node{
constructor(key){
this.key = key;//節點值
this.left = null;//左側子節點引用
this.right = null;//右側子節點引用
}
}
通過指針來表示節點之間的關係(邊),樹中也使用兩個指針,一個指向左側子節點,一個指向右側子節點
聲明一個Node類來表示樹中的每個節點,鍵是樹中對節點的稱呼
聲明一個root來控制樹的第一個節點
方法:
1.insert:向樹中插入鍵
2.search:在樹中查找鍵,有責返回true,無則返回false
3.inOrderTraverse:中序遍歷所有節點
4.preOrderTraverse:先序遍歷所有節點
5.postOrderTraverse:後序遍歷所有節點
6.min:返回最小的值/鍵
7.max:返回最大的值/鍵
8.remove:移除鍵
4.向二叉搜索樹插入一個鍵
import { Compare, defaultCompare } from ‘…/util’;
import {Node} from ‘./models/node’;
export default class BinarySearchTree{
constructor(compareFn = defaultCompare){
this.compareFn = compareFn;
this.root = null;
}
}
insert(key){
if (this.root == null){
this.root = new Node(key);
}else{
this.insertNode(this.root, key);
}
}
1.如果插入的是第一個節點,創建一個Node實例,並將它賦值給root,指向這個新節點
2.添加到根節點以外的其他位置,需要一個輔助方法
insertNode(node,key){
if (this.compareFn(key,node.key) === Compare.LESS_THAN){
if (node.left == null){
node.left = new Node(key);
}else{
this.insertNode(node.left, key);
}
}else{
if (node.right == null){
node.right = new Node(key);
}else{
this.indertNode(node.right, key);
}
}
}
3.indertNode會幫助我們找到新節點應該插入的正確位置
1.傳入樹的根節點和要插入的節點
2.新節點的鍵小於根節點的鍵,那麼需要檢測左側子節點,使用compareFn來比較值
如果沒有左側子節點,就在那插入新節點,如果有子節點,就遞歸調用insertNode
尋找樹的下一層
3.如果新節點的鍵大於根節點的鍵,同理,檢測右側子節點,插入新節點
5.樹的遍歷
遍歷一棵樹是訪問每個節點並進行某種操作
1.中序遍歷(左 自己 右)
中序遍歷是一種以上行順序訪問BST所有節點的遍歷方式,從最小到最大的順序訪問所有節點
中序遍歷的一種應用就是對樹進行排序操作
inOrderTraverse(callback){
this.inOrderTraverseNode(this.root, callback);
}
回調函數來定義我們對遍歷到的每一個節點的操作
inOrderTraverseNode(node,callback){
if (node != null){
this.inOrderTraverseNode(node.left,callback);
callback(node.key);
this.inOrderTraverseNode(node.right, callback);
}
}
首先判斷以參數傳入的節點是否爲null,即停止遞歸繼續執行的判斷條件
遞歸調用相同函數來訪問左側子節點,然後進一些操作,然後訪問右側子節點
2.先序遍歷(自己 左 右)
先序遍歷是以優先於後代節點的順序訪問每個節點,打印一個結構化的文檔
preOrderTraverse(callback){
this.preOrderTraverseNode(this.root, callback);
}
preOrderTraverseNode(node, callback){
if (node != null){
callback(node.key);
this.preOrderTraverseNode(node.left, callback);
this.preOrderTraverseNode(node.right, callback);
}
}
3.後序遍歷(左 右 自己)
後序遍歷則是先訪問節點的後代節點,再訪問節點本身,文件所佔空間的大小
postOrderTraverse(callback){
this.postOrderTraverseNode(this.root, callback);
}
postOrderTraverseNode(node, callback){
if (node != null){
this.postOrderTraverseNode(node.left, callback);
this.postOrderTraverseNode(node.right, callback);
callback(node.key);
}
}
4.搜索樹中的值
1.搜索最大值和最小值
最小值:沿着樹的左邊遍歷
最大值:沿着樹的右邊遍歷
min(){
return this.minNode(this.root);
}
minNode(node){
let current = node;
while (current != null && current.left != null){
current = current.left;
}
return current;
}
maxNode(node){
let current = node;
while (current != null && current.right != null){
current = current.right;
}
return current;
}
2.搜索特定的值
search(key){
return this.searchNode(this.root, key);
}
searchNode(node, key){
if (node == null){
return false;
}
if (this.compareFn(key, node.key) === Compare.LESS_THAN){
return this.searchNode(node.left, key);
}else if(
this.compareFn(key, node.key) === Compare.BIGGER_THAN
){
return this.searchNode(node.right, key);
}else{
return true;
}
}
首先檢測傳入的值是否爲空,比當前節點小就在左側子樹找,大就在右側子樹找
5.移除節點
remove(key){
this.root = this.removeNode(this.root, key);
}
root被賦值爲removeNode方法的返回值
removeNode(node, key){
if (node == null){
return null;
}
}
if (this.compareFn(key,node.key) === Compare.LESS_THAN){
node.left = this.removeNode(node.left, key);
return node;
}else if (
this.compareFn(key, node.key) === Compare.BIGGER_THAN
){
node.right = this.removeNode(node.right, key);
return node;
}else{
if (node.left == null && node.right == null){
node = null;2
return node;
}
if (node.left == null){
node = node.left;
return node;
} else if (node.right == null){
node = node.right;
return node;
}
const aux = this.minNode(node.right);
node.key = aux.key;
node.right = this.removeNode(node.right, aux.key);
return node;
}
1.檢測鍵爲null,返回null
2.存在則需要找到要移除的鍵,找到後要處理三種不同的情況
1.移除一個葉節點
該節點沒有任何的左側或右側子節點的葉節點,給他賦值null
但是賦值null是不夠的,還需要處理指針
可是他沒有子節點,於是返回null將對應的父節點指針賦予null值
2.移除有一個左側或右側子節點的節點
需要跳過這個節點,直接將父節點指向它的指針指向子節點
3.移除有兩個子節點的節點
1.找到了要移除的節點,需要找到它右邊子樹中的最小節點
2.然後用這個最小節點的鍵去更新這個節點的值,也就是說它被移除了
3.但是擁有兩個相同鍵的節點不合理,繼續把右側子樹中的最小節點移除
4.向它的父節點返回更新後的節點引用
6.自平衡樹
BST存在一個問題,一邊會非常深,一邊卻只有幾層
1.AVL樹:自平衡二叉搜索樹,任何一個節點左右兩側子樹的高度之差最多爲1
2.添加或移除節點時,它會嘗試保持平衡
3.擴展我們寫的BST類,覆蓋用來維持AVL樹平衡的方法,檢驗它的平衡因子
4.節點高度
getNodeHeight(node){
if (node == null){
return -1;
}
return Math.max(
this.getNodeHeight(node.left), this.getNodeHeight(node.right)
) + 1;
}
右子樹高度hr和左子樹高度hl之間的差值,應爲1、-1、0,如果不是則需要平衡該AVL樹
7.紅黑樹
需要一個包含多次插入和刪除的操作,那麼紅黑樹是比較好的
1.節點不爲紅就爲黑
2.樹的根節點是黑的
3.所有葉節點都是黑的
4.如果一個節點爲紅,那麼它的兩個子節點都爲黑
5.不能有兩個相鄰的紅節點
6.從給定節點到它的後代節點所有路徑包含相同數量的黑色節點
##二叉堆和堆排序
特殊的二叉樹,也就是堆數據結構,也叫二叉堆
可以高效、快速的找出最大值和最小值,常被用於優先隊列,堆排序算法
1.一棵完全二叉樹,樹每一層都有左側和右側子節點(除最後一層的葉節點),
並且最後一層的葉節點儘可能使左側子節點,叫做結構特性
2.二叉堆不是最小堆就是最大堆,允許你快速找出最小/大值,
所有的節點都大於等於(最大堆)或小於等於(最小堆)每個它的子節點,叫做堆特性
3.不一定是二叉搜索樹,每個子節點都要大於等於父節點或小於等於父節點
4.創建最小堆類
import {defaultCompare} from ‘…/util’;
export class MinHeap{
constructor(compareFn = defaultCompare){
this.compareFn = compareFn;
this.heap = [];
}
}
1.二叉樹兩種表示方法:指針/數組,對於數組使用以下方法
getLeftIndex(index){
return 2 * index + 1;
}
getRightIndex(index){
return 2 * index + 2;
}
getParentIndex(index){
if (index === 0){
return undefined;
}
return Math.floor((index - 1) / 2);
}
1.insert:插入一個新值
將值插入堆的底部葉節點,再執行siftUp方法,表示要與父節點進行交換,直到父節點小於這個插入值
insert(value){
if (value != null){
this.heap.push(value);
this.siftUp(this.heap.length - 1);
return true;
}
return false;
}
上移操作
siftUp(index){
let parent = this.getParentIndex(index);
while(
index > 0 &&
this.compareFn(this.heap[parent], this.heap[index]) >
Compare.BIGGER_THAN
){
swap(this.heap, parent, index);
index = parent;
parent = this.getParentIndex(index);
}
}
交換函數
const swap = (array, a, b) => [array[a], array[b]] = [array[b], array[a]];
2.extract:移除最大值/最小值,並返回這個值
將堆的最後一個元素移動到根部執行siftDown函數
extract(){
if (this.isEmpty()){
return undefined;
}
if (this.size() === 1){
return this.heap.shift();
}
const removedValue = this.heap.shift();
this.siftDown(0);
return removedValue;
}
如果堆爲空,返回undefined
下移操作
siftDown(index){
let element = index;
const left = this.getLeftIndex(index);
const right = this.getRightIndex(index);
const size = this.size();
if (
left < size &&
this.compareFn(this.heap[element], this.heap[right]) >
Compare.BIGGER_THAN
) {
element = right;
}
if (index !== element){
swap(this.heap, index, element);
this.siftDown(elelment);
}
}
3.findMinimum:返回最大值/最小值,不會移除這個值
size(){
return this.heap.length;
}
isEmpty(){
return this.size() === 0;
}
findMinimum(){
return this.isEmpty() ? undefined : this.heap[0];
}
5.創建最大堆類
類似於創建最小堆類
6.堆排序算法
1.用數組創建一個最大堆用作源數據
2.創建最大堆後,最大的值會被存儲在堆的第一個位置,要將它替換爲堆的最後一個值,堆大小減一
3.將堆的根節點下移並重復步驟2直到堆的大小爲1
##圖
非線性數據結構:圖
圖是網絡結構的抽象模型,一組由邊連接的節點,任何二元關係都可以用圖來表示
1.一個圖G=(V,E)
V:一組頂點
E:一組邊,連接V中的頂點
2.由一條邊連接在一起的頂點稱爲相鄰頂點
一個頂點的度是其相鄰頂點的數量
路徑是頂點的一個連續序列
簡單路徑要求不包含重複的頂點
環也是一個簡單路徑
無環:無環的 連通的:每兩個頂點間都存在路徑
3.有向圖/無向圖
圖可以是無向的或是有向的,雙向上都存在路徑則是強連通的
4.圖的表示
1.鄰接矩陣,每個節點都和一個整數相關聯,用一個二維數組來表示頂點之間的連接
array[i][j] === 1,array[i][j] === 0
不是強連通的圖如果用鄰接矩陣來表示,則矩陣中將會有很多0
意味着要浪費計算機存儲空間來表示根本不存在的邊
2.鄰接表
由圖中每個頂點的相鄰頂點列表所組成
3.關聯矩陣
矩陣的行表示頂點,列表示邊,頂點v是邊e的入射點
array[v][e] === 1,array[v][e] === 0
5.創建Graph類
class Graph{
constructor(isDirected = false){
this.isDirected = isDirected;
this.vertices = [];
this.adjList = new Dictionnary();
}
}
接受一個參數來判斷圖是否有向,默認情況下圖是無向的
使用一個數組來存儲圖中所有頂點的名字,以及一個字典來存儲鄰接表,頂點爲鍵,鄰接點列表爲值
添加一個新的頂點
addVertex(v){
if (!this.vertices.includes(v)){
this.vertices.push(v);
this.adjList.set(v, []);
}
}
添加頂點之間的邊
addEdge(v, w){
if (!this.adjList.get(v)){
this.addVertex(v);
}
if (!this.adjList.get(w)){
this.addVertex(w);
}
this.adjList.get(v).push(w);
if (!this.isDirected){
this.adjList.get(w).push(v);
}
}
6.圖的遍歷
兩種算法:廣度優先搜索/深度優先搜索
可以尋找特定的點或尋找兩個頂點之間的路徑,檢查圖是否連通,檢查圖是否含有環
思想:必須追蹤每個第一次訪問的節點,並且追蹤有哪些節點還沒有被完全探索,需要指定第一個被訪問的節點
完全搜索一個頂點要求我們查看該頂點的每一條邊,對於連接的未被訪問的標註被發現,加入待訪問頂點列表
爲了保證算法的效率,務必訪問每個頂點至多兩次,連通圖中每條邊和頂點都會被訪問到
1.廣度優先搜索:隊列 存入隊列,最先入隊的頂點先被探索
先寬後深的訪問頂點
1.創建一個隊列Q
2.標註v爲被發現的,並將v入隊列Q
3.如果Q非空,則運行以下的步驟
1.將u從Q出隊列
2.標註u被發現
3.將u所有未被訪問過的鄰點入隊列
4.標註u爲已被探索的
2.深度優先搜索:棧 存入棧,沿路徑被探索,存在新的相鄰頂點就去訪問
先深後廣的訪問頂點
1.標註v爲被發現的
2.對於v的所有未訪問的鄰點w,訪問頂點w
3.標註v爲已被探索的
3.白:還沒被訪問 灰:被訪問過,並未被探索過 黑:被訪問且被完全探索過
7.最短路徑算法
1.Dijkstra算法
計算一個從單點源到所有其他源的最短路徑的貪心算法
2.Floyd-Warshall算法
計算圖中所有最短路徑的動態規劃法
8.最小生成樹
最低成本實現橋樑互通-使用MST算法來解決
1.Prim算法
2.Kruskal算法
##排序和搜索算法
1.冒泡排序
比較所有相鄰的兩個項
function bubbleSort(array, compareFn = defaultCompare){
const {length} = array;
for (let i = 0;i < length;i++){
for (let j = 0;j < length - 1 - i;j++){
if (compareFn(array[j], array[j+1]) === Compare.BIGGER_THAN){
swap(array, j, j+1);
}
}
}
return array;
}
function swap(array, a, b){
[array[a], array[b]] = [array[b], array[a]];
}
算法複雜度:O(n^2)
2.選擇排序
找到最小值並將其放在第一位,依次類推
function selectionSort(array, compareFn = defaultCompare){
const {length} = array;
let indexMin;
for (let i = 0;i < length - 1;i++){
indexMin = i;
for (let j = i;j < length;j++){
if (compareFn(array[indexMin], array[j]) === Compare.BIGGER_THAN){
indexMin = j
}
}
if (i != indexMin){
swap(array, i, indexMin);
}
}
return array;
}
算法複雜度:O(n^2)
3.插入排序
每次拍一個數組項,假定第一項已經排好了,接着,與第二項進行比較
function insertionSort(array, compareFn = defaultCompare){
const {length} = array;
let temp;
for (let i = 1;i < length;i++){
let j = i;
temp = array[i];
while (j > 0 && compareFn(array[j-1], temp) === Compare.BIGGER_THAN){
array[j] = array[j - 1];
j–;
}
array[j] = temp;
}
return array;
}
4.歸併排序
將原始數組分成較小的數組,直到每個小數組只有一個位置,接着將小數組歸併成大數組
function mergeSort(array, compareFn = defaultCompare){
if (array.length > 1){
const {length} = array;
const middle = Math.floor(length / 2);
const left = mergeSort(array.slice(0, middle), compareFn);
const right = mergeSort(array.slice(middle, length), compareFn);
array = merge(left, right, compareFn);
}
return array;
}
function merge(left, right, compareFn){
let i = 0;
let j = 0;
const result = [];
while (i < left.length && j < right.length){
result.push(
compareFn(left[i], right[j]) === Compare.LESS_THAN ? left[i++] : right[j++]
);
}
return result.concat(i < left.length ? left.slice(i) : right.slice(j));
}
算法複雜度:O(n*log(n))
5.快排
1.首先,在數組中選擇一個值作爲主元,也就是數組中間的那個值
2.創建兩個指針引用,左邊一個指向數組的第一個值,右邊一個指向數組的最後一個值
移動左指針找到一個比主元大的值,移動右指針找到一個比主元小的值,然後交換
重複這個過程,直到左指針超過右指針,這一步叫做劃分操作
3.接着對劃分後的小數組重複之前的步驟,最後數組完全排序
function quickSort(array, compareFn = defaultCompare){
return quick(array, 0, arraty.length - 1, compareFn);
}
function quick(array, left, right, compareFn){
let index;
if (array.length > 1){
index = partition(array, left, right, compareFn);
if (left < index - 1){
quick(array, left, index - 1, compareFn);
}
if (index < right){
quick(array, index, right, compareFn);
}
}
return array;
}
function partition(array, left, right, compareFn){
const pivot = array[Math.floor((right + left) / 2)];
let i = left;
let j = right;
while (i <= j){
while (compareFn(array[i], pivot) === Compare.LESS_THAN){
i++;
}
while (compareFn(array[i], pivot) === Compare.GIGGER_THAN){
j–;
}
if (i <= j){
swap(array, i, j);
i++;
j–;
}
}
return i;
}
算法複雜度:O(nlog(n))
##算法設計與技巧
1.分而治之

##算法複雜度
衡量算法的效率:資源:CPU佔用、內存佔用、硬盤佔用、網絡佔用
1.O表示法:一般考慮的是CPU佔用,也就是時間佔用
1.O(1):和參數無關,也就是說函數複雜度爲常數
2.O(n):和參數有關,存在最好和最壞,n是輸入的數組的大小
3.O(n^2):雙層嵌套循環
2.NP完全理論概述
一般來說一個算法的複雜度爲O(n^k),k爲常數,則認爲這個算法是高效得
對於給定的問題,如果存在多項式算法,則計爲P多項式
NP非確定性多項式算法,一個問題可以在多項式時間內驗證是否正確,則計爲NP
P都是NP,P=NP?
NP完全問題,滿足以下條件,則稱決策問題L是NP完全的
1.L是NP問題,可以在多項式時間內驗證,但還沒找到多項式算法
2.所有NP問題都能在多項式時間內歸納爲L
不可解問題與啓發式算法
有些問題是無解的,但是可以在符合要求的時間內找到一個近似解,這就是啓發式算法
未必是最優解,足夠解決問題
通過對算法的學習,可以提高我們解決問題的能力

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章