1. 程式人生 > 實用技巧 >前端總結資料結構與演算法基礎

前端總結資料結構與演算法基礎

翻書問題或者走臺階問題。

問:共有n個臺階,每次只能上1個臺階或者2個臺階,共有多少種方法爬完臺階?共有n頁書,每次只能翻1頁或者2頁書,共有多少種方法翻完全書?
ps:本質上是斐波那契數列問題。假設只有一個臺階,則只有一種跳法,f(1)=1;如果兩個臺階,那麼有兩種跳法:1,一次跳一級,2,一次跳兩級,f(2) = 2。如果大於2的n級臺階,那麼一次跳一級臺階,剩下還有n-1級臺階,有f(n-1)種跳法。假如一次跳2級臺階,剩下n-2級臺階,有f(n-2)中跳法。這就表示f(n) = f(n-1)+f(n-2)。

function fibonacci(n) {
    if (n === 1 || n === 2) {
        
return n; } else { return fibonacci(n-1) + fibonacci(n-2) } } // 一個記憶化的斐波那契數列 let tem = [0, 1] function fibonacci(n) { let result = tem[n] if(typeof result !== 'number') { result = fibonacci(n-1)+fibonacci(n-2) tem[n] = result // 將每次 fibonacci(n) 的值都快取下來 } return
result } // 動態規劃:從底部開始解決問題, 將所有小問題解決掉, 然後合併成一個整體解決方案, 從而解決掉整個大問題 。 function fibonacci(n) { let last = 1; let nextLast = 1; let result = 1; for (let i = 2; i < n; ++i) { result = last + nextLast; nextLast = last; last = result; } return result; }

二分查詢。

陣列array包含了順序的元素,[1,2,3,...,10],查詢目標元素t是否在陣列中。
我們已經提前知道陣列是順序排列的,比如遞增順序。
時間複雜度為O(logN)
遞推公式:
f(N) = f(N/2) + O(1) = f(N/4) + 2 * O(1)
假設 N = 2 ^ M
最後可以推出
f(N) = O(logN)


let list = [1,2,3,4,5,6,7,8,9,10]
function binarySearch(array, t) {
    let left = 0, right = array.length - 1;
    while(left <= right) {
        let mid = parseInt((right + left)/2)
        if(array[mid] < t) {
            left = mid + 1
        } else if(array[mid] > t) {
            right = mid - 1
        } else {
            return true
        }
    }
    return false
}
// 遞迴寫法
function binarySearch2(array, t, left, right) {
    if(left > right) return -1
    let mid = parseInt((right - left)/2) + left
    if(array[mid] === t) {
        return mid
    }else if(array[mid] < t){
        return binarySearch2(array, t, mid + 1, right)
    }else if(array[mid] > t){
        return binarySearch2(array, t, left, mid - 1)
    }
}
let test1 = binarySearch(list, 5)
let test2 = binarySearch2(list, 5, 0, list.length-1)
console.log(test1, test2)

貪心演算法--最少硬幣找零問題

所謂貪心,就是先選擇當前階段的最優解,不去考慮這次的選擇會不會對未來造成影響,想要通過每個階段的區域性最優解,達到全域性最優解。

假設你為一家自動售貨機廠家程式設計序,自動售貨機要每次找給顧客最少數量硬幣,美國有以下面額的硬幣:1美分、5美分、10美分、25美分。比如說要找36美分的零錢,我們可以用1個25美分、1個10美分和一個1美分。
(ps:找零問題,先找大額的硬幣25分,依次遞減)


function minCoinChange(amount, coins) {
    let minCoin = amount
    let count = 0
    for(let i = 0, l = coins.length; i < l; i++) {
        if (coins[i] <= minCoin) {
            count += Math.floor(minCoin / coins[i])
            console.log(coins[i] + '*' + Math.floor(minCoin / coins[i]))
            minCoin = minCoin % coins[i]
        }
    }
    return count
}
let counts = minCoinChange(61, [25, 10, 5, 1])
console.log(counts)

動態規劃--01揹包問題

function main(volume, value, c){
    let tArray = []
    let useGoodsNo = []
    for(let i = 0, l = volume.length; i <= l; i++) {
        tArray[i] = []
        useGoodsNo[i] = 0
        for(let j = 0; j <= c; j++) {
            if(i == 0 || j == 0){
                tArray[i][j] = 0
            }
        }
    }

    volume.unshift(0)  //讓i = 1的時候對應的是1號物品
    value.unshift(0)
    
    for(let i = 1, l = volume.length; i < l; i++) {  // i從1開始 tArray[0][j]已經填滿
        for(let j = 1; j <= c; j++) {
            if(j < volume[i]){
                tArray[i][j] = tArray[i-1][j]
            } else {
                //如果裝了剩下的體積
                let leftVolume = j - volume[i]
                // 當前物品的價值
                let curValue = value[i]
                // 剩下的體積的價值是 (tArray[i - 1]因為0 1揹包,不能重複放同個物品)
                let leftValue = tArray[i - 1][leftVolume]

                tArray[i][j] = Math.max(tArray[i-1][j], curValue+leftValue)
            }
        }
    }
    // 填滿表格
    console.table(tArray)
    // 逆向獲取物品
    let C = c
    for(let i = value.length-1; i > 0; i--){
        if(tArray[i][C] > tArray[i-1][C]){
            useGoodsNo[i] = 1
            C = C - volume[i]
        }
    }
    console.table(useGoodsNo)  // 所選的物品
    console.log(tArray[value.length-1][c])  // 最大價值
}
main([2,3,4,5], [3,4,5,6], 8)

展平陣列

// 展平陣列 [[1, 2], 3, [[[4], 5]]] => [1, 2, 3, 4, 5]
function flatten(arr) {
    return [].concat(
        ...arr.map(x => Array.isArray(x) ? flatten(x) : x)
    )
}

