天津理工大学研究生学位课《算法设计与分析》期末大作业

本篇博客深入解析了研究生课程中的算法设计与分析问题,涵盖渐进表达、时间复杂度比较、货币套汇问题、哈夫曼树与编码、动态规划应用及优化。通过实例探讨了算法优劣标准、多项式计算优化、网络可靠性路径和人力资源计划的动态规划解法。
摘要由CSDN通过智能技术生成

2022~ 2023学年度第一学期 研究生学位课《 算法设计与分析 》 期末大作业

2022级电子信息天理研究生

一、简答题

1.若,写出用Θ、Ω和О描述f(n) 的渐进表达。(7分)

答:
属于T(n)=aT(n/b)+cnk的形式,其中cnk表示问题分解成子问题和将子问题的解合并成原问题的解的时间。
此时a=9,b=3,k=1,cnk=n。所以f(n)=Θ(nlogba)=Ω(n)=O(n2)

2.求解某一问题的算法1在最坏情形下的时间复杂度是,算法2在最坏情形下的时间复杂度是,且,则算法1与算法2在最坏情形下的时间代价消耗上的优劣关系如何?(8分)

答:
对于T1来说它表示随着问题规模n的增大,算法的执行时间的增长率与f(n)的增长率相同,一般指的是最坏情况下的时间复杂度,也就是算法运行时间的上界。而对于T2来说,大Ω记号看成n的一类函数。n趋向无穷大时,T2(n)的增长趋势,始终超过g(n)。大Ω记号也表示为渐近下界,所以算法1的时间消耗要大于算法2。

3.图的单源最短路径问题用Dijkstra算法求解时,若某些边的权值为负数,先对图中的每一条边加上一个相同的充分大的正数使得每一条边的权值非负,然后再用Dijkstra算法求解即可得到原问题的解。若该方法可行,请说明理由;若该方法不可行,请举例说明。(10)

答:
该方法可行,首先用Dijkstra算法求解单源最短路径问题时,若某些边的权值为负数,Dijkstra算法是肯定失效的,但是如题中所提示的思想,先对图中的每一条边加上一个相同的充分大的正数使得每一条边的权值非负,然后再用Dijkstra算法求解即可得到原问题的解。
(1)核心思想:利用约翰逊Johnson算法来辅助Dijkstra算法进行求解。所以核心算法为约翰逊Johnson算法来计算出每条边加上的充分大的正数。
(2)翰逊Johnson算法流程:Johnson算法的核心是对所有边权重重新标记。新标号的边权转化为非负的,从而使得Dijkstra算法在新图上可用。
①建立超级源点 src 向所有的点连一条权重为 0 的有向边(这样其他点不会走到 src)。
②以 src 为源点跑 Bellman-Ford 确定图中是否有负环并确定 h[i] 的值,有负环直接结束。
③以每一个原图中的点为起点,以 w(u,v)+h[u]−h[v] 为边的权重跑 V 次 Dijkstra。
④得到的距离数组 dis[u][v] 需要 −h[u]+h[v] 才能得出真实的距离,注意特殊处理无法走到的情况的值。
(3)算法详细步骤:
这里我们引入一个类似于势能和每个结点绑定的值h[i],考虑边<u,v>权重为w<u,v>,重新标记边权为:在这里插入图片描述
,这样标记的好处是对于一条路径v0,v1,…,vn,原图的长度为:sum=w(v0,v1)+w(v1,v2)+…+w(vn-1,vn),而新图的长度为:
在这里插入图片描述
经过处理后,我们发现原图与新图路径长度的差值其实只与起止位置有关,与路径无关,原图的路径和新图的最短路径的选择是等价的。
(4)算法评估:
① 空间复杂度:Bellman-Ford 我们只需要一个额外的 h 数组,Dijkstra 需要 O(V) 的空间不赘述。如果每一轮我们都直接将求出的最短路都输出而不是存在一个 O(V2) 的数组中的话,那么额外空间就是 O(V) 的,存图的空间为 O(V+E) 总复杂度为 O(V+E)。
② 时间复杂度:Bellman-Ford 为 O(VE) 。如果使用优化的 Dijkstra 则总复杂度为 O(V(E+V)logE)。

