淺析常見的算法範式

做者:Aral Roca

翻譯:瘋狂的技術宅javascript

原文:https://dev.to/christinamcmah...前端

未經容許嚴禁轉載java

首先明確三個概念:程序員

算法: 按步驟解決問題的過程。面試

範式: 思考問題的模式。算法

算法範式: 爲問題構建高效解決方案的常規方法。segmentfault

本文討論一些經常使用的算法範式,例如服務器

  • 分治算法
  • 動態規劃
  • 貪婪算法
  • 回溯算法

分治法

在排序算法中,合併和快速排序這兩種算法的共同點就是分而治之的算法。微信

分而治之是一種常見的算法設計,它的思路是把問題分解爲與原始問題類似的較小子問題。一般以遞歸方式解決子問題,並結合子問題的解決方案來解決原始問題。多線程

分治法的邏輯能夠分爲三個步驟:

  1. 將原始問題劃分爲較小的子問題。
  2. 經過遞歸解決子問題,解決完畢以後返回子問題的解決方案。
  3. 將子問題的解決方案合併爲原始問題的解決方案。

分治法的例子:二叉搜索

下面是用分治實現的二叉搜索。

function binarySearchRecursive(array, value, low, high) {
    if (low <= high) {
        const mid = Math.floor((low + high) / 2);
        const element = array[mid];

        if (element < value) {
            return binarySearchRecursive(array, value, mid + 1, high);
        } else if (element > value) {
            return binarySearchRecursive(array, value, low, mid - 1);
        } else {
            return mid;
        }
    }
    return null;
}

export function binarySearch(array, value) {
    const sortedArray = quickSort(array);
    const low = 0;
    const high = sortedArray.length - 1;

    return binarySearchRecursive(array, value, low, high);
}

請注意,上面的 binarySearch 函數是供他人調用的,而 binarySearchRecursive 是實現分治法的地方。

動態規劃法

動態規劃是一種優化技術,用於經過把複雜問題分解爲較小的子問題來解決。看上去很像是分治法,但動態規劃不是把問題分解爲獨立的子問題而後再組合在一塊兒,而是隻把問題分解爲獨立的子問題。

算法邏輯分爲三個步驟:

  1. 定義子問題。
  2. 重複解決子問題。
  3. 識別並解決基本問題。

動態規劃案例:最小硬幣找零問題

這是一個名爲爲硬幣找零問題的常見面試題。硬幣找零問題是給定找零的金額,找出能夠用多少特定數量的硬幣來找零的方式。最小硬幣找零問題只是找到使用給定面額的錢所需的最少硬幣數量。例如,若是須要找零 3 毛 7 分,則能夠使用 1 個 2 分,1個 5 分,1 個 1 毛錢和1個 2 毛錢。

function minCoinChange(coins, amount) {
    const cache = [];
    const makeChange = (value) => {
        if (!value) {
            return [];
        }
        if (cache[value]) {
            return cache[value];
        }
        let min = [];
        let newMin;
        let newAmount;
        for (let i = 0; i < coins.length; i++) {
            const coin = coins[i];
            newAmount = value - coin;
            if (newAmount >= 0) {
                newMin = makeChange(newAmount);
            }
            if (newAmount >= 0 && 
            (newMin.length < min.length - 1 || !min.length) && (newMin.length || !newAmount)) {
                min = [coin].concat(newMin);
            }
        }
        return (cache[value] = min);
    }
    return makeChange(amount);
}

在上面的代碼中,參數 coins 表示面額(在人民幣中爲 [1, 2, 5, 10, 20, 50])。爲了防止重複計算,用到了一個 cachemakeChange 函數是遞歸實現的,它是一個內部函數,能夠訪問 cache

console.log(minCoinChange([1, 2, 5 10, 20], 37)); // => [2, 5, 10, 20]
console.log(minCoinChange([1, 3, 4], 6)) // => [3, 3]

貪心算法

貪心算法與當前的最優解決方案相關,並試圖找到一個全局的最佳方案。與動態規劃不一樣,它不考慮全局。貪心算法傾向於簡單直觀,但可能不是總體最優的解決方案。

貪心算法案例:最小硬幣找零問題

上面用動態規劃解決的硬幣問題也能夠用貪心算法解決。這個解決方案的是否能獲得最優解取決於所採用的面額。

function minCoinChange(coins, amount) {
    const change = [];
    let total = 0;
    for (let i = coins.length; i>= 0; i--) {
        const coin = coins[i];
        while (total + coin <= amount) {
            change.push(coin);
            total += coin;
        }
    }
    return change;
}

能夠看到,貪心算法比動態規劃的方案要簡單得多。下面看一下一樣的求解案例,來了解二者之間的區別:

console.log(minCoinChange([1, 2, 5 10, 20], 37)); // => [2, 5, 10, 20]
console.log(minCoinChange([1, 3, 4], 6)) // => [4, 1, 1]

貪心算法給出了第一個問題的最優解,但第二個並非最優解(應該是 [3,3])。

貪心算法比動態規劃算法要簡單並且更快,可是獲得的有可能不是最優解。

回溯算法

回溯算法很是適合逐步查找和構建解決方案。

  1. 嘗試以一種方式解決問題。
  2. 若是它不起做用,就回溯並重復步驟 1,直到找到合適的解決方案爲止。

對於回溯算法,我會另寫一篇文章來介紹更復雜的算法。究竟寫什麼我還沒想好,也許是寫一個對數獨求解的程序,若是你對這個感興趣,請關注個人公衆號!

算法是永無止境的,但願本文能幫你瞭解一些常見的算法範式。

173382ede7319973.gif


本文首發微信公衆號:前端先鋒

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

歡迎繼續閱讀本專欄其它高贊文章:


相關文章
相關標籤/搜索