《算法设计与分析》课程大作业

算法设计与分析期末大作业

信计201  屈静怡  JophieQu@163.com

1.算法作业一

1.1.1.问题描述:

通常,股民希望通过低买高卖来获取股票的最大收益。比如一支股票,5天内价格分别为[10,11,7,10,6]。如果第1天以10元的价格买进,第2天以11元的价格卖出,则收益为1元;如果第3天以7元的价格买进,第4天以10元的价格卖出,则收益为3元。显然,3元是最大收益。
由此,提出问题:对于任意nn天的股票价格序列,希望找出最佳的买入点和卖出点。

1.1.2.算法设计:

对于任意nn天的股票价格,有人提出一种算法I:先找到这组股票价格中的最低值和最高值,然后从最低值出发向右股价序列中找到一个最大值;此外,从最高值出发向左股价序列中找到一个最小值。比较这两种情况下得到的收益值,选择收益大的作为买进和卖出的时间点。
对于任意nn天的股票价格,还可以有算法II(穷举法):对于股价序列,两两求出它们之间的差值,并记录当前的最优收益值。
对于任意nn天的股票价格,还可以有两种分治求解策略:
(1)算法Ⅲ(顺序对分治):先用分治算法求出该组股价序列中的(从小到大)顺序对(顺序对的概念参照PPT课件的逆序对),再来求解差值最大的顺序对,以此作为最优投资的选择。
(2)算法IV(直接分治):假设该序列存在函数max_profit_dc(price)max\_profit\_dc(price)可求得最佳买卖点,则利用分治递归策略,不妨将序列一分为二,每个子序列可利用相同的max_profit_dc(price)max\_profit\_dc(price)来求解最佳买卖点,依此类推,直至子问题继续分解为最小规模的子问题,再进行合并回代。合并过程中,需要注意最佳买卖点不一定都在子问题内部找到,也可能在子问题的分界点上实现。

1.1.3.作业要求:

(1)上述算法I,能够保证任何情况下都能获得最佳买卖点吗?如果不能,试举一个反例。

(2)试分析穷举法和两种分治算法的算法复杂度。

(3)利用算法Ⅲ(顺序对分治)或算法Ⅳ(直接分治),编写一个程序,计算规模为n(10)n(≥10)的一个股价序列的最佳买卖点。数据自定义,给出结果,并附代码。

1.2.1.算法Ⅰ的分析

算法Ⅰ并不能保证任何情况下都能获得最佳买卖点的。要证明该算法的错误,只需要举出一个反例即可。

譬如题干所述实例[10,11,7,10,6][10, 11, 7, 10, 6],有[10,11max,7,10,6min][10, 11_{max}, 7, 10, 6_{min}],通过算法Ⅰ得到的最佳买卖点为{10,11}\{10,11\},但实际最佳买卖点为{7,10}\{7,10\}。即算法Ⅰ的策略是错误。

1.2.2.算法Ⅱ的分析

算法Ⅱ(穷举法)通过两两对比求出差值。核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
int Max=-0x3f3f3f,start=0,over=0;
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
if(day[j]-day[i]>Max){
Max=day[j]-day[i];
start=i,over=j;
}
}
}
cout<<"最大收益值:"<<Max<<endl;
cout<<"买入日:"<<start<<" "<<"卖出日:"<<over;

算法Ⅱ要经历双重循环,即时间复杂度为O(n2)O(n^2)。开辟一维数组,空间复杂度为O(n)O(n)

1.2.3.算法Ⅲ、Ⅳ的分析
(1)算法Ⅳ描述

在本题中我们以算法Ⅳ为例,叙述算法思想。将题目抽象成数学模型,则为:对于给定的一个长度为nn的序列aa,找到一对点对(i,j)(i,j)使得ajaia_j-a_i的值最大。

这个问题是CDQ分治的一种典型例子,算法流程如下:

1、找到这个序列的中点midmid

2、将所有点对(i,j)(i,j)划为三类:

  • 1imid,1jmid1\le i\le mid,1\le j\le mid的点对;
  • 1imid,mid+1jn1\le i\le mid,mid+1\le j\le n的点对;
  • mid+1in,mid+1jnmid+1\le i\le n,mid+1\le j\le n的点对。