4.人们希望通过一系列的货币兑换能够从1元某种货币换得大于1元的同种货币(例如:美元->日元->加元->欧元->美元),这种操作俗称“套汇”. 因此要解决的问题是:①从某一固定货币出发,能否找到这样的货币序列,达到获得收益的目的;②求能够得到最大收益的货币序列. 要求: 1)用图模型来准确地建模描述该问题;2)分析一下若采用穷举法解该问题应付出的最大计算量(可对n=8时计算).(10)

答:

步骤一:

构造图G=(V,E),其中顶点为n种货币Ci1,Ci2…Cin,设有关兑换率的矩阵为R,R[i,j]表示一单位货币ci能够兑换cj的单位数。由题可得:R[i1,i2],R[i2,i3]…R[iR-1,i R],R[iR,i1]>1,即:

1/(R[i1,i2]*R[i2,i3]…R[iR-1,iR]*R[iR,i1])<1

两边取对数得:

lg(1/R[i1,i2])+lg(1/R[i2,i3])+…+lg(1/R[iR-1,iR])+lg(1/R[iR,i1])<0

则可设边(i1,i2)的权值为lg(1/R[i1,i2]),即为-lg(R[i1,i2]),此问题即转换为寻找一个负回路Ci1,Ci2…Cik,Ci1使得 -(lgR[i1,i2]+lgR[i2,i3]+…lgR[iR,i1])<0,可以使用Floyd算法求解,此时最坏的时间复杂度为O(n^3)

步骤二:
假设需要兑换的货币为Ci,则一共有C81=8种选择,然后选择第一次兑换的货币公有C71=7种选择,依次选择,最后选择到C1,此时的复杂度为C(8!),若采用穷举法解决该问题是,时间复杂度为O(n!)。

5.什么是哈夫曼(Huffman)树,简述哈夫曼编码过程,哈夫曼编码过程是按照什么算法设计方法设计的?有n个叶节点的哈夫曼树一定有多少个节点并给出简单证明。(10分)

答:

(1)哈夫曼(Huffman)树:哈夫曼树又称最优二叉树,是一种带权路径长度最短的二叉树。所谓树的带权路径长度,就是树中所有的叶结点的权值乘上其到根结点的路径长度(若根结点为0层,叶结点到根结点的路径长度为叶结点的层数)。
(2)哈夫曼编码过程:哈夫曼编码是基于哈夫曼树而产生的一种编码,如果已经构建完成哈夫曼树后,哈夫曼编码就是左子树上的为0,右子树上的为1,从根节点扫描下来到叶子节点,编码完成后此过程称为哈夫曼编码过程,而输出的值为哈夫曼编码。
(3)哈弗曼树编码过程的算法设计方法:运用特定的数据结构实现哈夫曼编码。哈夫曼编码(Huffman Coding)是一种编码方式,以哈夫曼树─即最优二叉树,带权路径长度最小的二叉树,经常应用于数据压缩。在计算机信息处理中,“哈夫曼编码”是一种一致性编码法(又称"熵编码法"),用于数据的无损耗压缩。这一术语是指使用一张特殊的编码表将源字符(例如某文件中的一个符号)进行编码。这张编码表的特殊之处在于,它是根据每一个源字符出现的估算概率而建立起来的(出现概率高的字符使用较短的编码,反之出现概率低的则使用较长的编码,这便使编码之后的字符串的平均期望长度降低,从而达到无损压缩数据的目的)。这种方法是由David.A.Huffman发展起来的。
(4)有n个叶节点的哈夫曼树一定有2n-1个节点
证明:
两个节点合成一个节点,变成一个结点,两个叶子结点(两个叶结点,一个根结点,共三个结点)满足2n-1;这棵树与另一个节点又组成一棵树,这样增加了两个结点,多了一个叶结点,又满足2n-1;这样继续下去,都是多两个结点,多一个叶子结点。所以满足一棵有n个叶子结点的哈夫曼树共有2n-1个结点。

