IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> DFS之剪枝与优化 -> 正文阅读

[数据结构与算法]DFS之剪枝与优化

概括

剪枝 就是减小搜索树规模、尽早排除搜索树中不必要的分支的一种手段。形象地看,就好像剪掉了搜索树的枝条,故称为剪枝。

方法

1、优化搜索顺序

在一些搜索问题中,搜索树的各个层次、各个分支之间的顺序不是固定的。不同的搜索顺序会产生不同的搜索树形态,其规模大小也相差甚远。

2、排除等效冗余

在搜索过程中,如果我们能够判定从搜索树的当前节点上沿着某几条不同分支到达的子树是等效的,那么只需要对其中的一条分支执行搜索。

3、可行性剪枝

在搜索过程中,及时对当前状态进行检查,如果发现分支已经无法到达递归边界,就执行回溯。(某些题目条件的范围限制是一个区间,此时可行性剪枝也被称为“上下界剪枝”。

4、最优性剪枝

在最优化问题的搜索过程中,如果当前花费的代价已经超过了当前搜到的最优解,那么无论采取多么优秀的策略到达递归边界,都不可能更新答案。此时可以停止对当前分支的搜索,执行回溯。

5、记忆化

可以记录每个状态的搜索结果,在重复遍历一个状态时直接检索并返回。(就好像我们对图进行深度优先遍历时,标记一个节点是否已经被访问过。

例题

1、小猫爬山

原题链接: 小猫爬山

题目描述

翰翰和达达饲养了 N 只小猫,这天,小猫们要去爬山。
经历了千辛万苦,小猫们终于爬上了山顶,但是疲倦的它们再也不想徒步走下山了(呜咕>_<)。
翰翰和达达只好花钱让它们坐索道下山。
索道上的缆车最大承重量为 W,而 N 只小猫的重量分别是 C1、C2……CN。
当然,每辆缆车上的小猫的重量之和不能超过 W。
每租用一辆缆车,翰翰和达达就要付 1 美元,所以他们想知道,最少需要付多少美元才能把这 N 只小猫都运送下山?

输入格式

第 1 行:包含两个用空格隔开的整数,N 和 W。
第 2…N+1 行:每行一个整数,其中第 i+1 行的整数表示第 i 只小猫的重量 Ci。

输出格式

输出一个整数,表示最少需要多少美元,也就是最少需要多少辆缆车。

数据范围

1 ≤ \leq N ≤ \leq 18
1 ≤ \leq C i C_i Ci? ≤ \leq 1 0 8 10^8 108

输入样例

5 1996
1
2
1994
12
29

输出样例

2

解题思路

这个题目可以将所有的缆车合并看成一个状态,状态之间的转移可以看成由将小猫放在那辆缆车决定
剪枝1 : 优化搜索顺序,将小猫按照重量递减排序(先放重量大的可以相对较快搜索出最优解)
剪枝2:最优性剪枝 如果当前搜到的小猫组数已经大于当前搜到的最优解的时候就立即回溯

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

const int N = 25;

int sum[N], n, a[N], k, res, m;

void dfs(int u)
{
    if(k >= res)    return ; // 最优性剪枝
    if(u == n + 1){ // 递归完所有的小猫
        res = k;
        return ;
    }

    for(int i = 0; i < k; i ++) // 遍历当前的所有缆车
        if(sum[i] + a[u] <= m){ // 如果能够放下当前小猫
            sum[i] += a[u];
            dfs(u + 1);
            sum[i] -= a[u];
        }

    sum[k ++ ] = a[u]; // 新开一个缆车
    dfs(u + 1);
    sum[ -- k] = 0;
}

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i ++)    cin >> a[i];

    sort(a + 1, a + 1 + n); // 优化搜索顺序
    reverse(a + 1, a + 1 + n);

    res = n;
    dfs(1);
    cout << res << endl;

    return 0;
}

2、木棒

原题链接: 木棒