3、将(1,n)(1,n)这个序列拆成两个序列(1,mid)(1,mid)(mid+1,n)(mid+1,n)。此时第一类点对和第三类点对都在这两个序列中。

4、递归地处理第一类和第三类点对。

5、通过在每一层递归中处理第二类点对。

(2)算法Ⅲ、Ⅳ时空复杂度

算法Ⅲ(顺序对分治)和算法Ⅳ(直接分治)满足递归方程

T(n)={O(1),n<22T(n2)+O(n),n2T(n)= \begin{cases} O(1),\qquad \qquad \,n<2\\ 2T(\frac{n}{2})+O(n),n\ge2 \end{cases}

n2n\ge2时,有T(n)=n+2T(n2)=n+2(n2)+4T(n4)=...=nlognT(n)=n+2T(\frac{n}{2})=n+2(\frac{n}{2})+4T(\frac{n}{4})=...=nlogn。即算法Ⅲ、Ⅳ的时间复杂度为O(nlogn)O(nlogn)

空间复杂度为O(n)O(n)

(3)实例应用

例如,对于实例[1,2,5,7][1,2,5,7],在第一层递归中分为[1,2][1,2][5,7][5,7],左半部分第一类点对得到的最大收益是1,右半部分第三类点对得到的最大收益是2。再通过两边循环得到的第二类点对最大收益为7-1=6。则合并所有情况取最大值,最大收益为6。

1.3.算法Ⅳ运行结果
(1)

Input:

5

10 11 7 10 6

Output:

最大收益值:3

买入日:第3天 卖出日:第4天

test1

(2)

Input:

10

4 23 7 23 3 4 34 23 10 9

Output:

最大收益值:31

买入日:第5天 卖出日:第7天

test2

1.4.算法Ⅳ代码附录

代码附录链接:https://paste.org.cn/BfrkIv8CW7

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include<bits/stdc++.h>
using namespace std;

const int N=1e5+7;
const int inf=0x3f3f3f3f;
int a[N];

int max_profit_dc(int l, int r,int& Min,int& Max){

if(l==r){
Min=l,Max=r;
return 0;
}

int mid=(l+r)>>1;
int x1_Min=0,x1_Max=0,x3_Min=0,x3_Max=0,x2_Min=inf,x2_Max=-inf;

int x1=max_profit_dc(l,mid,x1_Min,x1_Max); //递归处理第一类点对的情况
int x3=max_profit_dc(mid+1,r,x3_Min,x3_Max); //递归处理第三类点对的情况

Min=mid,Max=mid+1;

for(int i=mid+1;i<=r;i++){ //找到mid~r最大的值
if(a[i]>x2_Max){
Max=i;
x2_Max=a[i];
}
}
for(int i=l;i<=mid;i++){ //找到l~mid最小的值
if(a[i]<x2_Min){
Min=i;
x2_Min=a[i];
}
}
int x2=x2_Max-x2_Min; //在每一层递归中单独处理第二类点对的情况
if(x2<x3){ //如果第二类的最大收益值小于第三类
Min=x3_Min;
Max=x3_Max;
}
if(a[Max]-a[Min]<x1){ //如果第二、三类的最大收益值小于第一类
Max=x1_Max;
Min=x1_Min;
}
return max(max(x1,x3),x2);
}
int main(){
int n;
cout<<"请输入这只股票价格序列的长度"<<endl;
cin>>n;
cout<<"请分别输入这只股票不同日期的价格"<<endl;
for(int i=1;i<=n;i++)
cin>>a[i];
int Max=0,Min=0;
cout<<"最大收益值:"<<max_profit_dc(1,n,Min,Max)<<endl;
cout<<"买入日:第"<<Min<<"天 "<<"卖出日:第"<<Max<<"天"<<endl;
}

2.算法作业二

2.1.1.问题描述:

给定两个字符串xxyy,如果要将字符串xx替换为yy,转换过程有三个基本操作:插入(Ins)字符C、删除(Del)字符C、将字符C替换(Rep)为字符C’。如何组合这些操作从而使得转换的代价最小?

2.1.2.算法设计:

(1)本问题是一个优化问题,可采用动态规划法来求解。

(2)**该问题在文本编辑、基因检测等领域有重要应用。**对于单纯的字符串转换代价问题,可假设单次插入、删除、替换操作具有相同的代价(=1)。

(3)定义子问题:

考虑将问题的后缀作为子问题,也即分别取输入字符串xxyy的后面部分作为子问题。不妨设函数DP是用于求解该问题的策略,则子问题可表示为DP(x[i:],y[j:])(x[i:],y[j:]),其中,0ilen(x)0≤i≤len(x), 0jlen(y)0≤j≤len(y)len()len()表示求长度的函数。易知子问题个数为len(x)×len(y)len(x)×len(y)

(4)建立子问题的递归关系。

如图所示子问题的结构,为了将字符串xx转换为yy,对于字符x[i]x[i]有以下三种可能的操作:

  • 删除字符x[i]x[i]。如图b:DP(x[i:],y[j:])=(x[i:],y[j:])=DP(x[i+1:],y[j:])+cost(Delx[i])(x[i+1:],y[j:])+cost(Delx[i])

  • 插入字符y[j]y[j]。如图c:DP(x[i:],y[j:])=(x[i:],y[j:])=DP(x[i:],y[j+1:])+cost(Insy[j])(x[i:],y[j+1:])+cost(Insy[j])

  • 替换字符x[i]x[i]y[j]y[j]。如图d:DP(x[i:],y[j:])=(x[i:],y[j:])=DP(x[i+1:],y[j+1:])+cost(Repy[j])(x[i+1:],y[j+1:])+cost(Repy[j])

其中,cost()cost()函数为执行一次操作的代价,DelInsRep表示删除、插入、替换操作。于是,DP(x[i:],y[j:])(x[i:],y[j:])就等于以上三者中的最小值。由此可建立完整的递归关系(从略)。递归初始条件为DP(len(x),len(y))=0(len(x),len(y)) = 0

2.1.3.作业要求

(1)理解以上动态规划算法思想,写出完整的递归关系式。

(2)对输入x=“book”和y=“broke”,利用上述动态规划思想,人工求解将xx转换为yy的最小转换代价(可列表求解)。

(3)将以上算法付诸代码实现,给出三个输入实例的分析结果,并附代码。

2.2.算法分析
(1)算法描述

不妨记执行一次操作的代价为一个单位,对于当前的xix_iyjy_j,子问题的递归关系统共有四种情况:

(1)当xix_i等于yjy_j时,有dpi,j=dpi+1,j+1dp_{i,j}=dp_{i+1,j+1}

(2)当xix_i不等于yjy_j时,有三种可能

  • 在字符串xx中删除xix_i,则有dpi,j=dpi+1,j+1dp_{i,j}=dp_{i+1,j}+1
  • 在字符串xx中插入yjy_j,相当于在字符串yy中删除yjy_j,则有dpi,j=dpi,j+1+1dp_{i,j}=dp_{i,j+1}+1
  • 在字符串xx中将xix_i替换为yjy_j,相当于在字符串xx中删除xix_i后又在字符串xx中插入yjy_j,则有dpi,j=dpi+1,j+1+1dp_{i,j}=dp_{i+1,j+1}+1

同时,也要注意边界情况:当字符串xxyy为空的时候,后缀子问题的值应当为子问题的长度。即dpi,len(y)=len(x)idp_{i,len(y)}=len(x)-i,或dplen(x),j=len(y)jdp_{len(x),j}=len(y)-j

综合所有情况,得到后缀子问题的状态转移方程为:

{dpi,len(y)=len(x)i,j=len(y)dplen(x),j=len(y)j,i=len(x)dpi,j=dpi+1,j+1,  xi=yjdpi,j=min{dpi+1,j,  dpi,j+1,  dpi+1,j+1}+1,      else\begin{cases} dp_{i,len(y)}=len(x)-i,\qquad \qquad \qquad \qquad \qquad j=len(y)\\ dp_{len(x),j}=len(y)-j,\qquad \qquad \qquad \qquad \qquad i=len(x)\\ dp_{i,j}=dp_{i+1,j+1},\qquad \qquad \qquad \qquad \qquad \qquad \;x_i=y_j\\ dp_{i,j}=min\{dp_{i+1,j},\;dp_{i,j+1},\;dp_{i+1,j+1}\}+1,\;\;\;else \end{cases}

(2)时空复杂度

算法涉及两层循环嵌套,时间复杂度为O(nm)O(nm)。空间复杂度为O(n+m)O(n+m)。其中n=len(x)n=len(x)m=len(y)m=len(y)

(3)实例应用

人工求解将x=bookx=book转换为y=brokey=broke的最小转换代价,列表如下。其中箭头表示当前数值从哪个状态转移(转移方向不唯一)。

状态转移

2.3.运行结果

以下是三个实例的测试结果。

(1)

Input:

x=aaaa,  y=aax=aaaa,\; y=aa

Output:

2

状态转移列表如下,答案出口为dp0,0dp_{0,0},即【】中所示。

x\y a a
a 【2】 3
a 1 2
a 0 1
a 1 0
(2)

Input:

x=Jingyi,  y=Jophiex=Jingyi,\;y=Jophie

Output:

5

状态转移列表如下,答案出口为dp0,0dp_{0,0},即【】中所示。

x\y J o p h i e
J 【5】 6 6 5 5 6
i 5 5 5 5 4 5
n 5 4 4 4 4 4
g 5 4 3 3 3 3
y 5 4 3 2 2 2
i 5 4 3 2 1 1
(3)

Input:

x=thank,  y=thinkingx=thank,\;y=thinking

Output:

4

状态转移列表如下,答案出口为dp0,0dp_{0,0},即【】中所示。

x\y t h i n k i n g
t 【4】 5 5 4 4 4 4 5
h 5 4 5 4 3 3 3 4
a 6 5 4 4 3 2 2 3
n 6 5 4 3 3 2 1 2
k 7 6 5 4 3 3 2 1
2.4.代码附录

代码附录链接:https://paste.org.cn/6bwe4lKfu4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<bits/stdc++.h>
using namespace std;
int main(){
string x,y;
cin>>x>>y;
vector<vector<int>>dp(x.size()+1,vector<int>(y.size()+1,0));
for(int i=0;i<=x.size();i++) dp[i][y.size()]=x.size()-i;
for(int j=0;j<=y.size();j++) dp[x.size()][j]=y.size()-j;
for(int i=x.size()-1;i>=0;i--){
for(int j=y.size()-1;j>=0;j--){
if(x[i]==y[j]){
dp[i][j]=dp[i+1][j+1];
} else{
dp[i][j]=min(dp[i+1][j+1],min(dp[i+1][j],dp[i][j+1]))+1;
}
}
}
cout<<dp[0][0]<<endl;
}

3.算法作业三

3.1.1.问题描述:

假设零钱系统的币值为{1,p,p2,,pn},  p>1\{1, p, p^2, … , p^n\},\;p>1,且每个钱币的重量都等于1,设计一个最坏情况下时间复杂度最低的算法,使得对任何钱数yy,该算法得到的零钱个数最少。

3.1.2.作业要求:

(1)说明该问题的贪心算法策略,并举出一个实例。

(2)参考教材定理4.6,试简单证明贪心策略的最优性。

3.2.算法分析:
(1)贪心策略

将本题题意抽象为数学模型,则对于序列{1,p,p2,,pn},p>1\{1, p, p^2, … , p^n\}, p>1,有约束函数:

i=0mxipi=y,xiN\sum_{i=0}^mx_ip^i=y,\qquad x_i\in N

且其目标函数为

min{i=1mxi}min\{\sum_{i=1}^mx_i\}

假如对进制转换足够敏感的话,很容易发现这个问题的本质上是将yy表示成pp进制后,将每个数位加起来的和就是最少的零钱个数。即按照币值从大到小排列零钱,从币值大的pkp^k开始贪心,每种钱尽量多用。如果剩余零钱小于该币值,再考虑用下一种钱币。

(2)实例说明

对于y=58,p=7y=58,p=7,则用pp进制来表示yy则有y=112(7)y=112_{(7)},则零钱个数w=1+1+2=4w=1+1+2=4

如果用贪心的思想,即对于y=58,p=7y=58,p=7,首先取一个p2=49p^2=49,剩下5849=958-49=9元;再取一个p1=7p^1=7,剩下97=29-7=2元;再取两个p0=1p^0=1元,正好取完。则有p2+p1+2p0=yp^2+p^1+2p^0=y,最少的零钱个数为w=1+1+2=4w=1+1+2=4

(3)贪心策略的证明

Fk(y)F_k(y)表示在动态规划算法(最优解)中用前kk种硬币,总钱数为yy的最小重量。记Gk(y)G_k(y)表示在贪心算法种用前kk种硬币,总钱数为yy的最小重量。

下面通过数学归纳法来证明对于任意kk,有Fk(y)Gk(y)F_k(y)\equiv G_k(y)

(1)当k=1k=1时显然成立。

(2)假设k>1k\gt1时成立,即Gk(y)=Fk(y)G_k(y)=F_k(y)且存在θ=p,  δ=0\theta=p,\;\delta=0​,则有

θpkδ=pk+1\theta p^k-\delta=p^{k+1}

且满足0δ<p,θN0\le\delta\lt p,\theta\in N,则由教材定理4.6可得

Gk+1(y)=Fk+1(y)G_{k+1}(y)=F_{k+1}(y)

k+1k+1时也成立。则对于任意的kk,均有Fk(y)Gk(y)F_k(y)\equiv G_k(y),证毕。

(4)时空复杂度

时间复杂度为O(n)O(n),其中nnyy位数的长度。空间复杂度为O(1)O(1),仅为常数级别。

4.算法作业四

4.1.1.问题描述:

许多计算机科学问题可利用图来建立模型,且最后问题的求解过程往往就是在图上进行遍历和搜索。

4.1.2.作业要求:

(1)请查阅有关资料,举1~2个可转换为图搜索问题的应用场景,并简要说明其中图搜索的机制(不少于200字)。

(2)回溯法和分支限界方法也常在图的搜索算法中。请简述回溯法和分支限界方法的算法思想和算法设计步骤。

4.2.图搜索的应用场景与搜索机制

图的搜索其实可以应用在大学选课的场景中。比如大学课程中有:《微积分》,《线性代数》,《离散数学》,《概率论与统计学》,《算法导论》,《机器学习》等等。当我们想要学习《机器学习》的时候,就必须先学会《微积分》和《概率论与统计学》。这些课程就相当于几个顶点uu, 顶点之间的有向边(u,v)(u,v)就相当于学习课程的顺序。这样的选课场景我们通常称其解决方案为”拓扑排序“。下面将介绍如何将这个过程抽象出来,用算法来实现。

但是如果某一天排课的老师打瞌睡了,说想要学习《算法导论》,还得先学《机器学习》,而《机器学习》的前置课程又是《算法导论》。在这里,《算法导论》和《机器学习》间就出现了一个闭环,显然我们现在没办法弄清楚到底需要学什么了,于是我们也没办法进行拓扑排序了。因而如果有向图中存在环路,那么我们就没办法进行拓扑排序了。即拓扑排序还有一个应用场景是,在有向图中判环

因而将场景抽象出数学模型,即为我们在一个有向无环图中,将图中的顶点以线性的方式进行排序,使得对任何的顶点uuvv的有向边(u,v)(u,v) ,都有uuvv的前面。

我们可以通过广度优先搜索或者深度优先搜索来实现拓扑排序。广度优先的思路其实就是对每个入度为0的且未被访问过的节点进行广度优先搜索。

在搜索过程中,只要搜索了uuvv之间的边,那就将vv的入度减1,相当于删除边的操作。入度为零就代表着它的前置条件已经完全满足。一个节点只有当其入度为0且未被访问过,才对其进行广度优先搜索。具体算法流程请看代码。

代码提取链接:https://paste.org.cn/wDu2ox1MNW

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include<bits/stdc++.h>
using namespace std;

const int N=1e5+7;

vector<int>g[N];
vector<int>ans;
int v,e,indeg[N];
bool vis[N];

void bfs(int u){
queue<int>q;
vis[u]=true;
q.push(u);
while(!q.empty()){
int t=q.front();
q.pop();
ans.push_back(t);
for(auto x:g[t]){
indeg[x]--;//将入度减一
if(!indeg[x]&&!vis[x]){//从入度为0且未被访问过的节点压入队列中
q.push(x);
vis[x]=true;
}
}
}
}

int main(){
cin>>v>>e;
for(int i=0;i<e;++i){
int u,v;
cin>>u>>v;
g[u].push_back(v);//邻接表建有向图
indeg[v]++;//记录入度
}
for(int i=0;i<v;++i){
if(!indeg[i]&&!vis[i])
bfs(i);//从入度为0且未被访问过的节点开始搜索
}
for(int x:ans)
cout<<x<<endl;
}
4.3.1.回溯 & 分支限界的思想

在屈婉玲教授的《算法设计与分析》中,回溯与分支限界法都是基于深度优先搜索的基础上进行优化改进的。回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。回溯法是一种优选搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。分支限界法则是在回溯算法的基础上,设定一个代价函数对搜索分支进行剪枝优化,从而降低算法的复杂度。

4.3.1.回溯 & 分支限界的算法设计步骤
(1)回溯 & 分支限界的一般步骤:

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

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

3、以深度优先方式搜索解空间,并在搜索过程中确定界函数避免无效搜索。

(2)回溯 & 分支限界的实例

TSP旅行商问题为例,下图为一个实例。

TSP实例

TSP实例

回溯法的解空间为:

TSP回溯法

TSP回溯法搜索树

分支限界法的解空间为:

image-20220704223636619

TSP分支限界法搜索树

不妨设巡回路线从1开始,解向量为<i0=1, i1,,in1><i_0=1,\ i_1,\ldots,i_{n-1}>,其中$ i_1,\ldots,i_{n-1}{2,\ldots,n}的一个排列。搜索树为上图所示。其中代价函数为的一个排列。搜索树为上图所示。其中代价函数为F,界函数(当前得到的最短巡回路线长度),界函数(当前得到的最短巡回路线长度)为B$,且有

F=j=1kcj+lik+ijBlijF=\sum_{j=1}^{k}c_j+l_{i_k}+\sum_{i_j\notin B} l_{i_j}

由此,我们便确定好了解空间剪枝函数(界函数)。以下为分支限界法的实例代码,分支限界与回溯的不同之处主要在于多了一个界函数的剪枝优化。

代码提取链接:https://paste.org.cn/hqffCgqfri

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include<bits/stdc++.h>
using namespace std;
const int kMax=107;
vector<pair<int,int>>g[kMax];
vector<vector<int>>ans;
vector<int>temp;
vector<int>vis,l;
int n,m,B=0x3f3f3f3f;
void dfs(int fa,int sum,int count){
if(count==n){
if(fa==1&&sum<=B){
ans.push_back(temp);//存储当前最优解
B=sum;//更新界函数
}
return ;
}
for(auto i:g[fa]){
if(!vis[i.first]){
int F=sum+l[fa];//求解代价函数F
for(int i=2;i<=n;i++){
if(!vis[i]){
F+=l[i];
}
}
if(F>B){ //如果代价函数F大于界函数B,则回溯到父节点。
return ;
}
vis[i.first]=true;
temp.push_back(i.first);
dfs(i.first,sum+i.second,count+1);
temp.pop_back();//回溯
vis[i.first]=false;
}
}
}
int main(){
cin>>n>>m;
vis.resize(n+1,false);
l.resize(n+1,0x3f3f3f3f);
while(m--){
int u,v,w;cin>>u>>v>>w;
g[u].push_back({v,w});//建图
g[v].push_back({u,w});
}
for(int i=1;i<=n;i++){
for(auto it:g[i]){
l[i]=min(l[i],it.second);//预处理每个节点所连边的最小权值
}
}
temp.push_back(1);
dfs(1,0,0);//搜索入口
cout<<"最短巡回路径有"<<ans.size()<<"条,分别为"<<endl;
for(auto i:ans){
for(auto it:i){
cout<<it<<" ";
}cout<<endl;
}
cout<<"最短巡回路径长度为"<<B<<endl;
}
/*

4 6
1 2 5
1 3 9
1 4 4
3 4 7
2 4 2
2 3 13

*/

5.算法作业五

5.1.1.问题描述

根据高等数学知识,积分ex2dx\int{e^{x^2}dx}没有初等函数形式的原函数。但对于定积分01ex2dx\int_0^1{e^{x^2}dx},我们可以通过设计随机数值方法计算其积分的近似值。

5.1.2.作业要求

(1)采用线性同余法,编写一个随机数生成的程序 。

(2)应用自编随机数生成程序,分别在平面区域[0,1]×[0,e][0,1]×[0,e]内取100100010000100000100、1000、10000、100000个随机点,设计随机数值算法计算定积分01ex2dx\int_0^1{e^{x^2}dx}的近似值,并列表分析近似值的收敛性。

5.2.算法分析

由线性同余法在O(n)O(n)的时间复杂度产生nn个随机数序列,其核心代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<bits/stdc++.h>
using namespace std;

unsigned long long seed=chrono::system_clock::now().time_since_epoch().count();
mt19937 rnd(seed);

const int mod=1e9+7,B=rnd(),C=rnd();
int main(){
int n;
cin>>n;
unsigned long long x=(B*rnd()+C)%mod;
for(int i=1;i<=n;i++){
x=(B*x+C)%mod;
cout<<x<<" ";
}
}

本算法计算定积分01ex2dx\int_0^1{e^{x^2}dx}的核心思想是蒙特卡罗法。要得到平面区域I=[0,1]×[0,e]I=[0,1]×[0,e]内的随机点,则对于随机数xx,可做变换x=xmodx^*=\frac{x}{mod}y=xemody^*=\frac{xe}{mod},从而得到随机点(x,y)(x^*,y^*)。我们记落在积分区域内点的数量为cntcnt,总的点个数为nn,则得到估算的积分近似值为cntn\frac{cnt}{n}

自适应辛普森积分,我们首先得到精确值01ex2dx1.462651745907\int_0^1{e^{x^2}dx}≈1.462651745907。在本题,我们通过列表法比较近似值来的绝对误差和方差来评价近似值的精确性。

5.3.运行结果 & 结果分析
n 近似值(随机十次取平均值) 绝对误差ϵ\epsilon
100 1.467872187367859985 0.005220441460859896
1000 1.465153905539400983 0.002502159632400894
10000 1.464882077356555113 0.002230331449555024
100000 1.462897731621780020 0.000245985714779931
1000000 1.462603580756653297 0.000048165150346792

我们发现,随着nn的增大,ϵ\epsilon更加小,即精确性越高,随机算法更加稳定。

同时,根据大数定律,我们也了解到,由于每次测试的简单样本都是独立同分布的,随着测试次数的增多,算术平均结果更加趋近于精确值。

5.4.代码附录

代码提取链接:https://paste.org.cn/v23DKWNouR

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include<bits/stdc++.h>
#define double long double
using namespace std;

unsigned long long seed=chrono::system_clock::now().time_since_epoch().count();
mt19937 rnd(seed);

const unsigned long long mod=1e9+7;
const unsigned long long B=rnd(),C=rnd();
const double eps=1e-6;
const double E=2.718281828459;

int main(){
int n,cnt=0;
double x,y;
cin>>n;
unsigned long long x1=(B*rnd()+C)%mod,y1=(B*rnd()+C)%mod;
for(int i=1;i<=n;i++){
x=x1*1.0/mod;
y=y1*E/mod;
if(pow(E,pow(x,2))-y>eps)
cnt++;
x1=(B*x1+C)%mod;
y1=(B*y1+C)%mod;
}
cout<<fixed<<setprecision(18)<<cnt*1.0/n*E<<endl;
}
打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2022-2023 JingyiQu
  • 访问人数: | 浏览次数:

如果这篇博客帮助到你,可以请我喝一杯咖啡~

支付宝
微信