6.简述动态规划的基本原理和方法,说明使用动态规划方法设计求解算法的适用问题的特点和基本步骤。简述用动态规划方法与分治方法所设计的算法的共同点和不同点。(10分)

答:
(1)动态规划的基本原理和方法:
① 将原问题分解为n个子问题:将原问题分解为若干个子问题,子问题和原问题形式相同或者类似,只不过规模变小,当子问题全部解决时,原问题即解决。子问题的解一旦求出就被保存,所以每个子问题只需要求解一次。
② 确定状态:用动态规划解决问题,经常碰到的情况是,K个整形变量能构成一个状态,我们用一个K维的数组来存储各个状态的值。注意这里的值并不一定是一个数,而可能是结构体等。一个状态下的值通常是一个或者多个子问题的解。
③ 确定一些初始状态(边界条件)的值:如果使用记忆化搜索,即设置递归出口,如果使用递推,就需要将这些值填入dp数组。
④ 确定状态转移方程:求出不同状态之间是如何迁移的,即如何从一个或者多个值已知的状态,求出另一个状态的值。状态的迁移通常可以用递推公式表示,该公式就是状态迁移方程。
(2)动态规划方法设计求解算法的适用问题的特点:
① 问题具有最优子结构性质:如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质。
② 无后效性:当前的若干个状态值一旦确定,则此后过程的演变就只和这若干个状态的值有关,和之前是采取那种手段、哪条路径演变到当前的这若干个状态无关。事实上,不满足无后效性的问题分解是写不出状态转移方程的。不过这也与我们分划问题涉及状态的艺术有关。
(3)动态规划方法设计求解算法的适用问题的基本步骤:
① 划分阶段:按照问题的时间或空间特征,把问题分为若干个阶段。注意这若干个阶段一定要是有序的或者是可排序的(即无后向性),否则问题就无法用动态规划求解。
② 选择状态:将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来。当然,状态的选择要满足无后效性。
③ 确定决策并写出状态转移方程:之所以把这两步放在一起,是因为决策和状态转移有着天然的联系,状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。所以,如果我们确定了决策,状态转移方程也就写出来了。但事实上,我们常常是反过来做,根据相邻两段的各状态之间的关系来确定决策。
④ 写出规划方程(包括边界条件):动态规划的基本方程是规划方程的通用形式化表达式。一般说来,只要阶段、状态、决策和状态转移确定了,这一步还是比较简单的。动态规划的主要难点在于理论上的设计,一旦设计完成,实现部分就会非常简单。根据动态规划的基本方程可以直接递归计算最优值,但是一般将其改为递推计算。实际应用当中经常不显式地按照上面步骤设计动态规划,而是按以下几个步骤进行:
(i)分析最优解的性质,并刻划其结构特征。
(ii)递归地定义最优值。
(iii)以自底向上的方式或自顶向下的记忆化方法(备忘录法)计算出最优值。
(iiii)根据计算最优值时得到的信息,构造一个最优解。
(4)动态规划方法与分治方法所设计的算法的共同点和不同点:
① 相同点:动态规划通常用于求解最优解问题,与分治法类似,其基本思想也是将待求解问题分解成若干子问题,先求解子问题,然后从这些子问题的解得到原问题的解。
② 不同点:分治法是将原问题分解为多个子问题,利用递归对各个子问题独立求解,最后利用各子问题的解进行合并形成原问题的解。分治法将分解后的子问题看成是相互独立的。若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题会被重复计算多次。而动态规划的做法是将已解决子问题的答案保存下来,动态规划将分解后的子问题理解为相互间有联系,有重叠的部分。在需要子问题答案的时候便可直接获得,而不需要重复计算,这样就可以避免大量的重复计算,提高效率。