题目描述

乔治拿来一组等长的木棒,将它们随机地砍断,使得每一节木棍的长度都不超过 50 个长度单位。
然后他又想把这些木棍恢复到为裁截前的状态,但忘记了初始时有多少木棒以及木棒的初始长度。
请你设计一个程序,帮助乔治计算木棒的可能最小长度。
每一节木棍的长度都用大于零的整数表示。

输入格式

输入包含多组数据,每组数据包括两行。
第一行是一个不超过 64 的整数,表示砍断之后共有多少节木棍。
第二行是截断以后,所得到的各节木棍的长度。
在最后一组数据之后,是一个零。

输出格式

为每组数据,分别输出原始木棒的可能最小长度,每组数据占一行。

数据范围

数据保证每一节木棍的长度均不大于 50。

输入样例

9
5 2 1 5 2 1 5 2 1
4
1 2 3 4
0

输出样例

6
5

解题思路

从小到大枚举原始木棒的长度 len (也就是枚举答案)。当然, len 应该是所有木棍长度总和 sum 的约数 ,并且原始木棒的根数 cnt 就等于 sum / len

剪枝 1 : 优化搜索顺序 : 把木棍长度从大到小排序 (感觉像是减少高层次的树枝数量)

剪枝 2 : 排除等效冗余
(1) 可以限制先后加入一根原始木棒的木棍长度是递减的 (这是因为先拼上一根长度为 x 的木棍,再拼上一根长为 y 的木棍(x < y),与先拼上 y 再拼上 x 显然是等效的,只需要搜索其中一种)
(2)当一根木棍在某一个位置上拼接失败时,那么相同长度的木棍拼接在此处也是失败的
(3) 如果在当前原始木棒中 “尝试拼入的第一根木棍” 的递归分支返回失败,那么直接判定当前分支失败,立即回溯。这是因为在拼入这根木棍前,面对的原始木棒都是 ” 空 “的(还没有进行拼接),这些木棒是等效的。木棍拼在当前的木棒中失败,拼在其它木棒中一样会失败。
(4) 如果在当前原始木棒中拼入一根木棍后,木棒恰好被拼接完整,并且 “接下来拼接剩余原始木棒” 的递归分支返回失败,那么直接判定当前分支失败,立即回溯,该剪枝可以用贪心来解释,”再用1跟木棍恰好拼完当前原始木棒” 必然比 “再用若干跟木棍拼完当前原始木棒” 更好。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

const int N = 110;

int len, sum, w[N], n;
bool st[N];

bool dfs(int cnt, int s, int u)
{
    if(cnt * len == sum)  return true;
    if(s == len)    return dfs(cnt + 1, 0, 0); // 拼完当前木棒,新开一根木棒

    for(int i = u; i < n; i ++){ // 剪枝 2(1) 
        if(st[i] || s + w[i] > len)   continue;// 已经用过当前木棍或者拼上当前木棍超过当前的木棒长度

        st[i] = 1;
        if(dfs(cnt, s + w[i], i))   return true;
        st[i] = 0;

        if(!s || s + w[i] == len)   return false; // 剪枝 2(3) 和 2(4) 

        int j = i;
        while(j < n && w[j] == w[j + 1]){ // 剪枝 2(2)
            j ++;
        }
        i = j;
    }
    return false;
}

int main()
{
    while(cin >> n, n){
        sum = 0;
        for(int i = 0; i < n; i ++){ cin >> w[i];sum += w[i];}
        // cout << sum << endl;
        memset(st, 0, sizeof st);
        sort(w, w + n);// 剪枝 1
        reverse(w, w + n);

        len = 1;
        while(1){
            if(sum % len == 0 && dfs(0, 0, 0)){
                cout << len << endl;
                break;
            }else   len ++;
        }
    }
    return 0;
}

本文档基于 AcWing 制作

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-10-08 21:07:02  更:2022-10-08 21:07:18 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 19:22:50-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码