代码链接:pan.baidu.com/s/15inIth8Vl89R1CgQ_wYc2g
提取码:gf13
算法分析与设计第 1 次实验 | |||||
时间 | 2020.3.31 | 地点 | 软件大楼 127 | ||
实验名称 | 用分治法查找数组元素的最大值和最小值 | ||||
实验目的 | 通过上机实验,要求掌握分治算法的问题描述、算法设计思想、程序设计。 | ||||
实验原理 | 使用二分思想的查找算法,根据不同的输入用例,能准确的输出用例中的最值, 并计算出程序运行所需要的时间。 | ||||
实验步骤 | ① 将用例数据从文件中读取到数组中 ; ② 进行分治操作如下; ③ 若达到基底(此时仅有一个元素),则将该数据元素返回; ④ 二分操作,将当前区域分为两份,分别在两个区域中找到最值(最大/最小),比较这两个最值,得到更大(或更小)的一个并返回该值; ⑤重复③④步骤直到返回最值; | ||||
关键代码 | 关键代码(带注释)1. 二分操作代码(核心)找最小值的操作与此类似,只是在返回最值时返回较小的那个; 2. 计算时间部分代码通过时间函数QueryPerformanceFrequency与cpu的主频频率得到程序段的运行时间,可以精确到微妙,比clock函数更精确一些; 3. 随机数生成程序利用rand()函数生成大小为10、1000、100000的三个文件,其中全为随机数; | ||||
测试结果 | 运行结果截图及分析可以看到对三种规模的文件进行测试的结果,程序段运行时间的单位为ms,而验证是利用普通的遍历法遍历数组找出最值与分治法找出的最值相比较,验证分治算法得正确性; 时间复杂度分析: 因为要找数组中的最值,每个元素都至少要比较一次,因此时间复杂度是 O(n); 从主定理法也可以得到式子: T(n)=2T(n/2)+O(1)(n>1)、1(n=1); 计算可得时间复杂度为O(n) 从运行时间来看,大中小三种规模都相差100倍,而运行时间也几乎相差100倍; | ||||
实验心得 | 通过这次实验,我回顾了分治算法的基本原理,在其中加入了生成随机数的算法与计算程序时间算法过程,让我熟悉了随机数算法。 实验可改进的地方:其实查找最大值与最小值问题本质一致,可以仅用一个递归,同时查找最大最小值,将比较次数从2n降到3n/2; 这里给出该算法的一种分治实现方法: 不直接返回最值而是直接将两个最值存储; |
算法分析与设计第 1 次实验 | |||||
时间 | 2020.3.31 | 地点 | 软件大楼 127 | ||
实验名称 | 用分治法实现合并排序 | ||||
实验目的 | 通过上机实验,要求掌握分治算法的问题描述、算法设计思想、程序设计。 | ||||
实验原理 | 给定任意几组数据,利用分治法合并排序的思想,将数据进行排序并将排好的数据进行输出。 | ||||
实验步骤 | ① 将用例数据从文件中读取到数组中 ; ② 进行分治操作如下; ③ 若达到基底(此时仅有两个元素),则将这两个元素进行排序; ④ 分操作,将当前区域分为两份子序列,分别在两个子序列中进行排序; ⑤当递归返回时进行当前两个区域的合并操作,即利用一种排序算法将其排序 ⑥重复③④⑤步骤直到返回; | ||||
关键代码 | 关键代码(带注释)1. 合并排序操作代码(核心)先将划分的左右两个子序列进行排序,接着对这两个子序列进行归并; 2. 归并排序操作代码对当前两个子序列进行归并; 3. 计算时间部分代码通过时间函数QueryPerformanceFrequency与cpu的主频频率得到程序段的运行时间,可以精确到微妙,比clock函数更精确一些; 4. 随机数生成程序利用rand()函数生成大小为10、1000、100000的三个文件,其中全为随机数; 3、4两个部分与之前的实验所用方法相同; | ||||
测试结果 | 运行结果截图及分析可以看到对三种规模的文件进行测试的结果,程序段运行时间的单位为ms,而验证是输出排序后的小规模数据,验证分治算法得正确性; 时间复杂度分析: 从主定理法可以得到式子: T(n)=2T(n/2)+O(n)(n>1)、1(n=1); 计算可得时间复杂度为O(nlogn); 因为递归过程中用到了栈空间并且和一个tmp数组,因此空间复杂度为O(n); | ||||
实验心得 | 通过这次实验,我回顾了分治算法的基本原理,在其中加入了生成随机数的算法与计算程序时间算法过程,让我熟悉了随机数算法。 归并排序可以分为两个部分:分操作和合操作,合操作有多种方式,比如直接使用一种排序算法等; |
算法分析与设计第 1 次实验 | |||||
时间 | 2020.4.5 | 地点 | 软件大楼 127 | ||
实验名称 | 最小费用问题 | ||||
实验目的 | 通过上机实验,要求掌握动态规划算法的问题描述、算法设计思想、程序设计。 | ||||
实验原理 | 给定任意几组数据,利用动态规划法的思想,根据题目所给出的条件,计算出最小费用。 | ||||
实验步骤 | 问题分析:用一个五元组来表示商品组合(a,b,c,d,e)的最小费用: cost(a,b,c,d,e) A[k]、B[k]、C[k]、D[k]、E[k]表示第k种优惠方案的商品组合。offer(m) 是第m种优惠方案价格、如果cost(a,b,c,d,e)使用第m种优惠方案,则 cost(a,b,c,d,e)=cost(a-A[m],b-B[m],c-C[m],d-D[m],e-E[m])+offer(m) 即该问题具有最优子结构,使用动态规划算法比较简单; 通过对问题的分析,我们知道了状态的表示和转移的基本方法,我们很容易得到一个状态转移方程: cost(a,b,c,d,e) =Min{cost[a-A[m],b-B[m],c-C[m],d-D[m],e-E[m]]+offer[m]}; 初始条件为: cost[a,b,c,d,e] =Cost[1]*a+Cost[2]*b+Cost[3]*c+Cost[4]*d+Cost[5]*e; 即不用优惠的购买费用。 算法步骤:① 将用例数据从文件中读取到数组中 ; ② 动态规划思想; 状态表示:cost[n1][n2][n3][n4][n5]dp[n1][n2][n3][n4][n5]表示第1,2,3,4,5种物品购买数量分别为n1,n2,n3,n4,n5的最小价格; 边界状态:cost[0][0][0][0][0]=0; 状态转移:设pi为第i种优惠,将单价也视为一种优惠; dp[n1][n2][n3][n4][n5]=minmi={dp[n1−i1][n2−i2][n3−i3][n4−i4][n5−i5]+offer[i]}; ③ 所需数据定义; ④ 开始动态规划,对每一种要购买的商品的数目的组合都进行遍历,求解的顺序为(假定商品种类为3),product[i]为第i种商品的购买数量: cost[0][0][1]->cost[0][0][2]->...->cost[0][0][product[3]] ->cost[0][1][1]->cost[0][1][2]->...->cost[0][1][product[3]] ... ->cost[0][product[2]][1]->cost[0][product[2]][2]->cost[0][product[2]][product[3]] ... ... ->cost[product[1]][product[2]][1]->cost[1][product[2]][2]->cost[product[1]][product[2]][product[3]] 求当前商品组合的cost最小值方法: 遍历每一种优惠商品组合,用所购商品总数减去优惠组合内对应商品数,算出该种优惠是否是最小且满足情况的(大小不越界); ⑤当所有所购商品数量遍历结束后cost[purch[1][0]][purch[2][0]][purch[3][0]][purch[4][0]][purch[5][0]]即是最小值; | ||||
关键代码 | 关键代码(带注释)1. dp部分代码for循环是对每种商品数量进行动态规划求最小值 2. 求当前的商品数量组合求最小值遍历每一种的优惠组合,求优惠后的最小值 3. init初始化函数(从文件中读) | ||||
测试结果 | 运行结果截图及分析题目样例:
时间复杂度分析: 因为程序动态规划需要对每一种要购买的商品,每一个商品的数量(K),每一种优惠组合(S)进行一次遍历,因此时间复杂度为 O(a*b*c*d*e*S); | ||||
实验心得 | 通过这次实验,我回顾了动态规划算法的基本原理,在其中加入了生成随机数的算法与计算程序时间算法过程,使编程的灵活性提高。 可以改进的地方: 用随机数函数生成的数据存在一定的不合理性(如优惠组合可能会更贵或者特别便宜等),如果考虑这些因素可以提高代码的可用性; |
算法分析与设计第 1 次实验 | |||||
时间 | 2020.4.5 | 地点 | 软件大楼 127 | ||
实验名称 | 树的最大连通分支问题 | ||||
实验目的 | 通过上机实验,要求掌握动态规划算法的问题描述、算法设计思想、程序设计。 | ||||
实验原理 | 给定任意几组数据,利用动态规划法的思想,根据题目所给出的条件,计算出最大连通分支。 | ||||
实验步骤 | 问题分析:树的动态规划算法:即:最大连通分支在子树或树中,因此对树进行遍历,依次求出以每个结点为树根的最大连通分支权值; 动态规划思想:T(i)代表以i为根节点的树的最大连通子图权值,于是可知当i为叶子结点时T(i)=w(i),即等于根节点本身的权值。 当i不是根节点时有T(i)=w(i)+sum(T(k))(0<=k<=N),T(k)指i的子节点的最大连通子图权值,N指i的子节点个数,T(k)小于0时不加到T(i)上。 满足最优子结构,且有重叠子问题,因此可以用动态规划算法(从底至顶); 算法步骤:① 将用例数据从文件中读取到数组中 ; ② 建树;(其中一种合理的树) ④ 开始动态规划,以每一个结点作为根节点做以下操作: 1)对于叶子结点,其最大联通分支权值为该结点的权值; 2)对每一个子结点进行遍历,若其某一子结点的最大连通权值>0,则将其值加到它的最大连通权值,反之舍弃; 3)遍历完所有子结点后结束; ⑤输出最大连通权值到文件中。 | ||||
关键代码 | 关键代码(带注释)1. dp部分代码动态规划求以每一个结点K为根节点的子树的最大连通分支(while循环),这些值中的大于0的值加到结点k的连通分量中; 从根结点一直进入到叶结点后一层层的递归返回知道顶层,结束dp; 2. init初始化函数(从文件中读)因为还不确定父子关系,因此只能将输入的每一个结点都作一次父节点进行存储; 输入所有的边后,相当于建立了一颗子结点也可以指向父结点的错误的“树”。建树的过程就是删边过程(去掉那些错误指向); 3. 建树(以第一个结点为根结点的一棵树)删边操作如下: 将当前结点标记,遍历当前结点(父结点)的所有子结点,如果子结点已经标记过,则说明出现了逆的父子关系,需要删除该子结点; 删边分为两种情况,删除的子结点在表首,以及删除的子结点不在表首; 4. 计算时间部分代码通过时间函数QueryPerformanceFrequency与cpu的主频频率得到程序段的运行时间,可以精确到微妙,比clock函数更精确一些; 第二种方法简述: 基于图的ADT实现 建立无向图(一定是无环的) 接着可以直接进行动态规划的过程 sum数组用于保存每一个顶点的最大连通量,在进行DFS之前就已经进行过初始化了(赋每个顶点的权值); 用标记的方法使顶点遍历顺序与状态更新的子状态和树的方法一模一样; 与树一样是触底返回的,可以说状态更新的过程等等都与树一样; 这是因为树首先通过标记的方法来确定树的结构,也就是从根结点只能不停的向子树深入,而图的话是在DFS的过程通过标记实现同样的一个深入的过程; DFS结束后遍历sum数组,得到最大值并输出; | ||||
测试结果 | 运行结果截图及分析对于题目样例: 时间复杂度分析: 建树的操作需要遍历2*(n-1)条边并进行相应的删边处理、初始化操作需要遍历n-1条边并进行存储,因此时间复杂度为O(n); 因为对每一个结点都进进行dp(即dp的深度为n),对顶点的处理操作(判断是否需要加上叶结点的最大连通分支)的复杂度是O(1)的,因此时间复杂度为O(n); 缺点:没有证明输入的数据是否可以构成一颗树; | ||||
实验心得 | 通过这次实验,我回顾了动态规划算法的基本原理,对动态规划算法的应用更加的熟练; 动态规划不仅可以为我们提供解题的思路,还可以帮助我们对一些问题进行快速求解; 我用了第二种方法——建立无向无环图,利用DFS加标记顶点的方法可以实现基于树的方法相同的作用,在确定进入DFS的第一点之后,其遍历顶点的顺序与树是一模一样的,树是限定了当前结点的动态规划只能由子树的值来更新,而DFS加标记的方法同样可以确定当前的顶点不会去访问前一个顶点而只会继续深入到下一个,他们两种方法同样是触底返回; |