二、算法的设计与分析问题(共45分)

1.判别一个算法优劣的基本准则有哪些?按照这些准则要求,设计一个计算多项式数值的尽可能好的算法,并指出其算法时间复杂度(17分).

在这里插入图片描述

(1)算法优劣的基本准则:
①正确性、可读性、健壮性:对不合理输入的反映能力和处理能力
②时间复杂度:估算程序指令的执行次数(执行时间)
③空间复杂度:估算所需占用的存储空间
一个好的算法要好,执行次数越少,所占用的存储空间越小,也就是时间复杂度要低,空间复杂度也要低。
(2)算法设计:
方法一(直观累加算法):
① 核心思想:
根据多项式的直观方法运用累加“+=”和C++中幂函数“pow()”进行求解。
核心for循环{
P(x)+=下标为i的系数 * x^i;
}
② C++核心代码:

double  polynomialsum( int n;double a[];double x)
{
  double P=0;
  for(int i = 0; i <= n; i++)
      P += a[i] * pow(x,i);
  return P;
}

③ 算法评估:直观累加算法比较直接且容易想到,但是正是因为如此,导致时间复杂度太大,为O(xn),为多项式的max次数,当多项式数据很大的时候,算法效率很差。
方法二(类递归算法):
① 核心思想:将已知多项式进行化简,每一步提出x,并进行分离。于是此多项式求和问题变成从内向外循环,推导过程如下:
在这里插入图片描述
② 核心for循环{
P(x)=逆序系数+ x * s;
}
③ C++核心代码:

double polynomialsum(int n;double a[];double x)
{
  double P = 0;
  for(int i = n; i >= 0; i--)
    P = a[i] + x * P;
}

④详细代码:

#include<stdio.h>
#include<time.h>
#include<math.h>
clock_t start,stop;
double duration;
#define maxn 10
double f1(int n,double a[],dobule x);
double f2(int n,double a[],dobule x);
int main(){
int i;
double a[maxn]
for(i=0;i<maxn;i++){
a[i]=(double)i;
}
start=clock();
f2(maxn-1,a,1.1);
stop=clock();
duration=((double)(stop-start))/CLK_TCK;
printf(“ticks1=%f\n”,(double)(stop-start));
printf(“duration2=%6.2e\n”,duration);
return 0;
}
double f1(int n , double a[],double x){
int i;
double p=a[n]
for(i=n;i>0;i--){
p=a[i-1]+x*p;
}
return p;
}

⑤ 算法评估:输入时注意要按指数递减逆向输入各个单项式的系数(an、an-1…a1、a0),类递归算法将复杂的方法一的时间复杂度缩短为线性时间,算法时间复杂度:O(n),是一种比较优秀的算法。

2.已知: 有向图表示由个用户结点组成的网络. 若结点相邻,则可靠性函数表示从结点向结点传送数据成功的概率, ,显然,从结点经过结点向结点传送数据成功的概率(可靠性值)应为. 要求: 设计一个有效算法,求从结点向该网上其它结点传送数据的最可靠(即传送数据成功的概率最大)路径(18分).

算法设计:
(1)核心思路:
结合最短路径思想和概率乘法原理,最短路径选取Floyd算法,再结合数理统计的概率乘积知识。在有向图G中的每条边分别用可靠性函数r(a,b)来计算出结点a向结点b传送数据成功的概率来当这条边的权值,权值代表的是概率,权值为0代表两个用户节点之间不能传送数据,所以一开始的初始化都赋值为0,自身与自身可以传送数据,然后就是使用Floyd算法找出从结点s向该网上其他结点传送数据的最可靠的路径。
(2)算法思想:
已知网络G上各节点有向路(或路)的完好概率为0<=r(a,b)<=1,下面过程中出现的顶点设为S,S1,S2,…Sk,T。设一条由顶点S到顶点T的有向路(或路)所经过的顶点序号依次为{S,S1,S2,…Sk,T},则这条路所经过的各弧的完好概率分别为rss1,rs1s2,…,rskT。这条路的总完好概率rST是它经过的所有弧(或边)完好概率的乘积。因此,目标可以转换为寻找一条使rST取极大值的有向路(或路)的问题。在网络G中,其完好概率rij有特殊的规定:当i=j时,rij=1;当vivj不属于E时,rij=0。从而,可定义的完好概率矩阵A=(rij)n*n,其中rij为顶点vi与vj之间边的完好概率。当网络中某两点不临接时,约定其完好概率为0:
在这里插入图片描述

