[算法2] 第三集 剪枝

本篇本来我是不准备写的,但是架不住任务在身,我们就接着上回的dfs,来讲讲剪枝吧。没看上集的记得去看,点击此处去看上集

下面是定义(大致看看就好,看不懂的马上看看例子)

一、剪枝策略的寻找的方法


1)微观方法:从问题本身出发,发现剪枝条件

2)宏观方法:从整体出发,发现剪枝条件。

3)注意提高效率,这是关键,最重要的。

总之,剪枝策略,属于算法优化范畴;通常应用在DFS 和 BFS 搜索算法中;剪枝策略就是寻找过滤条件,提前减少不必要的搜索路径。

二、剪枝算法(算法优化)

1、简介

    在搜索算法中优化中,剪枝,就是通过某种判断,避免一些不必要的遍历过程,形象的说,就是剪去了搜索树中的某些“枝条”,故称剪枝。应用剪枝优化的核心问题是设计剪枝判断方法,即确定哪些枝条应当舍弃,哪些枝条应当保留的方法。

2、剪枝优化三原则: 正确、准确、高效.原则

     搜索算法,绝大部分需要用到剪枝.然而,不是所有的枝条都可以剪掉,这就需要通过设计出合理的判断方法,以决定某一分支的取舍. 在设计判断方法的时候,需要遵循一定的原则.

剪枝的原则:

  1) 正确性

  正如上文所述,枝条不是爱剪就能剪的. 如果随便剪枝,把带有最优解的那一分支也剪掉了的话,剪枝也就失去了意义. 所以,剪枝的前提是一定要保证不丢失正确的结果.

  2)准确性

  在保证了正确性的基础上,我们应该根据具体问题具体分析,采用合适的判断手段,使不包含最优解的枝条尽可能多的被剪去,以达到程序“最优化”的目的. 可以说,剪枝的准确性,是衡量一个优化算法好坏的标准.

 3)高效性

设计优化程序的根本目的,是要减少搜索的次数,使程序运行的时间减少. 但为了使搜索次数尽可能的减少,我们又必须花工夫设计出一个准确性较高的优化算法,而当算法的准确性升高,其判断的次数必定增多,从而又导致耗时的增多,这便引出了矛盾. 因此,如何在优化与效率之间寻找一个平衡点,使得程序的时间复杂度尽可能降低,同样是非常重要的. 倘若一个剪枝的判断效果非常好,但是它却需要耗费大量的时间来判断、比较,结果整个程序运行起来也跟没有优化过的没什么区别,这样就太得不偿失了.

3、分类

   剪枝算法按照其判断思路可大致分成两类:可行性剪枝最优性剪枝.

  1. 可行性剪枝 —— 该方法判断继续搜索能否得出答案,如果不能直接回溯。
  2. 最优性剪枝 —— 又称为上下界剪枝,是一种重要的搜索剪枝策略。它记录当前得到的最优值,如果当前结点已经无法产生比当前最优解更优的解时,可以提前回溯。

三、实例

1.验证二叉搜索树

我们顺着上回讲的验证二叉搜索树,这题还记得吧!!!

我那时只讲了讲思路,然后直接上的代码

class Solution 
{
public:
     long prev = LONG_MIN;
     bool isValidBST(TreeNode* root) 
 {
     if(root == nullptr) return true;
     bool left = isValidBST(root->left);
     // 剪枝
     if(left == false) return false;
     bool cur = false;
     if(root->val > prev)
     cur = true;
     // 剪枝
     if(cur == false) return false;
     prev = root->val;
     bool right = isValidBST(root->right);
     return left && right && cur;
 }
};
 
​

那时我直接剪枝了,有人反映没看懂,这里我们展开来讲讲哈。

首先,我们的思路是:

        如果一棵树是二叉搜索树,那么它的中序遍历的结果一定是⼀个严格递增的序列。 因此,我们可以初始化⼀个无穷小的全区变量,用来记录中序遍历过程中的前驱结点。那么就可以在中序遍历的过程中,先判断是否和前驱结点构成递增序列,然后修改前驱结点为当前结点,传入下一层的递归中。

假设我们有如下二叉树:

如果我们按照我们不剪枝遍历会发生什么?没错很明显存在false但是我们dfs仍然会中序遍历完整个二叉树,但是如果加了剪枝

运行到19这,我们就能判断它不是一个二叉搜索树,因此我们可以剪枝,把剩下的剪掉,直接返回结果,同理,我们是中序遍历的如果左边直接false了,我们也可以直接返回结果。

2.二叉搜索树中第K小的元素

1.题目题意

2.算法思路:

        我们可以根据中序遍历的过程,只需扫描前 k 个结点即可。 因此,我们可以创建⼀个全局的计数器 count,将其初始化为 k,每遍历⼀个节点就将 count--。直到某次递归的时候,count 的值等于 1,说明此时的结点就是我们要找的结果。 因此我们这题的解法就是(dfs中序遍历+count计数剪枝器)
算法流程:
1. 定义⼀个全局的变量 count,在主函数中初始化为 k 的值
递归函数的设计:int dfs(TreeNode* root): 返回值为第 k 个结点;
2.递归函数流程(中序遍历):
        1. 递归出口:空节点直接返回 -1,说明没有找到;
        2. 去左子树上查找结果,记为 retleft:
                a. 如果 retleft == -1,说明没找到,继续执⾏下⾯逻辑;
                b. 如果 retleft != -1,说明找到了,直接返回结果,无需执心下面代码(剪枝);
        3. 如果左⼦树没找到,判断当前结点是否符合,如果符合,直接返回结果
        4. 如果当前结点不符合,去右子树上寻找

3.代码实现

class Solution {
public:
    int ret,count;
    int kthSmallest(TreeNode* root, int k) {
        count=k;
        dfs(root);
        return ret;
    }
    void dfs(TreeNode* root)
    {
        if(root==nullptr||root==0)//剪枝
        return;
        dfs(root->left);
        count--;
        if(count==0)
        {
            ret =root->val;
            return;
        }
        dfs(root->right);

    }
};

这里我们再判空后面加上了但count==0是出函数,这个操作就是剪枝,萧少了多余的操作,效率更高。


剪枝我也就简单的讲完了,希望大家能够理解,再之后的学习中我们还会用到的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值