算法设计与分析基础(十):变治法
- 首先是“变”, 将问题变形, 转换成另一个问题,变得更容易求解;
- 然后是“治”,对问题的实例进行求解。
- 变治法有三个变形:
(1)实例化简——同样问题
(2)改变表现——同样实例
(3)问题化简——另一问题
实例化简——同样问题
预排序
列表若有序,列表上的部分问题更容易求解。因此很多问题需要先排序,则该问题的时间效率依赖于排序算法的效率。
回忆前面所学的排序算法:
例子:检验数组中元素的唯一性
Algorithm PEU (A[0..n-1]) // Presort Element Uniqueness
//先对数组排序再验证唯一性
//输入:数组A
//输出:若A没有相等的元素,返回“true”,否则返回“false”.
对数组排序; T:= true;
for i=0 to n-2 do
if A[i]=A[i+1] then T:= false;
3. return T.
改变表现——同样实例
集合上的操作–二叉查找树
把一个集合变换成 一棵二叉查找树是改变表现技术的一个实例。
好处是:在平均情况下,查找,插入,删除的效率是Θ(logn)。
最差情况下, Θ(n),退化成线性的情况。
所谓的二叉查找树,它或者是一棵空树,或者满足以下的性质:
每个结点作为搜索对象,它的关键字是互不相同的。
对于树上的所有结点,如果它有左子树,那么左子树上所有结点的关键字都小于该结点的关键字。
对于树上的所有结点,如果它有右子树,那么右子树上所有结点的关键字都大于该结点的关键字。
插入操作:(建树)
删除操作:
对于二叉查找树的删除操作(这里根据值删除,而非结点)分三种情况:在此之前,我们应该确保根据给定的值找到了要删除的结点,如若没找到该结点不会执行删除操作!
下面三种情况假设已经找到了要删除的结点:
-
如果为叶子结点(没有左、右子树),此时删除该结点不会破坏树的结构,直接删除即可,并修改其父结点指向它的引用为null.如下图:
-
如果其结点只包含左子树,或者右子树的话,此时直接删除该结点,并将其左子树 或者右子树设置为其父结点的左子树或者右子树即可,此操作不会破坏树结构。
-
当结点的左右子树都不空的时候,一般的删除策略是用其右子树的最小数据(容易找到)代替要删除的结点数据并递归删除该结点(此时为null),因为右子树的最小结点不可能有左孩子,所以第二次删除较为容易。
z的左子树和右子树均不空。找到z的后继y,因为y一定没有左子树,所以可以删除y,让y的父亲节点成为y的右子树的父亲节点,并用y的值代替z的值.如图:
堆和堆排序
设T是一棵深度为d的二叉树,结点为L中的元素.
若满足:
- 所有内结点(可能一点除外)的度数为2
- 所有树叶至多在相邻的两层
- d-1层的所有树叶在内结点的右边
- d-1层最右边的内结点可能度数为1 (没有
右儿子) - 每个结点的元素不小于儿子的元素
自底向上构造法
首先把数组按序填充到堆中各个结点然后按照自下而上,从右至左的顺序,从最后的父节点开始,到根为止,检查
这些节点的值是否都满足子结点比父结点小的约束条件。
如果不满足则调换父子结点的位置,然后再检查在新的位置上,是否满足父母优势要求。
复杂度分析:
结点的高度 该结点距树叶的距离
结点的深度 该结点距树根的距离
同一深度结点分布在树的同一层,同一高度结点可以分布在树的不同的层
思路:
HEAPIFY(i)的复杂度依赖于i的高度,按照高度计数结点数,乘以O(h),再求和
堆排序
算法HEAPSORT(A)
1. BUILD-H EAP(A)
2. for i< - length[A] downto 2 .
3. do exchange A[1]< >A[i]
4.heap-size[4] < -heap-size[4]-1
5.HEAPIFY(A,1)
复杂性: O(mlogn)
BUILD_ HEAP时间为O(n),从行2-5调用HEAPIFY 1-1次,每次O(logn),时间为O(nlogn)
Horner法则和二进制幂
Horner法则
计算n次多项式的值的算法。
例如,n=4,
直接计算,需要4+3+2+1=10=n(n-1)/2次乘法。用如下Horner/秦九韶算法只需要 n=4 次乘法;
当 x=3 时,计算 p(x)
系数 | 2 | -1 | 3 | 1 | -5 |
---|---|---|---|---|---|
X=3 | 2 | 2×3+(-1)=5 | 5×3+3=18 | 18×3+1=55 | 55×3+(-5)=160 |
霍纳法则的有趣特性
该算法在计算p(x)在某些点 x0上的值所产生的中间数字恰好可以作为 p(x)除以 x- x0 的商的系数,而算法的最后结果( p(x0)的值),等于这个除法的余数。
原理如下: 对任意的f(x)与g(x),存在q(x)与 r(x),使得
f(x) = q(x)g(x) + r(x)
表示 g(x) 除 f(x), 商为q(x), 余数为 r(x),因此,
p(x) = q(x)(x- x0) + r, p(x0) = q(x0)(x0 - x0) + r, p(x0) = r .
二进制幂
计算an的算法,有两种方法:
从左到右逐位扫描算法:例求a13, 13=1101
若该位为1,则前一个数平方后再乘以a,否则不需要再乘
从右到左逐位扫描算法:例如 求a13, 13=1101
若该位为0,中间结果相乘时候不相乘
问题化简——另一问题
求最小公倍数lcm(m, n)
原问题:
求能够被m和n整除的最小整数记为lcm(m, n)
常用算法:
m和n所有公共质因数乘以m的不在n中的质因数,再乘以n不在m中的质因数
24=2×2×2×3 60=2×2×3×5
lcm(24, 60)=(2×2×3)×2×5=120
缺陷:
需要连续素数的表
关联
m和n的最大公约数gcd(m,n)是m和n所有公共质因子的积。并且lcm(m,n) = m·n/gcd(m,n)
问题化简
转换为求最大公约数gcd(m,n)的高效的欧几里德算法
计算图中的路径数
原问题: 计算无向无权图中两个顶点之间的路径数量
问题化简: 利用邻接矩阵,可以证明:
图 G 中顶点 vi 到顶点 vj 之间长度为 k 的路径数量等于Ak 的 (i, j)位置的元素,其中 A 是图 G 的邻接矩阵。
简化为图论问题
过河问题:一个农夫希望用一条小船把一只狼,一头羊,一篮白菜从河的北岸渡到河的南岸,由于船小只能够容纳人狼羊菜中的两个。需要考虑的约束条件是:在没有人的情况下,狼和羊不能在一起,羊和白菜不能单独在一起。求解一个渡船的方案,把狼、羊、白菜都运过去。
对过河问题,画出人、狼、羊、菜的状态空间图后即可以发现有两条路径,这两条路径就是问题的两个解。
解:用四维0-1向量表示(人,狼,羊,菜)在河西岸的状态(在河西岸则分量取1,否则取0),共有24 =16 种状态.在河东岸的状态类似记作.
由题设,状态(0,1,1,0),(0,0,1,1),(0,1,1,1)是不允许的,从而对应状态(1,0,0,1), (1,1,0,0), (1,0,0,0)也是不允许的.
以可允许的10个状态向量作为顶点,将可能互相转移的状态用线段连接起来构成一个图.
根据此图便可找到渡河方法.