logo资料库

算法设计文档(含回溯法 递归法 贪心算法 背包...).doc

第1页 / 共93页
第2页 / 共93页
第3页 / 共93页
第4页 / 共93页
第5页 / 共93页
第6页 / 共93页
第7页 / 共93页
第8页 / 共93页
资料共93页,剩余部分请下载后查看
回溯法的一般描述
一般表达
规律
空间树
[
用回溯法解题的一般步骤:
用回溯法解决八皇后问题的C语言程序
用回溯法解决八皇后问题的PASCAL语言程序
回溯法解决迷宫问题
递归法
动态规划法
理论发展
[
原理简介
[
应用实例
[
算法
[
递归的基本概念和特点
[
1. 一般算法
自创“残卷法”用C语言解8皇后问题
[
2. 带图形显示的实现
[
3.C代码的另一个例子(效率较高)
[
4.IOCCC的一个实现(极简版)
[
5.循环实现 Java
[
6.Qbasic版的解决方案
[
7.高效解法-递归版
[
java实现//8 Queen 递归算法
[
c#实现
[
用PASCAL 解决八皇后问题
[
Python实现
[
PASCAL实现
贪心算法
百科名片
[
贪心算法的基本思路
[
例题分析
[
备注
[
附贪心算法成功案例之一
[
贪心算法在数学上的应用例子
[
简介
[
破译方法
[
字符类型
[
超级计算机与穷举法
[
简介
[
相关题目详解
背包问题
完全背包问题
多重背包问题
混合三种背包问题
二维费用的背包问题
分组的背包问题
有依赖的背包问题
泛化物品
背包问题问法的变化
01背包
[
01背包问题
问题描述 
算法分析
解决方案
优化时间复杂度
初始化的细节问题
小结
0-1背包问题可以定义如下:
[
装箱问题
论述
二次背包问题
分治法
[
分治法简介
贪心算法
百科名片
[
贪心算法的基本思路
[
例题分析
[
备注
[
附贪心算法成功案例之一
[
贪心算法在数学上的应用例子
欧几里得算法的概述
[
欧几里得算法原理
[
欧几里得算法设计
[
欧几里德算法的C语言版
[
欧几里德算法的C++/java语言版
[
求最大公约数的Stein算法
[
欧几里德算法的扩展
秦九韶简介
[
数书九章
秦九韶纪念馆
[
秦九韶算法
秦九韶
[
意义
[
PASCAL算法实现
[
分解质因数的方法
c语言 分解质因数算法
回溯法的一般描述 一般表达 可用回溯法求解的问题 P,通常要能表达为:对于已知的由 n 元组(x1,x2,…, xn)组成的一个状态空间 E={(x1,x2,…,xn)∣xi∈Si ,i=1,2,…,n},给定 关于 n 元组中的一个分量的一个约束集 D,要求 E 中满足 D 的全部约束条件的所有 n 元组。其中 Si 是分量 xi 的定义域,且 |Si| 有限,i=1,2,…,n。我们称 E 中满足 D 的全部约束条件的任一 n 元组为问题 P 的一个解。 解问题 P 的最朴素的方法就是枚举法,即对 E 中的所有 n 元组逐一地检测其是 否满足 D 的全部约束,若满足,则为问题 P 的一个解。但显然,其计算量是相当大 的。 规律 我们发现,对于许多问题,所给定的约束集 D 具有完备性,即 i 元组(x1,x2,…, xi)满足 D 中仅涉及到 x1,x2,…,xi 的所有约束意味着 j(j<i)元组(x1,x2,…, xj)一定也满足 D 中仅涉及到 x1,x2,…,xj 的所有约束,i=1,2,…,n。换句话 说,只要存在 0≤j≤n-1,使得(x1,x2,…,xj)违反 D 中仅涉及到 x1,x2,…,xj 的约束之一,则以(x1,x2,…,xj)为前缀的任何 n 元组(x1,x2,…,xj,xj+1,…, xn)一定也违反 D 中仅涉及到 x1,x2,…,xi 的一个约束,n≥i>j。因此,对于约 束集 D 具有完备性的问题 P,一旦检测断定某个 j 元组(x1,x2,…,xj)违反 D 中 仅涉及 x1,x2,…,xj 的一个约束,就可以肯定,以(x1,x2,…,xj)为前缀的任 何 n 元组(x1,x2,…,xj,xj+1,…,xn)都不会是问题 P 的解,因而就不必去搜 索它们、检测它们。回溯法正是针对这类问题,利用这类问题的上述性质而提出来的 比枚举法效率更高的算法。 空间树 回溯法首先将问题 P 的 n 元组的状态空间 E 表示成一棵高为 n 的带权有序树 T, 把在 E 中求问题 P 的所有解转化为在 T 中搜索问题 P 的所有解。树 T 类似于检索树, 它可以这样构造: 设 Si 中的元素可排成 xi(1) ,xi(2) ,…,xi(mi-1) ,|Si| =mi,i=1,2,…,n。 从根开始,让 T 的第 I 层的每一个结点都有 mi 个儿子。这 mi 个儿子到它们的双亲的 边,按从左到右的次序,分别带权 xi+1(1) ,xi+1(2) ,…,xi+1(mi) ,i=0,1,2,…, n-1。照这种构造方式,E 中的一个 n 元组(x1,x2,…,xn)对应于 T 中的一个叶 子结点,T 的根到这个叶子结点的路径上依次的 n 条边的权分别为 x1,x2,…,xn, 反之亦然。另外,对于任意的 0≤i≤n-1,E 中 n 元组(x1,x2,…,xn)的一个前缀 I 元组(x1,x2,…,xi)对应于 T 中的一个非叶子结点,T 的根到这个非叶子结点
的路径上依次的 I 条边的权分别为 x1,x2,…,xi,反之亦然。特别,E 中的任意一 个 n 元组的空前缀(),对应于 T 的根。 因而,在 E 中寻找问题 P 的一个解等价于在 T 中搜索一个叶子结点,要求从 T 的根到该叶子结点的路径上依次的 n 条边相应带的 n 个权 x1,x2,…,xn 满足约束 集 D 的全部约束。在 T 中搜索所要求的叶子结点,很自然的一种方式是从根出发, 按深度优先的策略逐步深入,即依次搜索满足约束条件的前缀 1 元组(x1i)、前缀 2 元组(x1,x2)、…,前缀 I 元组(x1,x2,…,xi),…,直到 i=n 为止。 在回溯法中,上述引入的树被称为问题 P 的状态空间树;树 T 上任意一个结点 被称为问题 P 的状态结点;树 T 上的任意一个叶子结点被称为问题 P 的一个解状态 结点;树 T 上满足约束集 D 的全部约束的任意一个叶子结点被称为问题 P 的一个回 答状态结点,它对应于问题 P 的一个解 [编辑本段] 用回溯法解题的一般步骤: (1)针对所给问题,定义问题的解空间; (2)确定易于搜索的解空间结构; (3)以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。 回溯法 C 语言举例 八皇后问题是能用回溯法解决的一个经典问题。 八皇后问题是一个古老而著名的问题。该问题是十九世纪著名的数学家高斯 185 0 年提出:在 8X8 格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇 后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。 用回溯法解决八皇后问题的 C 语言程序 #include #include int col[9]={0},a[9]; int b[17],c[17]; main() { int m,good; int i,j,k; char q; for(i=0;i<17;i++) { if(i<9) a[i]=1; b[i]=1;c[i]=1; }
to quit\n"); good=1; col[1]=1; m=1; while(col[0]!=1) { if(good) if(m==8) { for(i=1;i<9;i++) printf("col[%d] %d\n",i,col[i]); printf("input 'q' scanf("%c",&q); getchar(); if(q=='q'||q=='Q') exit(0); while(col[m]==8) { m--; a[col[m]]=b[m+col[m]]=c[8+m-col[m]]=1; } a[col[m]]=b[m+col[m]]=c[8+m-col[m]]=1; col[m]++; } else { a[col[m]]=b[m+col[m]]=c[8+m-col[m]]=0; m++; col[m]=1; } else { while(col[m]==8) { m--; a[col[m]]=b[m+col[m]]=c[8+m-col[m]]=1; } col[m]++; } good=a[col[m]]&&b[m+col[m]]&&c[8+m-col[m]];
');writeln;end } } 用回溯法解决八皇后问题的 PASCAL 语言程序 integer; program xy; var a:array[1..100]of b,c,d:array[-100..100]of boolean; n,i,j,k:integer; procedure try(k:integer); var i:integer; begin if k>n then begin for j:=1 to n do write(a[j],' else begin for i:=1 to n do if (b[i])and(c[k+i])and(d[k-i]){找条件} then begin a[k]:=i; b[i]:=false; c[k+i]:=false; d[k-i]:=false; try(k+1); b[i]:=true; c[k+i]:=true; d[k-i]:=true; end; end; end; begin readln(n); fillchar(b,sizeof(b),true); fillchar(c,sizeof(c),true); fillchar(d,sizeof(d),true); try(1); readln; end. {b[i]是判断横和竖,c[k+i]斜线/,d[k-i]斜线} 回溯法解决迷宫问题 
#include #include #define m 5 #define n 6 int sf=0; int mase[m][n]={{0,0,0,1,0,0},{0,1,0,0,0,0},{0,1,1,1,1,0},{0,0,0,0,0,1},{1,0,1,1, 0,0}}; void search(int x,int y) { if((x==m-1)&&(y==n-1)) sf=1; else { if((sf!=1)&&(y!=n-1)&&mase[x][y+1]==0) search(x,y+1); if((sf!=1)&&(x!=m-1)&&mase[x+1][y]==0) search(x+1,y); if((sf!=1)&&(y!=0)&&mase[x][y-1]==0) search(x,y-1); if((sf!=1)&&(x!=0)&&mase[x-1][y]==0) search(x-1,y); if(sf!=1) mase[x][y]=1; } } int main() { int //clrscr(); search(0,0); for(i=0;i
递归法 递归是设计和描述算法的一种有力的工具,由于它在复杂算法的描述中被经常采 用,为此在进一步介绍其他算法设计方法之前先讨论它。 能采用递归描述的算法通常有这样的特征:为求解规模为 N 的问题,设法将它分 解成规模较小的问题,然后从这些小问题的解方便地构造出大问题的解,并且这些规 模较小的问题也能采用同样的分解和综合方法,分解成规模更小的问题,并从这些更 小问题的解构造出规模较大问题的解。特别地,当规模 N=1 时,能直接得解。 递归算法的执行过程分递推和回归两个阶段。在递推阶段,把较复杂的问题(规 模为 n)的求解推到比原问题简单一些的问题(规模小于 n)的求解。例如上例中, 求解 fib(n),把它推到求解 fib(n-1)和 fib(n-2)。也就是说,为计算 fib(n),必须先计算 fib(n-1)和 fib(n-2),而计算 fib(n-1)和 fib(n-2),又必须先计算 fib(n-3)和 fib(n-4)。依 次类推,直至计算 fib(1)和 fib(0),分别能立即得到结果 1 和 0。在递推阶段,必须要 有终止递归的情况。例如在函数 fib 中,当 n 为 1 和 0 的情况。 在回归阶段,当获得最简单情况的解后,逐级返回,依次得到稍复杂问题的解, 例如得到 fib(1)和 fib(0)后,返回得到 fib(2)的结果,……,在得到了 fib(n-1)和 fib(n- 2)的结果后,返回得到 fib(n)的结果。 在编写递归函数时要注意,函数中的局部变量和参数只是局限于当前调用层,当 递推进入“简单问题”层时,原来层次上的参数和局部变量便被隐蔽起来。在一系列“简 单问题”层,它们各有自己的参数和局部变量。 由于递归引起一系列的函数调用,并且可能会有一系列的重复计算,递归算法的 执行效率相对较低。当某个递归算法能较方便地转换成递推算法时,通常按递推算法 编写程序。例如上例计算斐波那契数列的第 n 项的函数 fib(n)应采用递推算法,即从 斐波那契数列的前两项出发,逐次由前两项计算出下一项,直至计算出要求的第 n 项。 扩展阅读: 1.【问题】 2.编写计算斐波那契(Fibonacci)数列的第 n 项函数 fib(n)。 3.斐波那契数列为:0、1、1、2、3、……,即: 4. fib(0)=0; 5. fib(1)=1; 6. fib(n)=fib(n-1)+fib(n-2) (当 n>1 时)。 7.写成递归函数有: 8.int fib(int n)
9.{ if (n==0) return 0; 10. if (n==1) return 1; 11. if (n>1) return fib(n-1)+fib(n-2); 12.} 动态规划法 动态 规划法[dynamic programming method (DP)]是系统分析中一种常用的 方法 。在水 资源 规划中,往往 涉及到 地表 水库调 度、水 资源 量的合 理分配 、优 化调度 等问 题,而这 些问题 又可 概化为 多阶段 决策 过程问 题。动 态规 划法是 解决此 类问题 的 有效方法。动态规划法是 20 世纪 50 年代由贝尔曼(R. Bellman)等人提出,用来解 决多 阶段决 策过 程问题 的一种 最优 化方法 。所谓 多阶 段决策 过程,就是 把研究 问题 分 成若干个相互联系的阶段,由每个阶段都作出决策,从而使整个过程达到最优化。许多 实 际 问 题 利 用 动 态 规 划 法 处 理,常 比 线 性 规 划 法 更 为 有 效,特 别 是 对 于 那 些 离 散 型 问 题。实际上,动态规划法就是分多阶段进行决策,其基本思路是:按时空特点将复杂问 题 划 分 为 相 互 联 系 的 若 干 个 阶 段,在 选 定 系 统 行 进 方 向 之 后,逆 着 这 个 行 进 方 向,从 终 点 向 始 点 计 算,逐 次 对 每 个 阶 段 寻 找 某 种 决 策,使 整 个 过 程 达 到 最 优,故 又 称 为 逆 序 决 策过程。 [1]动态规划的基本思想 前文主要介绍了动态规划的一些理论依据,我们将前文所说的具有明显的阶段划 分和状态转移方程的动态规划称为标准动态规划,这种标准动态规划是在研究多阶段 决策问题时推导出来的,适合用于理论上的分析。在实际应用中,许多问题的阶段划 分并不明显,这时如果刻意地划分阶段法反而麻烦。一般来说,只要该问题可以划分
成规模更小的子问题,并且原问题的最优解中包含了子问题的最优解(即满足最优子 化原理),则可以考虑用动态规划解决。 动态规划的实质是分治思想和解决冗余,因此,动态规划是一种将问题实例分解 为更小的、相似的子问题,并存储子问题的解而避免计算重复的子问题,以解决最优 化问题的算法策略。 由此可知,动态规划法与分治法和贪心法类似,它们都是将问题实例归纳为更小 的、相似的子问题,并通过求解子问题产生一个全局最优解。其中贪心法的当前选择 可能要依赖已经作出的所有选择,但不依赖于有待于做出的选择和子问题。因此贪心 法自顶向下,一步一步地作出贪心选择;而分治法中的各个子问题是独立的(即不包 含公共的子子问题),因此一旦递归地求出各子问题的解后,便可自下而上地将子问 题的解合并成问题的解。但不足的是,如果当前选择可能要依赖子问题的解时,则难 以通过局部的贪心策略达到全局最优解;如果各子问题是不独立的,则分治法要做许 多不必要的工作,重复地解公共的子问题。 解决上述问题的办法是利用动态规划。该方法主要应用于最优化问题,这类问题 会有多种可能的解,每个解都有一个值,而动态规划找出其中最优(最大或最小)值 的解。若存在若干个取最优值的解的话,它只取其中的一个。但是首先要保证该问题 的无后效性,即无论当前取哪个解,对后面的子问题都没有影响.在求解过程中,该方法 也是通过求解局部子问题的解达到全局最优解,但与分治法和贪心法不同的是,动态 规划允许这些子问题不独立,(亦即各子问题可包含公共的子子问题)也允许其通过 自身子问题的解作出选择,该方法对每一个子问题只解一次,并将结果保存起来,避 免每次碰到时都要重复计算。 因此,动态规划法所针对的问题有一个显著的特征,即它所对应的子问题树中的 子问题呈现大量的重复。动态规划法的关键就在于,对于重复出现的子问题,只在第 一次遇到时加以求解,并把答案保存起来,让以后再遇到时直接引用,不必重新求解。 3、动态规划算法的基本步骤 设计一个标准的动态规划算法,通常可按以下几个步骤进行: (1)划分阶段:按照问题的时间或空间特征,把问题分为若干个阶段。注意这 若干个阶段一定要是有序的或者是可排序的(即无后向性),否则问题就无法用动态 规划求解。 (2)选择状态:将问题发展到各个阶段时所处于的各种客观情况用不同的状态 表示出来。当然,状态的选择要满足无后效性。
分享到:
收藏