这样所求问题就转换成在矩阵A=(rij)nn上找从顶点S至顶点T的最短路径问题。因此最大可靠路径的算法是最短路径算法的一个变种,所以可以使用Floyd标号法由上述矩阵A=(rij)nn来求出S到T的最短路,从而,所求出的这条最短路径就是原网络的最大可靠路,其可靠概率为rST=exp(-dST),其中dST为矩阵A中从S到T的最短路经长.
(3)核心算法:

function [P p f]=p_pathf(A)
[m n]=size(A);
f=0;f=0表示找到路,否则f=1
B=zeros(m,n);
for i=1:m对原矩阵进行转换
    for j=1:n
        if A(i,j)>0&A(i,j)<1
            B(i,j)=-log(A(i,j));
        elseif A(i,j)==0
            B(i,j)=inf;
        end
    end
end
[P d]=Floyd(B);利用Floyd算法求最短路
if d<inf
    p=1;
    for i=1:(length(P)-1)
        p=p*A(P(i),P(i+1));计算最短路径的完好概率
    end
    p;
else
    p=0;
    P=0;
    f=1;
end

3.试用动态规划方法设计一个算法求解下述人力资源计划问题(10分) 考虑某企业有一个工程必须要在T个单位时段内完成(每个单位时段可以看成是一日、一周或一个月,这T个单位时段称之为一个人力资源计划周期),这一工程项目每个时间段t()对雇员需求量是,企业的目标是在计划周期内制定一个最优的招聘或解聘这类雇员的人力资源计划,使得在计划周期内每一个时间段中,雇员的人数能满足该工程项目对雇员的需求,并且整个计划期企业的人力资源费用最省。其中: 1):每一单位时间段企业付给每名雇员的工资费用; 2):每招聘一个雇员的招聘费用; 3):每解聘一名雇员的解聘费用; 4):表示第t个时间段企业中该类雇员的人数(); 5):表示第t个时间段末()招聘该类雇员的人数()或解聘该类雇员的人数(); 6)在该工程项目开始即计划周期开始之前,企业中该类雇员的人数是; 7)为了适当保留一些雇员以备企业以后的正常运转,为以后的工程项目做准备,因此在第T个单位时段末,不考虑招聘或解聘雇员,即在第T阶段雇员的人数是。 该问题的最优控制数学模型描述如下: t=0,1,2, …,T-1 t=1,2,3, …,T 可以证明该问题的最优状态轨线(即最优人力计划方案中每一个时间段企业中该类雇员的人数)满足,其中。 要求:用动态规划方法设计一个求解最优控制轨线(招解聘策略方案)和最优状态轨线的算法,要求:1)写出其动态规划算法的递归公式(包括记录最优控制轨线表达式);2)用伪代码写出算法的具体步骤;3)指出算法的时间复杂度;4)用动态规划算法计算计算下列实例;5)能否设计一个比动态规划效率更高的算法求解该问题?若能,写出该算法并计算下列实例。 实例的参数见下表: T 15 1 3 4 1 8 t 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 2 5 4 5 6 16 12 5 8 3 4 2 2 8 7

