前端經常使用的算法思想理解和演示

遞歸與分治策略

把一個規模爲n的問題分解爲k個規模較小的子問題,這些子問題相互獨立且與原問題相同,遞歸的解這些子問題,而後把各個子問題的解合併獲得原問題的解。算法

【題目】

使用快速排序方法排列一個一維數組。數組

【思路】

對於輸入的子數組a[p:r],按照一下3個步驟進行排序:
1)分解divide:以a[p]爲基準元素將a[p:r]劃分紅3段a[p:q-1],a[q]和a[q+1:r],其中a[q]不小於a[p:q-1]中的任何元素且不大於a[q+1:r]中的任何元素,下標q在劃分中肯定。
2)遞歸求解conquer:經過遞歸調用排序,分別對a[p:q-1]和a[q+1:r]進行排序。
3)合併merge:合併a[p:q-1],a[q]和a[q+1:r]返回爲最終結果。ide

【代碼實現】

var __array__ = [1, 3, 2, 4, 5, 57, 6, 46, 4, 6, 45];

console.log("排序前:" + __array__);

function sort(x, y) {
    if (x < y) {
        var p = partition(x, y);
        sort(x, p - 1);
        sort(p + 1, y);
    }
}

function partition(p, q) {
    var x = p;
    var y = q;
    var r = p;
    var flag = __array__[r];
    while (true) {
        while (__array__[x] <= flag && x < y) {
            x++;
        }
        while (__array__[y] > flag && y > x) {
            y--;
        }
        if (x >= y) {
            break;
        }
        var temp = __array__[x];
        __array__[x] = __array__[y];
        __array__[y] = temp;
    }
    if (__array__[x] > flag) {
        x--;
    }
    __array__[p] = __array__[x];
    __array__[x] = flag;
    return x;
}

sort(0, __array__.length - 1);

console.log("排序後:" + __array__);

動態規劃

和分治法基本思想有共同的地方,不一樣的是子問題每每不是獨立的,有事母問題要藉助子問題的解來判斷,所以把已經計算好的問題記錄在表格中,後續若是須要查詢一下,能夠避免重複計算,這是動態規劃的基本思想。設計

不過動態規劃具體實現起來多種多樣,不過都具備相同的填表格式,一般按照下面步驟設計算法:code

1)找出最優解的性質,並刻畫其結構特徵;排序

2)遞歸的定義最優值;遞歸

3)以自底向上的方式計算出最優值;隊列

4)經過計算最優值時刻意記錄的判斷結果來構造最優解。it

可使用該算法思想設計算法的問題通常會具備二個決定性的性質:io

1)最優子結構性質;

2)子問題重疊性質。

備忘錄算法

和上面的算法思想差很少,不一樣的是備忘錄爲每一個解過的子問題創建備忘錄以備須要的時候查看,避免了相同的問題計算屢次。

通常來講,當一個問題的全部子問題都至少要解一次時,用動態規劃比備忘錄要好,由於不會有任務暫存且沒有多餘的計算;當子問題空間中部分問題沒必要解時,用備忘錄比較好。

不過上面不是絕對的,這樣說只是想區別一下二個思想的不一樣,具體的時候仍是要根據業務場景來在保證可行的前提下選擇更好的方法。

【題目】

給定n個矩形{A1,A2,...,An},其中Ai與Ai+1是可乘的,因爲矩陣知足結合律,不一樣的加括號方法計算次數不同,求最優的加括號方法。

【思路】

分別計算有1,2,3,...,n個矩陣的最優解,計算i個時候,所有的i-1的最優解已經記錄下來了,保證計算不重複。

【代碼實現】

/**
* 初始化數據
*/
var P = [30, 35, 15, 5, 10, 20, 25]; //記錄了矩陣的大小
var num = P.length - 1; //矩陣個數
var minNum = [];
var i, j; //全局複雜循環變量

/**
* 初始化數據
*/
for (i = 0; i < num; i++) {
    minNum[i] = [];
    for (j = 0; j < num; j++) {
        if (i == j) {
            minNum[i][j] = 0;
        } else {
            minNum[i][j] = "#";
        }
    }
}
/**
* 計算最優並記錄下來
*/
for(i=2;i<=num;i++){//計算的矩陣個數,從二個開始到所有的狀況
    for(j=1;j<=num+1-i;j++){//計算矩陣第j到第i+j-1個的狀況
        //先初始化認爲在第j分割是最優的(在第j分割的意思是j單獨一個,j+1->i+j-1是一組)
        var splitIndex=j;
        var splitMin=minNum[j][i+j-2]+P[j-1]*P[j]*P[i+j-1];
        minNum[j-1][i+j-2]=splitMin;
        for(splitIndex=j+1;splitIndex<=i+j-2;splitIndex++){
            splitMin=minNum[j-1][splitIndex-1]+minNum[splitIndex][i+j-2]+P[j-1]*P[splitIndex]*P[i+j-1];
            if(splitMin<minNum[j-1][i+j-2]){
                minNum[j-1][i+j-2]=splitMin;
            }
        }
    }
}

