程序的墓碑

当我们不再年轻的时候,当我们不再做程序的时候,唯有这些博客,记录着我们曾经为程序而存在着,为程序而活着。

博客园 首页 联系 订阅 管理

算法及其性质

算法是对问题求解过程的准确描述,由有限条指令组成,这些指令能在有限时间内执行完毕并产生确定性的输出。

算法需满足的4个性质:

输入零个多个外部量作为输入。

输出至少产生一个量作为输出,它(们)与输入量之间存在某种特定的联系。

确定性:组成算法的每条指令都是清晰、无歧义的。

有限性:每条指令的执行次数有限,执行每条指令的时间也有限。

程序与算法的区别:

程序是算法用某种程序设计语言的具体实现。

程序可以不满足算法的有限性性质。

算法的复杂度

复杂度:算法运行时需要耗费计算机资源的量,可以分为时间复杂度和空间复杂度。

算法复杂度依赖于三个方面:待求解问题的规模算法的输入算法本身。(如何理解)

T(n,I)给出的是在问题规模为n ,输入为I时的时间复杂度。但是我们不可能,也没必要对每种可能的输入都去求其时间复杂度。通常可以考虑3种情形下的时间复杂度:最坏情形、最好情形以及平均情形。

实践表明:可操作性最强、最有实际价值的是算法最坏情形下的时间复杂度

自此,如不作特殊说明,使用T(n)表示给定算法在最坏情形下的时间复杂度.

在进行阶的运算时,常系数低的阶以及常数项可以忽略。

根据O的定义,得到的是在问题规模充分大时,算法复杂度的一个上界。上界的阶越低则评估越有价值。

运算规则

O( f ) + O(g) = O(max( f , g));

O( f )·O(g) = O( f·g);

O(C·f (n)) = O(f (n));

f = O( f );

下界的阶越高,则评估精度越高,也就越有价值。

小结

进行算法的时间复杂度分析,就是求其T(n),并用O、Ω或是Θ以尽可能简单的形式进行表示。

理想情况下,希望能够使用Θ表示算法的时间复杂性。退而求其次,可以使用O或是Ω。

使用O时,希望估计的上界的阶越小越好。

使用Ω时,希望估计的下界的阶越大越好。

树的高度为ëlognû,所以将一个元素插入大小为n的堆所需要的时间是O(logn).

delete(H,i) 所需要的时间是O(logn).

make-heap(A): 从数组A创建堆

方法1从一个空堆开始,逐步插入A中的每个元素,直到A中所有元素都被转移到堆中。

时间复杂度为O(nlogn).

方法2

MAKEHEAP(创建堆)

输入:数组A[1…n]

输出:将A[1…n]转换成堆

1. fori← ën/2û downto 1

2. Sift-down(A,i){使以A[i]为根节点的子树调整成为堆,故调用down过程}

3. endfor

时间复杂度为O(n).

分治策略的思想(划分、治理、组合)

把规模较大的问题分解为若干个规模较小的子问题, 这些子问题相互独立且与原问题同类;(该子问题的规模减小到一定的程度就可以容易地解决)

依次求出这些子问题的解,然后把这些子问题的解组合起来得到原问题的解。

由于子问题与原问题是同类的,故分治法可以很自然地应用递归。

递归

优点:结构清晰,可读性强,而且容易用数学归纳法来证明算法的正确性,为设计算法、调试程序带来很大方便。

缺点:递归算法的运行效率较低,无论是耗费的计算时间还是占用的存储空间都比非递归算法要多。

分治法的适用条件

该问题的规模缩小到一定的程度就可以容易地解决;

该问题可以分解为若干个规模较小的同类问题;

利用该问题分解出的子问题的解可以合并为该问题的解;

该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题。这条特征涉及到分治法的效率,如果各子问题是不独立的,则分治法要做许多不必要的工作,重复地解公共的子问题,此时虽然也可用分治法,但一般用动态规划更为合适。

矩阵乘法

设A,B是两个n×n的矩阵,求C=AB.

方法1:直接相乘法 T(n)=Θ(n3)

方法2:分块矩阵法(直接应用分治策略) T(n)=Θ(n3)

方法3:Strassen算法(改进的分治策略)T(n)=Θ(nlog27)=Θ(n2.81)

大整数相乘

逐位相乘算法T(n)=Θ(n2)

分治算法T(n)=Θ(nlog23)=Θ(n1.59)

排列问题T(n)=Θ(n*n!)

2k×2k棋盘覆盖问题T(k)=4T(k-1)+O(1)=Θ(4k)

动态规划的基本思想

动态规划的实质是分治消除冗余,是一种将问题实例分解为更小的、相似的子问题,并存储子问题的解以避免计算重复的子问题,来解决最优化问题的算法策略。

基本步骤:

找出最优解的性质,并刻划其结构特征。

递归地定义最优值。

以自底向上的方式计算出最优值。

根据计算最优值时得到的信息,构造最优解。

矩阵链相乘乘法最少次数时间T(n)=Θ(n3)空间S(n)=Θ(n2)

最长公共子序列问题T(n)=Θ(nm) S(n)=Θ(min{ m,n})

两个字符串A,B, 长度分别为n, m. X和Y的最长公共子序列长度.

0-1背包问题T(n)=Θ(nC) S(n)=Θ(C)

输入:物品集合{u1,u2,…,un},重量分别为w1,w2,…,wn,价值分别为v1,v2,…,vn,承重量为C的背包

输出:背包所能装物品的最大价值