(1)动态规划算法的递归公式:
对于人力资源计划问题(HR planning issues),阶段计划按计划时间自然划分,状态定义为每阶段开始时的状态Xk(t),决策为每个时间阶段的人数Uk(t),记每个阶段的需求量(已知量)为Dk(t),则状态转移方程为:
在这里插入图片描述
已知:α:每一单位时间段企业付给每名雇员的工资费用,β:每招聘一个雇员的招聘费用,γ:每解聘一名雇员的解聘费用。设最终公司的雇员人数描述为:在这里插入图片描述
该问题的最优控数学模型为:
在这里插入图片描述
最优值函数:fk(Xk)=
其中允许决策集合Uk由每阶段的最大雇员数决定。终端条件为:
在这里插入图片描述
(2)伪代码:
① 变量标识:
T:人力资源计划。
Xpeople[]:当数组元素为正代表招聘,负代表解聘。
Demand:每个时间段需要的最少人数。
X0:初始人数。
XT:终止人数。
a:每一单位时间段企业付给每名雇员的工资费用。
b:每招聘一个雇员的招聘费用。
r:每解聘一名雇员的解聘费用。
② 核心代码:

public static void optimum(int T, int[] Demand, int X0, int XT, int a, int b, int r) {
        // 动态规划从后往前推
        URecruitment = new int[T + 1];
        // URecruitment[T] = 0;
        XPeople = new int[T + 1];
        XPeople[T] = XT;
        XPeople[0] = X0;
        int DTmax = Math.max(Arrays.stream(Demand).max().getAsInt(), XT);
        int[][] dp = new int[T + 1][Math.max(DTmax, XT) + 1];
        Demand[T] = XT;
        dp[T][XT] = XT * a;
        for (int i = T - 1; i >= 0; i--) {
            int Number = Demand[T - 1];
            if (i == 0) {
                Number = X0;
            }
            int minSub = Integer.MAX_VALUE;
            for (Number = Number; Number <= DTmax; Number++) {
                int sub = XPeople[i + 1] * a + (Number - XPeople[i + 1]) > 0
                        ? (Number - XPeople[i + 1]) * r :
                        (XPeople[i + 1] - Number) * b;
                dp[i][Number] = sub + dp[i + 1][XPeople[i + 1]];
                if (minSub > sub && Number >= Demand[i]) {
                    minSub = sub;
                    URecruitment[i] = XPeople[i + 1] - Number;
                    XPeople[i] = Number;
                }
            }
        }
    }

(3)时间复杂度:O(T*M)
(4)运行结果:
在这里插入图片描述

(5)创新算法:
对于本题,我们还可以使用贪心算法去实现,所谓贪心选择性质是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。这是贪心算法可行的第一个基本要素,也是贪心算法与动态规划算法的主要区别。在动态规划算法中,每步所作的选择往往依赖于相关子问题的解。因而只有在解出相关子问题后,才能作出选择。而在贪心算法中,仅在当前状态下作出最好选择,即局部最优选择。然后再去解作出这个选择后产生的相应的子问题。贪心算法所作的贪心选择可以依赖于以往所作过的选择,但决不依赖于将来所作的选择,也不依赖于子问题的解。正是由于这种差别,动态规划算法通常以自底向上的方式解各子问题,而贪心算法则通常以自顶向下的方式进行,以迭代的方式作出相继的贪心选择,每作一次贪心选择就将所求问题简化为一个规模更小的子问题。
对于一个具体问题,要确定它是否具有贪心选择性质,我们必须证明每一步所作的贪心选择最终导致问题的一个整体最优解。通常可以用我们在证明活动安排问题的贪心选择性质时所采用的方法来证明。首先考察问题的一个整体最优解,并证明可修改这个最优解,使其以贪心选择开始。而且作了贪心选择后,原问题简化为一个规模更小的类似子问题。然后,用数学归纳法证明,通过每一步作贪心选择,最终可得到问题的一个整体最优解。其中,证明贪心选择后的问题简化为规模更小的类似子问题的关键在于利用该问题的最优子结构性质。

  • 8
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值