基本算法思想:递归+分治+动态规划+贪心+回溯+分支限界

17

作者:心叶
时间:2018-05-01 19:28

本文对应github地址:https://github.com/yelloxing/...

以上实现了常见算法的java、c语言、javascrpt(或node.js)、python3和go语言实现,持续更新中。

下面针对一些基本的算法思想,给出大致的说明和用例。

递归与分治策略

分治法的基本思想

把一个规模为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]返回为最终结果。

【代码实现】

见下面评论对应代码

动态规划

基本思想

和分治法基本思想有共同的地方,不同的是子问题往往不是独立的,有事母问题要借助子问题的解来判断,因此把已经计算好的问题记录在表格中,后续如果需要查询一下,可以避免重复计算,这是动态规划的基本思想。

不过动态规划具体实现起来多种多样,不过都具有相同的填表格式,通常按照下面步骤设计算法:
1)找出最优解的性质,并刻画其结构特征;
2)递归的定义最优值;
3)以自底向上的方式计算出最优值;
4)通过计算最优值时刻意记录的判断结果来构造最优解。

可以使用该算法思想设计算法的问题一般会具有二个决定性的性质:
1)最优子结构性质;
2)子问题重叠性质。

备忘录算法

和上面的算法思想差不多,不同的是备忘录为每个解过的子问题建立备忘录以备需要的时候查看,避免了相同的问题计算多次。

一般来说,当一个问题的所有子问题都至少要解一次时,用动态规划比备忘录要好,因为不会有任务暂存且没有多余的计算;当子问题空间中部分问题不必解时,用备忘录比较好。

不过上面不是绝对的,这样说只是想区别一下二个思想的不同,具体的时候还是要根据业务场景来在保证可行的前提下选择更好的方法。

算法使用例子

【题目】

给定n个矩形{A1,A2,...,An},其中Ai与Ai+1是可乘的,由于矩阵满足结合律,不同的加括号方法计算次数不一样,求最优的加括号方法。

【思路】

分别计算有1,2,3,...,n个矩阵的最优解,计算i个时候,全部的i-1的最优解已经记录下来了,保证计算不重复。

【代码实现】

见下面评论对应代码

贪心算法

基本思想

算法思想很简单,和字面意思一样,每次都选择对自己最有利的,不过这是有条件的,只有在满足条件下每次选择最有利自己的才可以获取最优解。

贪心选择性质和最优子结构性质是该思想最重要的性质:
1)贪心选择性质:所求问题的整体最优解可以通过一系列局部最优的选择达到。
2)最优子结构性质:当一个问题的最优解包含其子问题的最优解时,称此问题具有此性质。

算法使用例子

【题目】

有一批集装箱要装上一艘载重为c的轮船,其中集装箱i的重量为wi,要求在装货体积不受限制的条件下尽力多装集装箱的解。

【思路】

先排序,然后选择从最轻的开始装货物。

【代码实现】

这里就不提供具体代码了,因为感觉没有什么意义,最重要的是要先确定问题满足贪心选择性质,这样在很多时候,可以更容易的解决问题,这点很重要。

回溯法

基本思想

说的直白点就是深度优先方式系统搜索问题的算法。

算法使用例子

【题目】

有一批共n个集装箱要装上两艘载重方别为c1和c2的轮船上,其中集装箱i的重量为wi,且全部集装箱重量不大于两艘载重之和,问是否有一个装载方案完成装载。

【思路】

对第一艘船,构造一个0/1树,0代表不选择,1代表选择,然后分别去从根节点试图爬到叶节点,去一一记录下来可行的,选择最小的为解,余下的判断第二艘船是否装的下即可。

【代码实现】

见下面评论对应代码

分支限界

基本思想

对比回溯法就很容易思考,用广度优先的办法,不断扩大当前节点的孩子为当前节点,主要是求解一个最优解,算法相比回溯法要简单些。

算法使用例子

【题目】

有一批共n个集装箱要装上两艘载重方别为c1和c2的轮船上,其中集装箱i的重量为wi,且全部集装箱重量不大于两艘载重之和,问是否有一个装载方案完成装载。

【思路】

借助队列,一层层来检查,找到最优解。

【代码实现】

见下面评论对应代码

你可能感兴趣的

心叶 作者 · 2018年05月01日

【代码一:递归与分治策略】

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__);

+1 回复

心叶 作者 · 2018年05月01日

【代码四:分支限界】

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("失败");
}

+1 回复

心叶 作者 · 2018年05月01日

【代码二:动态规划】

/**
* 初始化数据
*/
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);

回复

心叶 作者 · 2018年05月01日

【代码三:回溯法】

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();

回复

载入中...