- 浏览: 30926 次
最近访客 更多访客>>
文章分类
最新评论
-
samng508:
0.0.0.0也可以通过校验
关于网络上IP地址校验正则表达式的一点缺陷 -
yt3929033:
很好,谢谢分享
关于网络上IP地址校验正则表达式的一点缺陷 -
glamey:
[color=brown][/color][size=24][ ...
Java SE 6.0新功能:让界面更加绚丽
经常会遇到复杂问题不能简单地分解成几个子问题,而会分解出一系列的子问题。简单地采用把大问题分解成子问题,并综合子问题的解导出大问题的解的方法,问题求解耗时会按问题规模呈幂级数增加。<o:p></o:p>
为了节约重复求相同子问题的时间,引入一个数组,不管它们是否对最终解有用,把所有子问题的解存于该数组中,这就是动态规划法所采用的基本方法。以下先用实例说明动态规划方法的使用。<o:p></o:p>
【问题】 求两字符序列的最长公共字符子序列<o:p></o:p>
问题描述:字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列。令给定的字符序列X=“x0,x1,…,xm<st1:chmetcnv w:st="on" tcsc="0" numbertype="1" negative="True" hasspace="False" sourcevalue="1" unitname="”">-1”</st1:chmetcnv>,序列Y=“y0,y1,…,yk<st1:chmetcnv w:st="on" tcsc="0" numbertype="1" negative="True" hasspace="False" sourcevalue="1" unitname="”">-1”</st1:chmetcnv>是X的子序列,存在X的一个严格递增下标序列<i0,i1,…,ik-1>,使得对所有的j=0,1,…,k-1,有xij=yj。例如,X=“ABCBDAB”,Y=“BCDB”是X的一个子序列。<o:p></o:p>
给定两个序列A和B,称序列Z是A和B的公共子序列,是指Z同是A和B的子序列。问题要求已知两序列A和B的最长公共子序列。<o:p></o:p>
如采用列举A的所有子序列,并一一检查其是否又是B的子序列,并随时记录所发现的子序列,最终求出最长公共子序列。这种方法因耗时太多而不可取。<o:p></o:p>
考虑最长公共子序列问题如何分解成子问题,设A=“a0,a1,…,am<st1:chmetcnv w:st="on" tcsc="0" numbertype="1" negative="True" hasspace="False" sourcevalue="1" unitname="”">-1”</st1:chmetcnv>,B=“b0,b1,…,bm<st1:chmetcnv w:st="on" tcsc="0" numbertype="1" negative="True" hasspace="False" sourcevalue="1" unitname="”">-1”</st1:chmetcnv>,并Z=“z0,z1,…,zk<st1:chmetcnv w:st="on" tcsc="0" numbertype="1" negative="True" hasspace="False" sourcevalue="1" unitname="”">-1”</st1:chmetcnv>为它们的最长公共子序列。不难证明有以下性质:<o:p></o:p>
(1) 如果am-1=bn-1,则zk-1=am-1=bn-1,且“z0,z1,…,zk<st1:chmetcnv w:st="on" tcsc="0" numbertype="1" negative="True" hasspace="False" sourcevalue="2" unitname="”">-2”</st1:chmetcnv>是“a0,a1,…,am<st1:chmetcnv w:st="on" tcsc="0" numbertype="1" negative="True" hasspace="False" sourcevalue="2" unitname="”">-2”</st1:chmetcnv>和“b0,b1,…,bn<st1:chmetcnv w:st="on" tcsc="0" numbertype="1" negative="True" hasspace="False" sourcevalue="2" unitname="”">-2”</st1:chmetcnv>的一个最长公共子序列;<o:p></o:p>
(2) 如果am-1!=bn-1,则若zk-1!=am-1,蕴涵“z0,z1,…,zk<st1:chmetcnv w:st="on" tcsc="0" numbertype="1" negative="True" hasspace="False" sourcevalue="1" unitname="”">-1”</st1:chmetcnv>是“a0,a1,…,am<st1:chmetcnv w:st="on" tcsc="0" numbertype="1" negative="True" hasspace="False" sourcevalue="2" unitname="”">-2”</st1:chmetcnv>和“b0,b1,…,bn<st1:chmetcnv w:st="on" tcsc="0" numbertype="1" negative="True" hasspace="False" sourcevalue="1" unitname="”">-1”</st1:chmetcnv>的一个最长公共子序列;<o:p></o:p>
(3) 如果am-1!=bn-1,则若zk-1!=bn-1,蕴涵“z0,z1,…,zk<st1:chmetcnv w:st="on" tcsc="0" numbertype="1" negative="True" hasspace="False" sourcevalue="1" unitname="”">-1”</st1:chmetcnv>是“a0,a1,…,am<st1:chmetcnv w:st="on" tcsc="0" numbertype="1" negative="True" hasspace="False" sourcevalue="1" unitname="”">-1”</st1:chmetcnv>和“b0,b1,…,bn<st1:chmetcnv w:st="on" tcsc="0" numbertype="1" negative="True" hasspace="False" sourcevalue="2" unitname="”">-2”</st1:chmetcnv>的一个最长公共子序列。<o:p></o:p>
这样,在找A和B的公共子序列时,如有am-1=bn-1,则进一步解决一个子问题,找“a0,a1,…,am<st1:chmetcnv w:st="on" tcsc="0" numbertype="1" negative="True" hasspace="False" sourcevalue="2" unitname="”">-2”</st1:chmetcnv>和“b0,b1,…,bm<st1:chmetcnv w:st="on" tcsc="0" numbertype="1" negative="True" hasspace="False" sourcevalue="2" unitname="”">-2”</st1:chmetcnv>的一个最长公共子序列;如果am-1!=bn-1,则要解决两个子问题,找出“a0,a1,…,am<st1:chmetcnv w:st="on" tcsc="0" numbertype="1" negative="True" hasspace="False" sourcevalue="2" unitname="”">-2”</st1:chmetcnv>和“b0,b1,…,bn<st1:chmetcnv w:st="on" tcsc="0" numbertype="1" negative="True" hasspace="False" sourcevalue="1" unitname="”">-1”</st1:chmetcnv>的一个最长公共子序列和找出“a0,a1,…,am<st1:chmetcnv w:st="on" tcsc="0" numbertype="1" negative="True" hasspace="False" sourcevalue="1" unitname="”">-1”</st1:chmetcnv>和“b0,b1,…,bn<st1:chmetcnv w:st="on" tcsc="0" numbertype="1" negative="True" hasspace="False" sourcevalue="2" unitname="”">-2”</st1:chmetcnv>的一个最长公共子序列,再取两者中较长者作为A和B的最长公共子序列。<o:p></o:p>
定义c[j]为序列“a0,a1,…,ai<st1:chmetcnv w:st="on" tcsc="0" numbertype="1" negative="True" hasspace="False" sourcevalue="2" unitname="”">-2”</st1:chmetcnv>和“b0,b1,…,bj<st1:chmetcnv w:st="on" tcsc="0" numbertype="1" negative="True" hasspace="False" sourcevalue="1" unitname="”">-1”</st1:chmetcnv>的最长公共子序列的长度,计算c[j]可递归地表述如下:<o:p></o:p>
(1)c[j]=0 如果i=0或j=0;<o:p></o:p>
(2)c[j]= c[i-1][j-1]+1 如果I,j>0,且a[i-1]=b[j-1];<o:p></o:p>
(3)c[j]=max(c[j-1],c[i-1][j]) 如果I,j>0,且a[i-1]!=b[j-1]。<o:p></o:p>
按此算式可写出计算两个序列的最长公共子序列的长度函数。由于c[j]的产生仅依赖于c[i-1][j-1]、c[i-1][j]和c[j-1],故可以从c[m][n]开始,跟踪c[j]的产生过程,逆向构造出最长公共子序列。细节见程序。<o:p></o:p>
# include <stdio.h><o:p></o:p>
# include <string.h><o:p></o:p>
# define N 100<o:p></o:p>
char a[N],b[N],str[N];<o:p></o:p>
<o:p> </o:p>
int lcs_len(char *a, char *b, int c[ ][ N])<o:p></o:p>
{ int m=strlen(a), n=strlen(b), i,j;<o:p></o:p>
for (i=0;i<=m;i++) c[0]=0;<o:p></o:p>
for (i=0;i<=n;i++) c[0]=0;<o:p></o:p>
for (i=1;i<=m;i++)<o:p></o:p>
for (j=1;j<=m;j++)<o:p></o:p>
if (a[i-1]==b[j-1])<o:p></o:p>
c[j]=c[i-1][j-1]+1;<o:p></o:p>
else if (c[i-1][j]>=c[j-1])<o:p></o:p>
c[j]=c[i-1][j];<o:p></o:p>
else<o:p></o:p>
c[j]=c[j-1];<o:p></o:p>
return c[m][n];<o:p></o:p>
}<o:p></o:p>
<o:p> </o:p>
char *buile_lcs(char s[ ],char *a, char *b)<o:p></o:p>
{ int k, i=strlen(a), j=strlen(b);<o:p></o:p>
k=lcs_len(a,b,c);<o:p></o:p>
s[k]=’\<st1:chmetcnv w:st="on" tcsc="0" numbertype="1" negative="False" hasspace="False" sourcevalue="0" unitname="’">0’</st1:chmetcnv>;<o:p></o:p>
while (k>0)<o:p></o:p>
if (c[j]==c[i-1][j]) i--;<o:p></o:p>
else if (c[j]==c[j-1]) j--;<o:p></o:p>
else { s[--k]=a[i-1];<o:p></o:p>
i--; j--;<o:p></o:p>
}<o:p></o:p>
return s;<o:p></o:p>
}<o:p></o:p>
<o:p> </o:p>
void main()<o:p></o:p>
{ printf (“Enter two string(<%d)!\n”,N);<o:p></o:p>
scanf(“%s%s”,a,b);<o:p></o:p>
printf(“LCS=%s\n”,build_lcs(str,a,b));<o:p></o:p>
}<o:p></o:p>
1、动态规划的适用条件<o:p></o:p>
任何思想方法都有一定的局限性,超出了特定条件,它就失去了作用。同样,动态规划也并不是万能的。适用动态规划的问题必须满足最优化原理和无后效性。<o:p></o:p>
(1)最优化原理(最优子结构性质)<o:p></o:p>
最优化原理可这样阐述:一个最优化策略具有这样的性质,不论过去状态和决策如何,对前面的决策所形成的状态而言,余下的诸决策必须构成最优策略。简而言之,一个最优化策略的子策略总是最优的。一个问题满足最优化原理又称其具有最优子结构性质。<o:p></o:p>
<o:p> </o:p>
图2<o:p></o:p>
例如图2中,若路线I和J是A到C的最优路径,则根据最优化原理,路线J必是从B到C的最优路线。这可用反证法证明:假设有另一路径J’是B到C的最优路径,则A到C的路线取I和J’比I和J更优,矛盾。从而证明J’必是B到C的最优路径。<o:p></o:p>
最优化原理是动态规划的基础,任何问题,如果失去了最优化原理的支持,就不可能用动态规划方法计算。根据最优化原理导出的动态规划基本方程是解决一切动态规划问题的基本方法。<o:p></o:p>
(2)无后向性<o:p></o:p>
将各阶段按照一定的次序排列好之后,对于某个给定的阶段状态,它以前各阶段的状态无法直接影响它未来的决策,而只能通过当前的这个状态。换句话说,每个状态都是过去历史的一个完整总结。这就是无后向性,又称为无后效性。<o:p></o:p>
(3)子问题的重叠性<o:p></o:p>
动态规划算法的关键在于解决冗余,这是动态规划算法的根本目的。动态规划实质上是一种以空间换时间的技术,它在实现的过程中,不得不存储产生过程中的各种状态,所以它的空间复杂度要大于其它的算法。选择动态规划算法是因为动态规划算法在空间上可以承受,而搜索算法在时间上却无法承受,所以我们舍空间而取时间。<o:p></o:p>
所以,能够用动态规划解决的问题还有一个显著特征:子问题的重叠性。这个性质并不是动态规划适用的必要条件,但是如果该性质无法满足,动态规划算法同其他算法相比就不具备优势。<o:p></o:p>
2、动态规划的基本思想<o:p></o:p>
前文主要介绍了动态规划的一些理论依据,我们将前文所说的具有明显的阶段划分和状态转移方程的动态规划称为标准动态规划,这种标准动态规划是在研究多阶段决策问题时推导出来的,具有严格的数学形式,适合用于理论上的分析。在实际应用中,许多问题的阶段划分并不明显,这时如果刻意地划分阶段法反而麻烦。一般来说,只要该问题可以划分成规模更小的子问题,并且原问题的最优解中包含了子问题的最优解(即满足最优子化原理),则可以考虑用动态规划解决。<o:p></o:p>
动态规划的实质是分治思想和解决冗余,因此,动态规划是一种将问题实例分解为更小的、相似的子问题,并存储子问题的解而避免计算重复的子问题,以解决最优化问题的算法策略。<o:p></o:p>
由此可知,动态规划法与分治法和贪心法类似,它们都是将问题实例归纳为更小的、相似的子问题,并通过求解子问题产生一个全局最优解。其中贪心法的当前选择可能要依赖已经作出的所有选择,但不依赖于有待于做出的选择和子问题。因此贪心法自顶向下,一步一步地作出贪心选择;而分治法中的各个子问题是独立的(即不包含公共的子子问题),因此一旦递归地求出各子问题的解后,便可自下而上地将子问题的解合并成问题的解。但不足的是,如果当前选择可能要依赖子问题的解时,则难以通过局部的贪心策略达到全局最优解;如果各子问题是不独立的,则分治法要做许多不必要的工作,重复地解公共的子问题。<o:p></o:p>
解决上述问题的办法是利用动态规划。该方法主要应用于最优化问题,这类问题会有多种可能的解,每个解都有一个值,而动态规划找出其中最优(最大或最小)值的解。若存在若干个取最优值的解的话,它只取其中的一个。在求解过程中,该方法也是通过求解局部子问题的解达到全局最优解,但与分治法和贪心法不同的是,动态规划允许这些子问题不独立,(亦即各子问题可包含公共的子子问题)也允许其通过自身子问题的解作出选择,该方法对每一个子问题只解一次,并将结果保存起来,避免每次碰到时都要重复计算。<o:p></o:p>
因此,动态规划法所针对的问题有一个显著的特征,即它所对应的子问题树中的子问题呈现大量的重复。动态规划法的关键就在于,对于重复出现的子问题,只在第一次遇到时加以求解,并把答案保存起来,让以后再遇到时直接引用,不必重新求解。<o:p></o:p>
3、动态规划算法的基本步骤<o:p></o:p>
设计一个标准的动态规划算法,通常可按以下几个步骤进行:<o:p></o:p>
(1)划分阶段:按照问题的时间或空间特征,把问题分为若干个阶段。注意这若干个阶段一定要是有序的或者是可排序的(即无后向性),否则问题就无法用动态规划求解。<o:p></o:p>
(2)选择状态:将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来。当然,状态的选择要满足无后效性。<o:p></o:p>
(3)确定决策并写出状态转移方程:之所以把这两步放在一起,是因为决策和状态转移有着天然的联系,状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。所以,如果我们确定了决策,状态转移方程也就写出来了。但事实上,我们常常是反过来做,根据相邻两段的各状态之间的关系来确定决策。<o:p></o:p>
(4)写出规划方程(包括边界条件):动态规划的基本方程是规划方程的通用形式化表达式。<o:p></o:p>
一般说来,只要阶段、状态、决策和状态转移确定了,这一步还是比较简单的。动态规划的主要难点在于理论上的设计,一旦设计完成,实现部分就会非常简单。根据动态规划的基本方程可以直接递归计算最优值,但是一般将其改为递推计算,实现的大体上的框架如下:<o:p></o:p>
标准动态规划的基本框架<o:p></o:p>
1. 对fn+1(xn+1)初始化; {边界条件}<o:p></o:p>
for k:=n downto 1 do<o:p></o:p>
for 每一个xk∈Xk do<o:p></o:p>
for 每一个uk∈Uk(xk) do<o:p></o:p>
begin<o:p></o:p>
5. fk(xk):=一个极值; {∞或-∞}<o:p></o:p>
6. xk+1:=Tk(xk,uk); {状态转移方程}<o:p></o:p>
7. t:=φ(fk+1(xk+1),vk(xk,uk)); {基本方程(9)式}<o:p></o:p>
if t比fk(xk)更优 then fk(xk):=t; {计算fk(xk)的最优值}<o:p></o:p>
end;<o:p></o:p>
9. t:=一个极值; {∞或-∞}<o:p></o:p>
for 每一个x1∈X1 do<o:p></o:p>
11. if f1(x1)比t更优 then t:=f1(x1); {按照10式求出最优指标}<o:p></o:p>
12. 输出t;<o:p></o:p>
但是,实际应用当中经常不显式地按照上面步骤设计动态规划,而是按以下几个步骤进行:<o:p></o:p>
(1)分析最优解的性质,并刻划其结构特征。<o:p></o:p>
(2)递归地定义最优值。<o:p></o:p>
(3)以自底向上的方式或自顶向下的记忆化方法(备忘录法)计算出最优值。<o:p></o:p>
(4)根据计算最优值时得到的信息,构造一个最优解。<o:p></o:p>
步骤(1)~(3)是动态规划算法的基本步骤。在只需要求出最优值的情形,步骤(4)可以省略,若需要求出问题的一个最优解,则必须执行步骤(4)。此时,在步骤(3)中计算最优值时,通常需记录更多的信息,以便在步骤(4)中,根据所记录的信息,快速地构造出一个最优解。<o:p></o:p>
<o:p> </o:p>
【问题】 凸多边形的最优三角剖分问题<o:p></o:p>
问题描述:多边形是平面上一条分段线性的闭曲线。也就是说,多边形是由一系列首尾相接的直线段组成的。组成多边形的各直线段称为该多边形的边。多边形相接两条边的连接点称为多边形的顶点。若多边形的边之间除了连接顶点外没有别的公共点,则称该多边形为简单多边形。一个简单多边形将平面分为3个部分:被包围在多边形内的所有点构成了多边形的内部;多边形本身构成多边形的边界;而平面上其余的点构成了多边形的外部。当一个简单多边形及其内部构成一个闭凸集时,称该简单多边形为凸多边形。也就是说凸多边形边界上或内部的任意两点所连成的直线段上所有的点均在该凸多边形的内部或边界上。<o:p></o:p>
通常,用多边形顶点的逆时针序列来表示一个凸多边形,即P=<v0,v1,…,vn-1>表示具有n条边v0v1,v1v2,…,vn-1vn的一个凸多边形,其中,约定v0=vn 。<o:p></o:p>
若vi与vj是多边形上不相邻的两个顶点,则线段vivj称为多边形的一条弦。弦将多边形分割成凸的两个子多边形<vi,vi+1,…,vj>和<vj,vj+1,…,vi>。多边形的三角剖分是一个将多边形分割成互不重迭的三角形的弦的集合T。图1是一个凸多边形的两个不同的三角剖分。<o:p></o:p>
<o:p> </o:p>
(a) (b)<o:p></o:p>
图1 一个凸多边形的2个不同的三角剖分<o:p></o:p>
在凸多边形P的一个三角剖分T中,各弦互不相交且弦数已达到最大,即P的任一不在T中的弦必与T中某一弦相交。在一个有n个顶点的凸多边形的三角刮分中,恰好有n-3条弦和n-2个三角形。<o:p></o:p>
凸多边形最优三角剖分的问题是:给定一个凸多边形P=<v0,v1,…,vn-1>以及定义在由多边形的边和弦组成的三角形上的权函数ω。要求确定该凸多边形的一个三角剖分,使得该三角剖分对应的权即剖分中诸三角形上的权之和为最小。<o:p></o:p>
可以定义三角形上各种各样的权函数ω。例如:定义ω(△vivjvk)=| vivj |+| vivk |+| vkvj |,其中,| vivj |是点vi到vj的欧氏距离。相应于此权函数的最优三角剖分即为最小弦长三角剖分。<o:p></o:p>
(1)最优子结构性质<o:p></o:p>
凸多边形的最优三角剖分问题有最优子结构性质。事实上,若凸(n+1)边形P=<v0,v1 ,…,vn>的一个最优三角剖分T包含三角形v0vkvn,1≤k≤n-1,则T的权为3个部分权的和,即三角形v0vkvn的权,子多边形<v0,v1,…,vk>的权和<vk,vk+1,…,vn>的权之和。可以断言由T所确定的这两个子多边形的三角剖分也是最优的,因为若有<v0,v1,…,vk>或<vk,vk+1,…,vn>的更小权的三角剖分,将会导致T
相关推荐
动态规划算法是五大常用算法之一,属于多阶段决策问题的解决方法。该算法的核心思想是将问题分解为多个阶段,每个阶段都有其对应的状态和决策,以便通过决策序列来达成最优解。 动态规划算法的基本概念是指每次决策...
本资源“常用算法分析设计”深入探讨了多种重要的算法设计方法,旨在帮助开发者和学习者提升解决复杂问题的能力。 首先,我们来讨论分治策略。分治法是一种将大问题分解为小问题来解决的思路,它将复杂的问题划分为...
动态规划算法是五大常用算法之一,是解决多阶段决策问题的有效方法。它的基本思想是将问题分解为多个阶段,每个阶段都有其状态和决策,然后通过决策序列来解决问题。 动态规划算法的基本概念是:每次决策依赖于当前...
贪心算法、分治算法和动态规划是三种常用的算法设计策略,每种算法都有其特点和应用场景。下面我们将对这三种算法进行详细的比较和分析。 分治算法 分治算法是一种将原问题划分成若干个规模较小的子问题,然后递归...
8. **动态规划法**: 动态规划通过存储子问题的解,避免重复计算,以解决最优化问题。它可以解决背包问题、最长公共子序列等。动态规划的核心是状态转移方程,通过构建表格来存储中间结果。 这些算法设计方法各有...
【软考常用算法设计方法详解】 在信息技术领域,软件设计师经常需要解决各种复杂的问题,而算法设计是解决问题的关键。算法是一系列明确的指令,用于解决特定问题或执行特定任务。在软考(全国计算机技术与软件专业...
然而,随着问题规模的增长,更高级的算法设计方法如递推法、贪婪法、回溯法、分治法、动态规划法等变得更为重要,因为它们能够以更高效的方式解决复杂问题。在设计算法时,除了正确性和效率外,还要考虑算法的可读性...
这篇文档“常用算法设计方法”提供了一份详尽的指南,涵盖了多种广泛应用于计算机科学领域的算法。以下是对这些算法的详细介绍: 1. **排序算法**:排序是数据处理的基础,包括冒泡排序、插入排序、选择排序、快速...
"常用算法设计方法(word版)"为初学者和算法设计爱好者提供了一个系统的算法设计方法概述,涵盖了迭代法、穷举搜索法、递推法、贪婪法、回溯法、分治法、动态规划法等多种常用的算法设计方法,并提供了 C 语言编写...
标题和描述中提到的知识点主要包括:ACM竞赛、算法设计、数据结构、程序设计、计算机程序的结构、迭代法、穷举搜索法、递推法、贪婪法、回溯法、分治法、动态规划法、递归技术。下面我将详细介绍这些知识点。 首先...
本篇文章将深入探讨C++中常用的算法设计方法。 一、排序算法 1. 冒泡排序:通过不断交换相邻的逆序元素实现排序,适用于小规模数据或部分有序的数据。 2. 快速排序:采用分治策略,以一个元素为基准,将数组分为两...
以下是一些常用的算法设计方法及其详解: 1. **迭代法**:迭代法是一种通过不断逼近目标值来求解问题的方法,常用于求解方程的根或者优化问题。例如,牛顿法、二分法都是迭代法的实例。在ACM竞赛中,迭代法可以用于...
《软考常用算法设计方法一2》是一份关于软件工程师资格考试(软考)中算法设计部分的重要参考资料,包含了大约30页的DOC文档。这份资料深入浅出地讲解了在软件工程和软件开发过程中至关重要的算法设计技巧,旨在帮助...