console.log("最優次數:");
console.log(minNum);

貪心算法

算法思想很簡單,和字面意思同樣,每次都選擇對本身最有利的,不過這是有條件的,只有在知足條件下每次選擇最有利本身的才能夠獲取最優解。

貪心選擇性質和最優子結構性質是該思想最重要的性質:

1)貪心選擇性質:所求問題的總體最優解能夠經過一系列局部最優的選擇達到。

2)最優子結構性質:當一個問題的最優解包含其子問題的最優解時,稱此問題具備此性質。

【題目】

有一批集裝箱要裝上一艘載重爲c的輪船,其中集裝箱i的重量爲wi,要求在裝貨體積不受限制的條件下盡力多裝集裝箱的解。

【思路】

先排序,而後選擇從最輕的開始裝貨物。

【代碼實現】

這裏就不提供具體代碼了,由於感受沒有什麼意義,最重要的是要先肯定問題知足貪心選擇性質,這樣在不少時候,能夠更容易的解決問題,這點很重要。

回溯法

說的直白點就是深度優先方式系統搜索問題的算法。

算法使用例子

【題目】

有一批共n個集裝箱要裝上兩艘載重方別爲c1和c2的輪船上,其中集裝箱i的重量爲wi,且所有集裝箱重量不大於兩艘載重之和,問是否有一個裝載方案完成裝載。

【思路】

對第一艘船,構造一個0/1樹,0表明不選擇,1表明選擇,而後分別去從根節點試圖爬到葉節點,去一一記錄下來可行的,選擇最小的爲解,餘下的判斷第二艘船是否裝的下便可。

【代碼實現】

var weight1 = 30; //第一艘船載重
var weight2 = 10; //第二艘船載重
var w = [1, 9, 9, 4, 4, 9]; //集裝箱

var nowW1 = 0; //當前載重
var nowBest1 = 0; //當前最優裝載
var n = w.length; //集裝箱個數

function Loading(deep) {
    if (deep > n) { //若是到達根
        if (nowW1 > nowBest1)
            nowBest1 = nowW1;
        return;
    }
    if (nowW1 + w[deep - 1] <= weight1) { //若是1分支能夠
        nowW1 += w[deep - 1];
        Loading(deep + 1);
        nowW1 -= w[deep - 1];
    }
    //0分支
    Loading(deep + 1);
}

function main() {
    Loading(1);
    var firstLoad = nowBest1;
    var all = 0;
    for (var i = 0; i < n; i++) {
        all += w[i];
    }
    console.log("第一艘載重:" + firstLoad + "n");
    if (all > weight2 + firstLoad) {
        console.log("失敗n");
    } else {
        console.log("成功n");
    }
}

main();

分支限界

對比回溯法就很容易思考,用廣度優先的辦法,不斷擴大當前節點的孩子爲當前節點,主要是求解一個最優解,算法相比回溯法要簡單些。

【題目】

有一批共n個集裝箱要裝上兩艘載重方別爲c1和c2的輪船上,其中集裝箱i的重量爲wi,且所有集裝箱重量不大於兩艘載重之和,問是否有一個裝載方案完成裝載。

【思路】

藉助隊列,一層層來檢查,找到最優解。

【代碼實現】

var weight1 = 30; //第一艘船載重
var weight2 = 10; //第二艘船載重
var w = [1, 9, 9, 4, 4, 9]; //集裝箱

var nowBest1 = 0; //當前最優裝載
var n = w.length; //集裝箱個數

var arrayFIFO = [];

arrayFIFO.push([1, 1]); //deep,此時已經載重
arrayFIFO.push([1, 0]);

var nowBest1 = 1;

while (arrayFIFO.length > 0) {
    var nowNode= arrayFIFO.shift();
    currentDeep = nowNode[0];
    currentWeight = nowNode[1];
    if (currentDeep >= n) {
        if (currentWeight > nowBest1) {
            nowBest1 = currentWeight;
        }
    } else {
        arrayFIFO.push([currentDeep + 1, currentWeight]);
        if (currentWeight + w[currentDeep] < weight1) {
            arrayFIFO.push([currentDeep + 1, currentWeight + w[currentDeep]]);
        }
    }
}
allW = 0;
for (val = 0; val < w.length; val++) {
    allW += w[val];
}
console.log("第一艘船載重:" + nowBest1);

if (allW <= nowBest1 + weight2) {
    console.log("成功");
} else {
    console.log("失敗");
}
相關文章
相關標籤/搜索