DFS + 剪枝策略

(0)目录

剪枝算法(算法优化)

回溯算法 和 贪心算法(全排列)

记忆化搜索(搜索+dp思想)

动态规划 之 最长上升序列

动态规划 之 背包系列

动态规划 之 dp + 图搜索

动态规划 之 最长公共子序列(非连续)

静态字典树(模拟动态的)

字典树Trie 之 基础模板(插入,查找,删除)

优先队列 之 堆排序实现(堆排序思想)

哈希(hash) 之 hash的插入和查找(链地址法)

哈希(hash) 之 ELFHash 和 静态hash(模拟链接法)

关于C++ const 的全面总结(个人感觉一般般)

c++ string 之 find_first_not_of 源代码

c++类型所占的字节和表示范围

ava 异常 之 实战篇(trows 和 try catch Dead Code)

java Map 之 排序(key,value)

一:简介

(1)相信做过ACM的人,都很熟悉图和树的深度优先搜索;算法里面有蛮力法 —— 就是暴力搜索(不加任何剪枝的搜索);

(2)蛮力搜搜需要优化时,就是需要不停的剪枝,提前减少不必要的搜索路径,提前发现判断的过滤条件;

(3)剪枝的核心问题就是设计剪枝判断方法,哪些搜索路径应当舍弃,哪些搜索路径不能舍弃(保留);

(4)高效的剪枝过滤条件需要从局部和全局来考虑问题,发现内在的规律。

(5)详细的剪枝算法,请见剪枝算法(算法优化)

二:示例验证

(1)题目来源于 poj 1011 Sticks  DFS + 剪枝

      题目大意:给出一些长度不大于 50 的木棍, 要求你把这些小木棍拼成,长度相同木棍,当然长度越小越好。

(2)解题思路

  1.   首先 Sum一定要能被 L 整除。 
        2.   L 一定 大于等于 题目给出的最长的木棍的长度 Max。由上述两点,我们想到,可以从 Max 开始递增地枚举 L, 直到成功地拼出 Sum/L 支长度为 L 的                  木棍。
        搜索种的剪枝技巧: 
        3. 将输入的输入从大到小排序,这么做是因为一支长度为 K 的完整木棍,总比几支短的小木棍拼成的要好。 形象一些: 如果我要拼 2 支长为8的木棍,                第一支木棍我拼成   5 + 3    然后拼第二支木棍但是失败了,而我手中还有长为 2 和 1  的木棍,我可以用 5 + 2 + 1 拼好第一支,再尝试拼第二
             支,仔细想一想,就会发现这样做没意义,注定要失败的。   我们应该留下 2+1 因为 2+1 比 3 更灵活。 
        4. 相同长度的木棍不要搜索多次, 比如: 我手中有一些木棍, 其中有 2 根长为 4 的木棍, 当前搜索状态是 5+4+....(即表示长度为 5,4,2 的三支拼在一起, 
                ...表示深层的即将搜索的部分), 进行深搜后不成功,故我 没必要用另一个 4 在进行 5+4+... (题目中相邻的前后比较)
         5. 将开始搜索一支长为 L 的木棍时,我们总是以当前最长的未被使用的 木棍开始,如果搜索不成功,那么以比它短的开始,那么也一定不能取得全局               的成功。因为每一支题目给出的木棍 都要被用到。如果,有 
              4
              5 4 4 3 2   想拼成长为 6 的木棍,那么从 5 开始, 但是显然没有能与 5  一起拼成 6 的,那么我就没必要去尝试从 4 开始的,因为 最终 5 一定会                         被遗弃。在拼第 2 3 ... 支木棍时,一样。(小的更加灵活)

             6. 最后的最简单的一个就是,
                     for(int i = 0; i < n; i++)

                          for(int j = 0; j < n; j++)
                              {}
               与
                     for(int i = 0; i < n; i++)
                        for(int j = i+1; j < n; j++)
                              {} 
                的区别,这个不多说了。
        7. 我用过的另一个剪枝,但是对 poj 的数据效果一般, 用一个数组, Sum[i] 保存 第 i 个木棍之后,即比第 i 枝木棍短或与之相等所有的木棍的长度和。               试想,如果剩余的所有木棍加在一起都不能和我当前的状态拼 出一根长为 L 的木棍(从长度来看),还有必要搜下去么? 

