痛定思痛,开启算法之路(十三)

5.7 在二叉树中计算平衡因子

这里写图片描述

首先对于n=1的基础情况是平凡的:
1)直接的归纳假设:我们已知如何计算节点数小于n的二叉树的全部节点的平衡因子。
对于一棵n>1的树,去掉根节点归纳求解剩下的两棵子树(因为节点的平衡因子只取决于左右子树的高度差),然而虽然我们知道子树(<=n-1)的平衡因子,但是根的平衡因子并不取决于此,必须知道子树高度才可以;所以扩展:
2)更强的归纳假设:已知如何计算节点数小于n的二叉树的全部节点的平衡因子和高度。
有了子树高度,则计算根的平衡因子(子树高做差)和高度(子树高度最大值再加一)就非常简单了;

    通过归纳,我们仅需把一个小规模问题的解扩展到一个大问题的解,如果解是更广泛的(因为问题被归纳扩展了),
则归纳步骤可能更容易,这是因为我们有更多的东西可以利用了;

5.8 寻找最大连续子序列

这里写图片描述

(若所有数值均为负数,则最大子序列为空,和为0)

1)直接归纳假设:已知如何找到规模小于n的序列的最大子序列

这里写图片描述

对于上面最后一种情况,如果直接利用如上假设是很难求出其值的;考虑上面最后一种情况,加入Xn之后的最大子序列:此时存在最大子序列的位置有两种情况,一个是存在于原有的j<=n-2的原串内部;另一个是位于新加入的Xn与原串的后缀所组成的新的子序列。所以为了容易的判断这两个位置的子序列的大小,扩大原有假设如下:

2)更强的归纳假设:已知如何找到规模小于n的序列的最大子序列,以及包括其后缀所组成的最大子序列。

如上分析如果知道了这两个子序列,那么算法就明确了。对于包括后缀的最大子序列如果和为0则直接比较Xn与内部最大子序列的和即可,选两者较大者得解;

(如下算法,Suffix_Max=0即包括后缀的最大子序列之和)
这里写图片描述

5.9 增强归纳假设

这里写图片描述

5.10 动态规划:背包问题

这里写图片描述

数学化:用P(n,K)表示该问题,n表示物品的数目而K表示背包的大小;P(i,k)表示前i个物品和背包大小为k的问题。

归纳假设(最初的设想):已知如何求解P(n-1,K);

**基础情况**是显然的:如果单一物品的大小为K,则仅此一解;
**n>1时**,分情况:
1.如果对于P(n-1,K)有一个解,则算法直接结束(不需要物品n即可);
2.若P(n-1,K)无解,则求解过程中必须包括物品n,而之前的n-1个物品放进K-kn的背包中求解是否可解;
所以由上分析规约到较小的规模必须求出P(n-1,K)和P(n-1,K-kn)(即不仅需要对大小为K的背包求解,还要为所有不大于K的背包求解)

归纳假设(第二次设想):已知如何求解P(n-1,k),其中0<=k<=K.

基础情况P(1,k)很容易:若k=0,则无解,否则只有此物品大小为k时才有一解;
把P(n,k)规约到两个子问题P(n-1,k)和P(n-1,k-kn)(当k-kn<0时忽略此情况);两个子问题均可归纳地解决。

虽然问题通过加强归纳可以解决,然而此算法每次由父问题规约为2个子问题,所以他将导致一个指数级的算法。

其实这个问题的解决在之前我们也遇到过,指数式的运行时间是因为每经过一次规约后就对问题的数目加倍,但是这里的情况是比较特殊的;因为其总问题的规模是一定的为nK(对于P(i,k),i的规模为n,k的规模为K),每次规约的过程只不过是对于子问题的多次重复使用,所以对于之前的结果我们可以借助一个n*K的二维表格用来保存之前的子问题结果即可,每次父问题的解只需在表格中取出即可;

这里写图片描述

这里写图片描述

复杂度:表格共nK项,且每一项由其他两项在常数时间内计算所得,所以为O(nK);

        总结:当一个问题可以归约到几个小一些但又不是足够小的子问题时,动态规划方法是有效的。
    所有的子问题都可以被计算,我们将维护一个大的矩阵来进行这种计算(空间换取时间)。

