算法分析防灾 leo.docx
《算法分析防灾 leo.docx》由会员分享,可在线阅读,更多相关《算法分析防灾 leo.docx(11页珍藏版)》请在冰豆网上搜索。
算法分析防灾leo
1、P类问题:
存在多项式时间(4分)算法的一类问题(1分)称作P类问题;
NP问题:
可以在多项式时间内验证一个解是否正确的问题
2、链式存储和连续存储的优缺点分析:
连续存储方式,如数组,通过下标可以直接访问数据,缺点是插入和删除操作比较麻烦;链表存储方式把逻辑上相邻的节点存储在物理上可能不相邻的单元,优点是可以充分利用所有存储空间,不会出现碎片现象,缺点是不能随机存储和访问数据。
3蛮力法
蛮力法是基于计算机运算速度快这一特性,在解决问题时采取的一种“懒惰”的策略。
这种策略不经过(或者说是经过很少的)思考,把问题的所有情况或所有过程交给计算机去一一尝试,从中找出问题的解。
蛮力策略的应用很广,具体表现形式各异,数据结构课程中学习的:
选择排序、冒泡排序、插入排序、顺序查找、朴素的字符串匹配等,都是蛮力策略具体应用。
比较常用还有枚举法、盲目搜索算法等
4分治算法框架
1)分治法思想:
将一个难以直接解决的大问题,分割成一些规模较小的几个相似问题,便于各个击破,分而治之;
2)适合用分治法策略的问题(应用)
当求解一个输入规模为n且取值又相当大的问题时,用蛮力策略效率一般得不到保证。
若问题能满足以下几个条件,就能用分治法来提高解决问题的效率。
1) 能将这n个数据分解成k个不同子集合,且得到k个子集合是可以独立求解的子问题,其中1<k≤n;
2) 分解所得到的子问题与原问题具有相似的结构,便于利用递归或循环机制;
在求出这些子问题的解之后,就可以推解出原问题的解;
3)算法框架
分治法的一般的算法设计模式如下:
Divide-and-Conquer(intn)/n为问题规模/
{if(n≤n0)/n0为可解子问题的规模/
{解子问题;
return(子问题的解);}
for(i=1;i<=k;i++)/分解为较小子问题p1,p2,……pk/
yi=Divide-and-Conquer(|Pi|);/递归解决Pi/
T=MERGE(y1,y2,...,yk);/合并子问题/
return(T);}
5贪婪算法
贪婪法又叫登山法,它的根本思想是逐步到达山顶,即逐步获得最优解。
贪婪算法没有固定的算法框架,算法设计的关键是贪婪策略的选择。
一定要注意,选择的贪婪策略要具有无后向性。
某状态以后的过程不会影响以前的状态,只与当前状态或以前的状态有关,称这种特性为无后效性。
因此,采用贪婪算法的问题较少。
如Hoffman树、构造最小生成树的Prim算法、Kruskal算法等,都属于贪婪算法。
6动态规划
1)动态规划的基本思想
动态规划方法的基本思想是,把求解的问题分成许多阶段或多个子问题,然后按顺序求解各子问题。
最后一个子问题就是初始问题的解
2)适合动态规划的问题特征
动态规划算法的问题及决策应该具有三个性质:
最优化原理、无后向性、子问题重叠性质
3)标准动态规划的基本框架
for(j=1;j<=m;j=j+1)//第一个阶段
xn[j]=初始值;
for(i=n-1;i>=1;i=i-1)//其它n-1个阶段
for(j=1;j>=f(i);j=j+1)//f(i)与i有关的表达式
xi[j]=j=max(或min){g(xi-1[j1——j2]),……,g(xi-1[jk——jk+1])};
t=g(x1[j1—j2]);//由最优解求解最优解的方案
print(x1[j1]);
for(i=2;i<=n-1;i=i+1)
{t=t-xi-1[ji];
for(j=1;j>=f(i);j=j+1)
if(t=xi[ji])break;
}
7回溯法
1)回溯法基本思想回溯法是在包含问题的所有解的解空间树中。
按照深度优先的策略,从根结点出发搜索解空间树,算法搜索至解空间树的任一结点时,总是先判断该结点是否满足问题的约束条件。
如果满足进入该子树,继续按深度优先的策略进行搜索。
否则,不去搜索以该结点为根的子树,而是逐层向其祖先结点回溯。
回溯法就是对隐式图的深度
2)算法框架1)问题框架设问题的解是一个n维向量(a1,a2,……,an),约束条件是ai(i=1,2,3……n)之间满足某种条件,记为f(ai)2)非递归回溯框架inta[n],i;初始化数组a[];i=1;While(i>0(有路可走))and([未达到目标])//还未回溯到头{if(i>n)//正在处理第i个元素搜索到一个解,输出;else{a[i]第一个可能的值;while(a[i]在不满足约束条件且在搜索空间内)a[i]下一个可能的值;if(a[i]在搜索空间内){标识占用的资源;i=i+1;}//扩展下一个结点else{清理所占的状态空间;i=i-1;}//回溯}}3)递归算法框架一般情况下用递归函数来实现回溯法比较简单,其中i为搜索深度。
inta[n];try(inti)
{if(i>n)输出结果;
elsefor(j=下界;j<=上界;j++)//枚举i所有可能的路径
{if(f(j))//满足限界函数和约束条件{a[i]=j;……//其它操作try(i+1);}
}回溯前的清理工作(如a[i]置空值等);}}3)应用
排列及排列树的回溯搜索;最优化问题的回溯搜索
8分支限界法
1)基本思想
分支搜索法也是一种在问题解空间上进行尝试搜索算法。
所谓“分支”是采用广度优先的策略,依次生成E-结点所有分支,也就是所有的儿子结点。
“限界”是加速搜索速度而采用的启发信息剪枝的策略。
和回溯法一样,在生成的节点中,抛弃那些不满足约束条件(或者说不可能导出最优可行解)的结点,其余节点加入活节点表。
然后从表中选择一个节点作为下一个E-节点。
2)算法框架如下:
search(T)//为找出最小成本答案结点检索T。
{leaf=0;初始化队;ADDQ(T);//根结点入队parent(E)=0;//记录扩展路径,当前结点的父结点
while(队不空){DELETEQ(E)//队首结点出队为新的E结点;for(E的每个儿子X)
if(s(X)
if(X是解结点)//x为叶结点 {U=min(cost(X),u);
leaf=x;}//方案的叶结点存储在leaf中}}
print(”leastcost=’,u);
while(leaf<>0)//输出最优解方案
{print(leaf);
leaf=parent(leaf);}
}
9、广度优先搜索
1)算法的基本思路
算法设计的基本步骤为:
1)确定图的存储方式; 2)图的遍历过程中的操作,其中包括为输出问题解而进行的存储操作;3)输出问题的结论。
2)算法框架
从广度优先搜索定义可以看出活结点的扩展是按先来先处理的原则进行的,所以在算法中要用“队”来存储每个E-结点扩展出的活结点。
为了算法的简洁,抽象地定义:
queue为队列类型,InitQueue()为队列初始化函数,EnQueue(Q,k)为入队函数,
QueueEmpty(Q)为判断队空函数,DeQueue(Q)为出队函数。
实际应用中,用数组或链表实现队列。
开辟数组visited记录visited结点的搜索情况。
在算法框架中以输出结点值表示“访问”。
A、邻接表表示图的广度优先搜索算法
intvisited[n];/n为结点个数/bfs(intk,graphhead[])
{inti; queueQ;edgenode*p;/定义队列/
InitQueue(Q);/队列初始化/
print(“visitvertex”,k);/访问源点vk/
visited[k]=1;
EnQueue(Q,k);/vk已访问,将其入队。
/
while(!
QueueEmpty(Q))/队非空则执行/
{i=DeQueue(Q);/vi出队为E-结点/
p=head[i].firstedge;/取vi的边表头指针/
while(p<>null)/扩展E-结点/
{if(visited[p->adjvex]=0)/若vj未访问过/
{print(“visitvertex”,p->adjvex);/访问vj/
visited[p->adjvex]=1;
EnQueue(Q,p->adjvex);}/访问过的vj人队/
p=p->next;}/找vi的下一邻接点/
}
B、邻接矩阵表示的图的广度优先搜索算法
bfsm(intk,graphg[][100],intn)
{inti,j;queueQ;
InitQueue(Q);
print(“visitvertex”,k);/访问源点vk/
visited[k]=1; EnQueue(Q,k);
while(notQueueEmpty(Q))
{i=DeQueue(Q);/vi出队/
for(j=0;j if(g[i][j]=1andvisited[j]=0)
{print(“visitvertex”,j);
visited[j]=1;
EnQueue(Q,j);}/访问过的vj入队/
}
}10、深度优先搜索
深度优先遍历首先访问出发点v,并将其标记为已访问过;然后依次从v出发搜索v的每个邻接点w。
若w未曾访问过,则以w为新的出发点继续进行深度优先遍历,直至图中所有和源点v有路径相通的顶点均已被访问为止。
若此时图中仍有未访问的顶点,则另选一个尚未访问的顶点作为新的源点重复上述过程,直至图中所有顶点均已被访问为止。
1)算法的基本思路算法设计的基本步骤为:
1)确定图的存储方式;2)遍历过程中的操作,其中包括为输出问题解而进行的存储操作;3)输出问题的结论。
4)一般在回溯前的应该将结点状态恢复为原始状态,特别是在有多解需求的问题中。
A、用邻接表存储图的搜索算法
intvisited[n];//n为节点个数,数组初始值为0graphhead[100];dfs(intk)/head图的顶点数组/{edgenode*ptr/ptr图的边表指针/visited[k]=1;/*记录已遍历过*/
print(“访问”,k);/*遍历顶点值*/
ptr=head[k].firstedge;/*顶点的第一个邻接点*/
while(ptr<>NULL)/*遍历至链表尾*/
{if(visited[ptr->vertex]=0)/*如过没遍历过*/dfs(ptr->vertex);/*递归遍历*/
ptr=ptr->nextnode;}/*下一个顶点*/
}算法分析:
n图中有n个顶点,e条边。
扫描边的时间为O(e)。
遍历图的时间复杂性为O(n+e)。
B、用邻接矩阵存储图的搜索算法
intvisited[n];graphg[100][100],intn;dfsm(graphg,intk)
{intj;
print(“访问”,k);
visited[k]=1;
for(j=1;j<=n;j++)//依次搜索vk的邻接点
if(g[k][j]=1andvisited[j]=0)
dfsm(g,j)//(vk,vj)∈E,且vj未访问过,故vj为新出发点
}
算法分析:
查找每一个顶点的所有的边,所需时间为O(n),遍历图中所有的顶点所需的时间为O(n2)。
设计题
【例37】求n次二项式各项的系数,已知二项式的展开式为:
问题分析:
若只用的数学组合数学的知识,直接建模
k=0,1,2,3……n。
用这个公式去计算,n+1个系数,即使你考虑到了前后系数之间的数值关系,算法中也要有大量的乘法和除法运算,效率是很低的。
数学知识是各阶多项式的系数呈杨辉三角形的规律
(a+b)01
(a+b)11 1
(a+b)2 1 2 1
(a+b)31 331
(a+b)414641
(a+b)5……
则求n次二项式的系数的数学模型就是求n阶杨辉三角形。
算法设计要点:
除了首尾两项系数为1外,当n>1时,(a+b)n的中间各项系数是(a+b)n-1的相应两项系数之和,如果把(a+b)n的n+1的系数列为数组c,则除了c
(1)、c(n+1)恒为1外,设(a+b)n的系数为c(i),(a+b)n-1的系数设为c’(i)。
则有:
c(i)=c’(i)+c’(i-1)而当n=1时,只有两个系数c
(1)和c
(2)(值都为1)。
不难看出,对任何n,(a+b)n的二项式系数可由(a+b)n-1的系数求得,直到n=1时,两个系数有确定值,故可写成递归子算法。
复杂度:
O(n2)
coeff(inta[],intn)
{if(n==1)
{a[1]=1;
a[2]=1;}
else
{coeff(a,n-1);
a[n+1]=1;
for(i=n;i>=2;i--)
a[i]=a[i]+a[i-1];
a[1]=1;
}
}
main()
{inta[100],i,n;
input(n);
for(i=1;i<=n;i=i+1)
input(a[i]);
coeff(a,n);
for(i=1;i<=n;i=i+1)
print(a[i]);
}
【例40】楼梯上有n阶台阶,上楼可以一步上1阶,也可以一步上2阶,编写算法计算共有多少种不同的上楼梯方法。
数学模型:
此问题如果按照习惯,从前向后思考,也就是从第一阶开始,考虑怎么样走到第二阶、第三阶、第四阶……,则很难找出问题的规律;而反过来先思考“到第n阶有哪几种情况?
”,答案就简单了,只有两种情况:
1) 从第n-1阶到第n阶;
2) 从第n-2阶到第n阶。
记n阶台阶的走法数为f(n),则
1n=1
f(n)=2n=2
f(n-1)+f(n-2)n>2
算法设计:
算法可以用递归或循环完成。
下面是问题的递归算法。
算法如下:
main()
{intn:
;
print('n=');
input(n);
print('f(',n,')=',f(n));
}
f(intn)
{if(n=1)return
(1);
if(n=2)return
(2);
else
return(f(x-1)+f(x-2));
}
【例1】兔子繁殖问题
问题描述:
一对兔子从出生后第三个月开始,每月生一对小兔子。
小兔子到第三个月又开始生下一代小兔子。
假若兔子只生不死,一月份抱来一对刚出生的小兔子,问一年中每个月各有多少只兔子。
问题分析:
因一对兔子从出生后第三个月开始每月生一对小兔子,则每月新下小兔子的对儿数(用斜体数字表示)显然由前两个月的小兔子的对儿数决定。
则繁殖过程如下:
一月二月三月四月五月六月……
111+1=22+1=33+2=55+3=8……
数学建模:
y1=y2=1,yn=yn-1+yn-2,n=3,4,5,……。
算法2:
表4-1递推迭代表达式
123456789
abc=a+ba=b+cb=a+cc=a+ba=b+cb=a+c……
由此归纳出可以用“c=a+b;a=b+c;b=c+a;”做循环“不变式”。
算法2如下:
main()
{inti,a=1,b=1;
print(a,b);
for(i=1;i<=4;i++)
{c=a+b;a=b+c;b=c+a;
print(a,b,c);}
}
【例5】穿越沙漠问题用一辆吉普车穿越1000公里的沙漠。
吉普车的总装油量为500加仑,耗油率为1加仑/公里。
由于沙漠中没有油库,必须先用这辆车在沙漠中建立临时油库。
该吉普车以最少的耗油量穿越沙漠,应在什么地方建油库,以及各处的贮油量。
数学模型:
根据耗油量最少目标的分析,下面从后向前分段讨论。
第一段长度为500公里且第一个加油点贮油为500加仑。
第二段中为了贮备油,吉普车在这段的行程必须有往返。
下面讨论怎样走效率高:
1)首先不计方向这段应走奇数次(保证最后向前走)。
2)每次向前行进时吉普车是满载。
3)要能贮存够下一加油点的贮油量,路上耗油又最少。
下图是满足以上条件的最佳方案,此段共走3次:
第一、二次来回耗油2/3贮油1/3,第三次耗油1/3贮油2/3,所以第二个加油点贮油为1000加仑。
由于每公里耗油率为1加仑,则此段长度为500/3公里。
第三段与第二段思路相同。
下图是一最佳方案此段共走5次:
第一、二次来回耗油2/5贮油3/5,第三、四次来回耗油2/5贮油3/5,第五次耗油1/5贮油4/5,第三个加油点贮油为1500加仑。
此段长度为500/5。
……500/5公里<———500/3公里———><———<———500公里第一———>第二———>第三终点<——贮油点(500)<——贮油点(1000)<———贮油点(1500)……图4-4贮油点及贮油量示意综上分析,从终点开始分别间隔500,500/3,500/5,500/7,……(公里)设立贮油点,直到总距离超过1000公里。
每个贮油点的油量为500,1000,1500,……。
算法设计:
由模型知道此问题并不必用倒推算法解决(只是分析过程用的是倒推法),只需通过累加算法就能解决。
变量说明:
dis表示距终点的距离,1000-dis则表示距起点的距离,k表示贮油点从后到前的序号。
desert(){intdis,k,oil,k;dis=500;k=1;oil=500;do{print(“storepoint”,k,”distance”,1000-dis,”oilquantity”,oil);k=k+1;dis=dis+500/(2*k-1);oil=500*k;}while(dis<1000)oil=500*(k-1)+(1000-dis)*(2*k-1);print(“storepoint”,k,”distance”,0,”oilquantity”,oil);}
走迷宫问题
方法一:
广度优先搜索算法:
算法设计:
从入口开始广度优先搜索可到达的方格入队,再扩展队首的方格,直到搜索到出口时算法结束。
对于迷宫中任意一点A(Y,X),有四个搜索方向:
向上A(Y-1,X)向下A(Y+1,X)向左A(Y,X-1)向右A(Y,X+1)当对应方格可行(值为0),就扩展为活结点。
数据结构设计:
用数组做队的存储空间,队中结点有三个成员:
行号、列号、前一个方格在队列中的下标。
搜索过的方格不另外开辟空间记录其访问的情况,而是用迷宫原有的存储空间置元素值为“-1”时,标识已经访问过该方格。
为了构造循环体,用数组fx={1,-1,0,0}、fy={0,0,-1,1}模拟上下左右搜索时的下标的变化过程。
intmaze[8][8]={{0,0,0,0,0,0,0,0},{0,1,1,1,1,0,1,0},{0,0,0,0,1,0,1,0},{0,1,0,0,0,0,1,0},{0,1,0,1,1,0,1,0},{0,1,0,0,0,0,1,1},{0,1,0,0,1,0,0,0},{0,1,1,1,1,1,1,0}};
struct{intx,y,pre;}sq[100];
intqh,qe,i,j,k;
main(){search();}search()
{qh=0;qe=1;sq[1].pre=0;sq[1].x=1;sq[1].y=1;maze[1][1]=-1;
while(qh<>qe)/当队不空/
{qh=qh+1;/结点出队/
for(k=1;k<=4;k++)/扩展结点/
{i=sq[qh].x+fx[k];j=sq[qh].y+fy[k]; if(check(i,j)=1) {qe=qe+1;/结点入队/
sq[qe].x=i; sq[qe].y=j;
sq[qe].pre=qh; maze[i][j]=-1;
if(sq[qe].x=8andsq[qe].y=8){out();return;}}}print(“NoAvailableWay.”);}check(inti,intj) {intflag=1;if(i<1ori>8orj<1orj>8)//是否在迷宫flag=0;
if(maze[i][j]=1ormaze[i][j]=-1)//是否可行 flag=0;
returnflag;
}out()//输出路径{print(“(“,sq[qe].x,”,”,sq[qe].y,”)”);
while(sq[qe].pre<>0) {qe=sq[qe].pre;
print('--',”(“,sq[qe].x,”,”,sq[qe].y,”)”);
}}算法分析:
算法的时间复杂度并不复杂是O(n),算法的空间复杂性为O(n2),包括图本身的存储空间和搜索时辅助空间“队”的存储空间。
走迷宫问题
方法二:
深度优先搜索算法:
算法设计:
深度优先搜索,就是一直向着可通行的下一个方格行进,直到搜索到出口就找到一个解。
若行不通时,则返回上一个方格,继续搜索其它方向。
数据结构设计:
我们还是用迷宫本身的存储空间,除了记录走过的信息,还要标识是否可行:
maze[i][j]=3标识走过的方格maze[i][j]=2标识走入死胡同的方格这样,最后存储为“3”的方格为可行的方格。
而当一个方格四个方向都搜索完还没有走到出口,说明该方格或无路可走或只能走入了“死胡同”。
算法如下:
intmaze[8][8]={{0,0,0,0,0,0,0,0},{0,1,1,1,1,0,1,0},{0,0,0,0,1,0,1,0},{0,1,0,0,0,0,1,0},{0,1,0,1,1,0,1,0},{0,1,0,0,0,0,1,1},{0,1,0,0,1,0,0,0},{0,1,1,1,1,1,1,0}},
fx[4]={1,-1,0,0},fy[4]={0,0,-1,1},i,j,k,total;main(){inttotal=0;
maze[