(3)详细代码

#include
#include
#include
#include
using namespace std;
const int Max = 65;

int n, len, stick[Max];
bool flag, vis[Max];

bool cmp(int a, int b)
{
    return a > b;
}

void dfs(int dep, int now_len, int u)
{   // dep为当前已被用过的小棒数,u为当前要处理的小棒。
    if(flag) return;//完成的递归返回条件
    if(now_len == 0){                    //  当前长度为0,寻找下一个当前最长小棒。
        int k = 0;
        while(vis[k]) k ++;              //  寻找第一个当前最长小棒。
        vis[k] = true;
        dfs(dep + 1, stick[k], k + 1);
        vis[k] = false;
        return;
    }
    if(now_len == len)
    {                  //  当前长度为len,即又拼凑成了一根原棒。
        if(dep == n) flag = true;        //  完成的标志:所有的n根小棒都有拼到了。
        else dfs(dep, 0, 0);
        return;//未完成的递归返回条件
    }
    for(int i = u; i < n; i ++)
        if(!vis[i] && now_len + stick[i] <= len)// 过滤条件
        {
            if(!vis[i-1] && stick[i] == stick[i-1]) continue;      //  不重复搜索:最重要的剪枝。
            vis[i] = true;
            dfs(dep + 1, now_len + stick[i], i + 1);
            vis[i] = false;
        }
}

int main()
{
    while(scanf("%d", &n) && n != 0)
    {
        int sum = 0;
        flag = false;
        for(int i = 0; i < n; i ++)
        {
            scanf("%d", &stick[i]);
            sum += stick[i];
        }
        sort(stick, stick + n, cmp);     //  从大到小排序。
        for(len = stick[0]; len < sum; len ++)
            if(sum % len == 0)//   这里也相当于是剪枝(过滤条件)
            {          //  枚举能被sum整除的长度。
                memset(vis, 0, sizeof(vis));
                dfs(0, 0, 0);
                if(flag) break;
            }
        printf("%d\n", len);
    }
    return 0;
}
(4)对比 比较 poj 1011(本博客) 和 poj 3900 ( 剪枝算法(算法优化))

    对于前者 —— 是按照性价比排序;性价比高的未必最后会要(有选择的要);形成两条剪枝条件:

剪枝1. 之后所有的钻石价值+目前已经得到的价值<=ans 则剪枝。

剪枝2. 剩下的重量全部装目前最高性价比的钻石+目前已经得到的价值<=ans 则剪枝(非常重要的剪枝)。

 对于后者 —— 按照长度排序(都是提前从大到小的排序);必须把全部小木棒用上,因此需要有visit数组在dfs前后的true 和 false 的变化;

剪枝1、 由于所有原始棒子等长,那么必有sumlen%Initlen==0;

剪枝2、若能在[maxlen,sumlen-InitLen]找到最短的InitLen,该InitLen必也是[maxlen,sumlen]的最短;若不能在[maxlen,sumlen-InitLen]找到最短的InitLen,则必有InitLen=sumlen;

剪枝3、由于所有棒子已降序排序,在DFS时,若某根棒子不合适,则跳过其后面所有与它等长的棒子;

剪枝4、最重要的剪枝:对于某个目标InitLen,在每次构建新的长度为InitLen的原始棒时,检查新棒的第一根stick[i],若在搜索完所有stick[]后都无法组合,则说明stick[i]无法在当前组合方式下组合,不用往下搜索(往下搜索会令stick[i]被舍弃),直接返回上一层


你可能感兴趣的:(algorithm,c/c++成长之路)