5.11 常见错误

1.忘记了基础情况,基础情况对于终止递归是必须的;
2.错误的把对于n的解扩展到问题对于n+1时的一个特定实例的解,而不是对于任意实例;
3.无意识的改变假设(如对于连通无向图的操作当中,利用归纳假设时在删除一点的情况下考虑较小的规模,而在这里删除一点可能造成图不再是连通的,从而导致了较小规模的实例不等同于原实例;在这里更好的解法是其不依赖于图是否连通)

5.12 小结

这里写图片描述
这里写图片描述

第六章 数列和集合的算法

序列需考虑元素的顺序,集合中不能出现重复元素。

6.2.1 纯二叉树搜索

这里写图片描述

这里写图片描述

这个算法比较常见,思想及过程很简单不再赘述;

6.2.2 循环序列的二叉搜索

这里写图片描述

6.2.3 二叉搜索特殊下标

这里写图片描述

因为对于序列值是不知道的而且i是变动的,所以不可以直接使用纯二叉搜索;但此处仍可以借助二叉搜索原理求解:因为所有整数均是不同的,找到a(n/2)的元素与n/2的大小比较,若元素小于其下标则其左半序列可直接舍去(因为元素均不相等且与下标均为整数,其左半元素一定均小于其各自下标);若大于原理相同,算法如下:

这里写图片描述

6.2.4 二叉搜索长度未知的序列

    这里的做法很好的扩展了思维,值得学习和思考;

    当人们一看到二叉搜索时首先、大多都是想到的是缩小一倍的规模来求解;而这里反其道而行,从小的出发每次扩大一倍的规模进行寻找:

    重要的不是其适用的形式以及所受到的限制(当然这些细节很重要),但更重要的是其思维方式的转变、算法的活学活用。

在一个有序的序列中查找一个元素等于Z(不论序列是否有界),从第一个元素开始i=0,若Z<=Ai则结束,否则扩展为2i位置处,从i到2i间寻找,依次2i到4i等等;其花费的时间复杂度与i是相关的为O(log i)而相比直接从1到n缩小规模搜索其复杂度为O(log n)还要小!(但是这里的细节还是要注意一下的:因为他是先扩大规模寻找大致范围,确定范围之后还需要再次进行缩小规模二叉搜索,所以其复杂度相当于时间上的二次放因子,故当i=O(n^(1/2))时算法最优)

6.2.5 重叠子序列问题

这里写图片描述

重叠子序列:可以把B的字符按序嵌入A中,嵌入的位置不必连续,则B为A的子序列。

对于验证是否为子序列的基本算法是很明确的:即从头开始扫描A直到b1第一次出现,继续扫描直到b2出现,依次类推即可,其时间复杂度为O(m+n);现在要求的是i的最大值:如果B^j为A的子序列,则对于1<=i<=j的任意i,一定有B^i也为A的子序列,且i<=n/m(n为A的长度,m为B的长度),有了上面这个性质那么我们就可以使用二叉搜索了:令
这里写图片描述

这里写图片描述

6.3 内插搜索

    二叉搜索总是把空间简单的减半,从而保证其性能是对数级的。
但是简单的减半并不是最优的,每次减少的规模不应该是固定死的,而是应该根据实际情况做出相应的减少规模的变化;
    比如查找一本800页的书的第150页,不应该只是每次简单的直接减半查找(虽然可行,但是显然不够高效),
我们应该直接找其大约1/5处比如翻到了200页,此时也不是减半而是大概找其3/4处,依次类推下去;
    这正体现了内插搜索的思想:也就是根据最可能接近目标的量来缩小搜索范围,而不是简单的将搜索空间减半,而其每次缩小的规模是由内插法决定的。

这里写图片描述

其算法步骤根二叉搜索无异,只是单侧界限是根据搜索比例计算出来的;其平均时间复杂度为O(log log n),虽然其性能有指数级进步但实际情况下并不明显:1)除非n很大,否则log n本身已经足够小了;2)每次单侧计算规模是相对复杂的。

猜你喜欢

转载自blog.csdn.net/enjoy_endless/article/details/79242685
今日推荐