隨機打亂陣列

function shuffle(arr) {
    for(let i = 0; i < arr.length-1; i++){
        const j = i + Math.floor(Math.random() * (arr.length - i))
        [arr[i], arr[j]] = [arr[j], arr[i]]
    }
}

函式節流

// 函式節流
function throttle(func, delay = 60) {
    let lock = false
    return (...args) => {
        if(lock) return
        func(...args)
        lock = true
        setTimeout(()=> {
            lock = false
        }, delay)
    }
}

柯里化

//對於curry(foo),g函式引數足夠4個,就呼叫foo(a,b,c,d),如果小於4就返回一個可以繼續積累引數的函式
const curry = func => {
    const g = (...allArgs) => allArgs.length >= func.length ?
    func(...allArgs) : (...args) => g(...allArgs, ...args)
    return g
} 
const foo = curry((a, b, c, d) =>{
    console.log(a, b, c, d)
})
foo(1)(2)(3)(4) // 1 2 3 4
foo(1)(2)(3)  // 不返回
const f = foo(1)(2)(3)
f(4) // 1 2 3 4 5

Y組合子

const y = function(le) {
    return function (f) {
        return f(f)
    }(function (f) {
        return le(
            function(...x) {
                return (f(f))(...x)
            }
        )
    })
}
const curryY = func => y(g => {
        (...allArgs) => {
            allArgs.length >= func.length ? func(...allArgs) : (...args) =>g(...allArgs, ...args)
        }
    }
)
const foo = curryY((a, b, c, d) => {
    console.log(a, b, c, d)
})
foo(1)(2)(3)(4)

連結串列

連結串列中最簡單的一種是單向連結串列,它包含兩個域,一個資訊域和一個指標域。這個連結指向列表中的下一個節點,而最後一個節點則指向一個空值。

function ListNode(val) {
    this.val = val
    this.next = null
}
let node1 = new ListNode(1)
let node2 = new ListNode(2)
let node3 = new ListNode(3)
node1.next = node2
node2.next = node3
console.log(node1)

function recursiveTraverse(head) {
    if(head != null) {
        console.log(head.val)
        recursiveTraverse(head.next)
    }
}
recursiveTraverse(node1)

// 翻轉一條單向連結串列
// 輸入 1 -> 2 -> 3 -> null
// 輸出 3 -> 2 -> 1 -> null
function reverseLinkedList(head){
    let dummy = head
    let tem = dummy
    while(head != null && head.next != null){
        dummy = head.next
        head.next = dummy.next
        dummy.next = tem
        tem = dummy
    }
    return dummy
}
let reverseLink = reverseLinkedList(node1)
console.log(reverseLink)

二叉樹

function TreeNode(val) {
    this.val = val
    this.left = null
    this.right = null
}
let root = new TreeNode(2)
root.left = new TreeNode(1)
root.right = new TreeNode(3)
console.log(root)
/*
*     2
*   /   \
*  1     3
*/

/*
    中序遍歷:(
        定義:
        1,中序遍歷左子樹
        2,遍歷根節點
        3,中序遍歷右子樹
    )
    前序遍歷:(
        定義:
          1,遍歷根節點
          2,前序遍歷左子樹
          3,前序遍歷右子樹
    )
    後序遍歷:(
        定義:
          1,後序遍歷左子樹
          2,後序遍歷右子樹
          3,遍歷根節點
    )
*/

function inOrder(root) {
    if(root) {
        console.log('前',root.val)  // 2 1 3
        inOrder(root.left)
        console.log('中',root.val)  // 1 2 3
        inOrder(root.right)
        console.log('後',root.val)  //  1 3 2
    }
}
inOrder(root)

/*
    二叉查詢樹/二叉搜尋樹:(
        定義:
        左子樹的所有節點的值小於根節點
        右子樹的所有節點的值大於根節點
        左子樹和右子樹都是二叉查詢樹
    )
    二叉搜尋樹的中序遍歷就是排好序的元素。
*/
function binarySearchTreeFind(root, target) {
    if(!root) return false
    if(root.val === target) {
        return true
    } else if(root.val < target){
        return binarySearchTreeFind(root.right, target)
    } else if(root.val > target) {
        return binarySearchTreeFind(root.left, target)
    }
}
console.log(binarySearchTreeFind(root, 1))

First In Last Out(FILO)
先進後出,後進先出

function Stack(){
    this.stack = []

    this.isEmpth = function(){
        return this.size() === 0
    }
    this.size = function() {
        return this.stack.length
    }
    // 出棧
    this.pop = function(){
        if(this.isEmpth()){
            return null
        } else {
            return this.stack.pop()
        }
    }
    // 進棧
    this.push = function (val){
        return this.stack.push(val)
    }
        // 返回棧頂元素
    this.peak= function(){
        if(this.isEmpth()) {
            return null
        } else {
            return this.stack[this.stack.length - 1]
        }
    }
}

let test = new Stack()
test.push(1)
console.log(test.peak())
console.log(test.isEmpth())
test.pop()
console.log(test.peak())

佇列

First In First Out(FIFO)
先進先出

function Queue() {
    this.queue = []

    this.size = function() {
        return this.queue.length
    }
    this.isEmpty = function(){
        return this.size() === 0
    }
    // 入佇列
    this.enqueue = function (val){
        this.queue.unshift(val)
    } 
    // 出佇列
    this.dequeue = function(){
        if(this.isEmpty()){
            return null
        } else {
            return this.queue.pop()
        }
    }
}
let test = new Queue()
test.enqueue(1)
test.enqueue(2)
console.log(test.queue)
test.dequeue()
console.log(test.queue)