贪心策略总是做出在当前看来最好的选择,并且不从整体上考虑最优问题,所

做出的每一步选择只是局部意义上最优选择(因而效率往往较高),逐步扩大解

的规模。当然,希望贪心策略得到的最终结果也是整体最优的(上面的解法得到

的恰好是最优解),但不能保证必定得到最优解。

单源最短路径

Dijkstra算法:n个顶点,m条边T(n)= Q(m+n2)

G=(V,E)最小生成树

Kruskal算法T(n)=O(mlogm+n)

1.对G的边E按权重以非降序排列。

2.初始时输出树T={};依次取排序表中的每条边,若加入T不形成回路,则加入T;否则将其丢弃;

3.不断重复步骤2,直到树T包含n-1条边,算法结束。

Prim算法 T(n)=Q(m+n2)

设G=(V,E)是连通无向带权图,V={1,2,…,n}。构造G 的最小生成树的Prim算法的基本思想是:

首先置X={1},

然后,只要X是V的真子集,就作如下的贪心选择:

选取权重最小的边(x,y), 其中x∈X,y∈Y,

将边(x,y)加入当前的最小生成树,

将顶点 y 从Y移到X 中,

这个过程一直进行到X=V时为止。

在这个过程中选取到的所有边恰好构成G 的一棵最小生成树。

哈夫曼编码T(n)=O(nlogn)

深度优先搜索(Depth-First Search, DFS)假设图有n个顶点,m条边T(n)=Q(m+n)

给定有向或是无向图G=(V,E),DFS工作过程如下:

1.将所有的顶点标记为”unvisited”。

2.选择一个起始顶点,不妨称为v ∈V,并将之标记为”visited”。

3.选择与v相邻的任一顶点,不妨称之为w,将w标记为”visited”。

4.继续选择一个与w相邻且未被访问的顶点作为x;将x标记为”visited”。继续选择于x相邻且未被访问的顶点。

5.此过程一直进行,直到发现一个顶点y,邻接于y的所有顶点都已经被标记为”visited”。此时,返回到最近访问的顶点,不妨称之为z,然后访问和z相邻且标记为”unvisited”的顶点。

6.上述过程一直进行,直到返回到起始顶点v。

广度优先搜索(Breadth-First Search, BFS)假设图有n个顶点,m条边T(n)=Q(m+n)

思路:在访问一个顶点v后,接下来依次访问邻接于v的所有顶点。对应的搜索树称之为广度优先搜索生成树。

实现:使用队列(Queue)

回溯法的基本步骤:

针对所给问题,定义问题的解空间;

确定易于搜索的解空间结构;

深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。

常用剪枝函数:

约束函数在扩展结点处剪去不满足约束的子树;

限界函数剪去得不到最优解的子树。

关于复杂性:

用回溯法解题的一个显著特征是在搜索过程中动态产生问题的解空间。在任何时刻,算法只保存从根结点到当前扩展结点的路径。如果解空间树中从根结点到叶结点的最长路径的长度为h(n),则回溯法所需的计算空间通常为O(h(n))。而显式地存储整个解空间则需要O(2h(n))O(h(n)!)内存空间。

分支限界法(Branch and Bounds)

分支限界法类似于回溯法,也是一种在问题的解空间树T中搜索问题解的算法。

分支限界法与回溯法的求解目标不同:

回溯法寻找的是满足给定性质的一个解或解的集合(如3着色问题、8皇后问题)。

分枝限界法关心的是目标函数的最大化或最小化。

例如背包问题(如前所示),若用回溯法求解,则在解空间搜索树中找到许多可行解,然后从可行解中找到使得背包内物品价值最大化的解,即最优解。然而,利用分枝限界法,无需这样即可找到问题的最优解。

分支限界法与回溯法的搜索方式不同

回溯法:深度优先

分支限界法:广度优先或最小耗费(最大效益)优先

分支限界法基本思想

分支限界法常以广度优先或以最小耗费(最大效益)优先的方式搜索问题的解空间树。

在分支限界法中,每一个活结点只有一次机会成为扩展结点。活结点一旦成为扩展结点,就一次性产生其所有儿子结点。在这些儿子结点中,导致不可行解或导致非最优解的儿子结点被舍弃,其余儿子结点被加入活结点表中。

此后,从活结点表中取下一结点成为当前扩展结点,并重复上述结点扩展过程。这个过程一直持续到找到所需的解活结点表为空时为止。

常见的两种分支限界法

从活结点表中选择下一扩展结点的不同方式导致不同的分支限界法:

队列式(FIFO)分支限界法(宽度优先):按照队列先进先出(FIFO)原则选取下一个节点为扩展节点。

优先队列式分支限界法(最小耗费或是最大效益优先):按照优先队列中规定的优先级选取优先级最高的节点成为当前扩展节点。

最大优先队列:使用最大堆,体现最大效益优先

最小优先队列:使用最小堆,体现最小耗费优先

回溯法与分支限界法的对比

方法

对解空间树的搜索方式

存储结点的常用数据结构

结点存储特性

常用应用

回溯法

深度优先搜索

堆栈

活结点的所有可行子结点被遍历后才被从栈中弹出

找出满足约束条件的所有解

分支限界法

广度优先或最小消耗(最大效益)优先搜索

队列或优先队列

每个结点只有一次成为活结点的机会

找出满足约束条件的一个解或特定意义下的最优解

posted on 2011-05-24 09:31  程序的墓碑  阅读(1562)  评论(0编辑  收藏  举报