软件设计师——08 算法设计与分析
1 算法设计与分析的基本概念
- 算法的定义:算法是对特定问题求解步骤的一种描述,是指令的有限序列,每条指令表示一个或多个操作;
- 算法的五个重要特性
- 有穷性:算法对任何合法的输入值,都必须在执行有穷步之后结束,且每一步都能在有穷时间内完成
- 确定性:算法中的每一条指令都有确定的含义,不会产生二义性;在任何条件下,算法都只有唯一的执行路径,相同输入只能得到相同输出
- 可行性:算法中描述的操作都能通过已经实现的基本运算执行有限次来实现
- 输入:算法有零个或多个输入,这些输入取自某个特定的对象集合
- 输出:算法有一个或多个输出,这些输出是同输入有着某些特定关系的量
2 算法分析基础
2.1 时间复杂度
-
定义:时间复杂度也称时间渐进复杂度,指算法中基本操作重复执行的次数,该次数是问题规模nnn的某个函数T(n)T(n)T(n)。当难以精确计算基本操作次数时,只需求出问题规模nnn的增长率即可;
-
示例:对于两个N×NN×NN×N矩阵相乘的算法,“乘法”运算是基本操作。整个算法的执行时间与该基本操作重复执行的次数n3n^3n3成正比,记作O(n3)O(n^3)O(n3);
for(i=0;i<=n;i++){for(j=0;j<=n;j++){c[i][j]=0;for(k=0;k<=n;k++){c[i][j]+=a[i][k]*b[k][j];}} }
-
常见时间复杂度比较:O(1)<O(log2n)<O(n)<O(nlog2n)<O(n2)<O(n3)<O(2n)O(1) < O(\log_2 n) < O(n) < O(n\log_2 n) < O(n^2) < O(n^3) < O(2^n)O(1)<O(log2n)<O(n)<O(nlog2n)<O(n2)<O(n3)<O(2n)
-
空间复杂度:指算法在运行过程中临时占用存储空间大小的度量,仅考虑运行过程中为局部变量分配的存储空间的大小。
2.2 递归式
- 作用:用于递归算法的时间复杂度分析;
- 主定理分析:设a≥1a \geq 1a≥1和b≥1b \geq 1b≥1为常数,f(n)f(n)f(n)为函数,T(n)T(n)T(n)为定义在非负整数上的递归式,T(n)=aT(n/b)+f(n)T(n)=aT(n/b)+f(n)T(n)=aT(n/b)+f(n)(其中n/bn/bn/b指⌊n/b⌋\lfloor n/b \rfloor⌊n/b⌋或⌈n/b⌉\lceil n/b \rceil⌈n/b⌉),那么T(n)T(n)T(n)可能有如下的渐进紧致界:
- 若对于常数ε>0\varepsilon > 0ε>0,有f(n)=O(nlogba−ε)f(n) = O(n^{\log_b a - \varepsilon})f(n)=O(nlogba−ε),则T(n)=O(nlogba)T(n) = O(n^{\log_b a})T(n)=O(nlogba)
- 若f(n)=O(nlogbalgkn)f(n) = O(n^{\log_b a}\lg^k n)f(n)=O(nlogbalgkn),则T(n)=O(nlogbalgk+1n)T(n) = O(n^{\log_b a}\lg^{k + 1} n)T(n)=O(nlogbalgk+1n)
- 若对于常数ε>0\varepsilon > 0ε>0,有f(n)=Ω(nlogba+ε)f(n) = \Omega(n^{\log_b a + \varepsilon})f(n)=Ω(nlogba+ε),且对于常数c>1c > 1c>1与所有足够大的nnn有af(b/n)≤cf(n)af(b/n) \leq cf(n)af(b/n)≤cf(n),则T(n)=O(f(n))T(n) = O(f(n))T(n)=O(f(n))
2.3 练习
-
已知算法A的运行时间函数为T(n)=8T(n/2)+n2T(n) = 8T(n/2) + n^2T(n)=8T(n/2)+n2,其中nnn表示问题的规模,则该算法的时间复杂度为()。另已知算法B的运行时间函数为T(n)=XT(n/4)+n2T(n) = XT(n/4) + n^2T(n)=XT(n/4)+n2,其中nnn表示问题的规模。对充分大的nnn,若要算法B比算法A快,则XXX的最大值为()。
- A.O(n)O(n)O(n)
- B.O(nlogn)O(nlogn)O(nlogn)
- C.O(n2)O(n^2)O(n2)
- D.O(n3)O(n^3)O(n3)
- A.15
- B.17
- C.63
- D.65
D C
第一空:
-
算法A的运行时间函数为 T(n)=8T(n/2)+n2T(n) = 8T(n/2) + n^2T(n)=8T(n/2)+n2,对比主定理形式 T(n)=aT(n/b)+f(n)T(n) = aT(n/b) + f(n)T(n)=aT(n/b)+f(n),得到 a=8a = 8a=8,b=2b = 2b=2,f(n)=n2f(n) = n^2f(n)=n2;
-
计算 $ \log_b a :::\log_2 8 = 3$
-
根据主定理的第一种情况:若存在常数 ε>0\varepsilon > 0ε>0,使得 f(n)=O(nlogba−ε)f(n) = O\left(n^{\log_b a - \varepsilon}\right)f(n)=O(nlogba−ε),则 T(n)=O(nlogba)T(n) = O\left(n^{\log_b a}\right)T(n)=O(nlogba)
-
验证 f(n)f(n)f(n) 是否满足条件:需要找 ε>0\varepsilon > 0ε>0,使得 n2=O(n3−ε)n^2 = O\left(n^{3 - \varepsilon}\right)n2=O(n3−ε)。取 ε=1\varepsilon = 1ε=1,则 n2=O(n3−1)=O(n2)n^2 = O\left(n^{3 - 1}\right) = O(n^2)n2=O(n3−1)=O(n2),显然成立。因此,算法A的时间复杂度为 O(n3)O\left(n^3\right)O(n3),对应选项 D;
第二空:
-
算法B的运行时间函数为 T(n)=XT(n/4)+n2T(n) = XT(n/4) + n^2T(n)=XT(n/4)+n2,同样对比主定理形式 T(n)=aT(n/b)+f(n)T(n) = aT(n/b) + f(n)T(n)=aT(n/b)+f(n),得到a=Xa = Xa=X,b=4b = 4b=4,f(n)=n2f(n) = n^2f(n)=n2;
-
计算 $ \log_b a :::\log_4 X$
-
要使算法B比算法A快,算法B的时间复杂度需低于 O(n3)O(n^3)O(n3)。根据主定理的第一种情况,需要满足 f(n)=O(nlogba−ε)f(n) = O\left(n^{\log_b a - \varepsilon}\right)f(n)=O(nlogba−ε);
-
若算法B的时间复杂度与算法A相同(均为 O(n3)O(n^3)O(n3)),则需满足 log4X=3\log_4 X = 3log4X=3(此时 nlogba=n3n^{\log_b a} = n^3nlogba=n3),则X=43=64X = 4^3 = 64X=43=64;
-
要使算法B比算法A快,则 XXX 需小于 646464。因此,XXX 的最大值为 636363,对应选项 C。
3 分治法
3.1 递归的概念
-
递归是子程序(或函数)直接或间接调用自身的过程。有两个基本要素:
- 递归出口(边界条件):明确递归何时终止,是递归过程的终点
- 递归体:描述大问题如何分解为小问题,通过不断分解小问题来推进递归过程
-
阶乘函数的递归示例
n!={1,n=0n(n−1)!,n>0n! = \begin{cases} 1, & n = 0 \\ n(n - 1)!, & n > 0 \end{cases}n!={1,n(n−1)!,n=0n>0int Factorial(int num){if(num == 0) {return 1;}if(num > 0) {return num * Factorial(num - 1);} }
- 当
num == 0
时,触发递归出口,返回1; - 当
num > 0
时,执行递归体,将num
的阶乘分解为num
乘以num - 1
的阶乘。
- 当
3.2 基本思想
- 核心思想:将难以直接解决的大问题,分解成若干规模较小且与原问题相同的子问题,逐个解决子问题(分而治之),最后将子问题的解合并得到原问题的解;
- 分治法的求解步骤
- 分解:把原问题拆分成一系列子问题,子问题的结构与原问题一致
- 求解:递归地求解各个子问题。若子问题的规模足够小,可直接求解
- 合并:将各个子问题的解合并起来,得到原问题的解
3.3 典型实例
- 归并排序:把待排序的元素分成大小大致相等的两个子序列,分别对这两个子序列进行排序,最后将排好序的子序列合并成一个有序序列;
- 最大子段和问题:给定由nnn个整数(可能有负数)组成的序列$ a_1, a_2, \cdots, a_n ,求该序列形如,求该序列形如,求该序列形如 \sum_{k = i}^{j} a_k 的字段和的最大值。当序列中所有整数均为负整数时,其最大子段和为0,所求的最大值为的字段和的最大值。当序列中所有整数均为负整数时,其最大子段和为0,所求的最大值为的字段和的最大值。当序列中所有整数均为负整数时,其最大子段和为0,所求的最大值为 \max{0, \max_{1 \leq i \leq j \leq n} \sum_{k = i}^{j} a_k} $。
3.4 练习
-
折半查找在有序数组A中查找特定的记录K:通过比较K和数组中的中间元素A[mid]进行比较,如果相等,则算法结束;如果K小于A[mid],则对数组的前半部分进行折半查找;否则对数组的后半部分进行折半查找。根据上述描述,折半查找采用了()算法设计策略。对有序数组(3,14,27,39,42,55,70,85,93,98),成功查找和失败查找所需要的平均比较次数分别是()(假设查找每个元素的概率是相同的)。
-
A.分治
-
B.动态规划
-
C.贪心
-
D.回溯
-
A.29/10和29/11
-
B.30/10和30/11
-
C.29/10和39/11
-
D.30/10和40/11
A C
将大问题分解成多个与原问题相同的子问题,分而治之,递归求解,即分治法
成功查找次数:1+2+3+3+4+2+3+4+3+4=29次,一共10个元素,即平均比较次数为29/10
- 先取中间元素(low+high)/2 = 4,所以中间元素取42
- 查找42,查找次数为1
- 查找14,查找次数为2(先找42,再找到14)
- 查找3,查找次数为3(42 >> 14 >> 3)
- 查找27,查找次数为3(42 >> 14 >> 27)
- 查找39,查找次数为4(42 >> 14 >> 27 >> 39)
- 查找85,查找次数为2(42 >> 85)
- 查找55,查找次数为3(42 >> 85 >> 55)
- 查找70,查找次数为4(42 >> 85 >> 55 >> 70)
- 查找93,查找次数为3(42 >> 85 >> 93)
- 查找98,查找次数为3(42 >> 85 >> 93 >> 98)
失败查找次数:每个元素的查找成功次数+1即是失败次数,即29+10=39次,10个元素都查找成功再加一个失败即11个,即平均查找次数为39/11
-
4 动态规划法
4.1 基本思想
-
与分治法的联系与区别:
- 和分治法类似,都是将待求解问题分解为若干子问题,先解子问题再由子问题解得到原问题解;
- 但分治法分解出的子问题往往相互独立,动态规划法分解出的子问题不独立;
-
适用问题:常用于求解具有最优性质的问题,这类问题有多个可行解,每个解对应一个值,需找出具有最优值(最大值或最小值)的解,且能找出其中一个最优解;
-
基本步骤
- 找出最优解的性质,刻画其结构特征
- 递归定义最优解的值
- 以自底向上的方式计算出最优值
- 构造最优解
-
应用场景
- 最优子结构:若问题的最优解包含其子问题的最优解,该问题具有最优子结构(此时贪心算法也可能适用)
- 重叠子问题:求解原问题的递归算法会反复解同样的子问题,而非总是产生新子问题,即递归算法不断调用同一个问题时,该问题包含重叠子问题
4.2 典型实例
- 0 - 1背包问题
- 问题描述:有nnn个物品,第iii个物品价值为viv_ivi,重量为wiw_iwi(viv_ivi、wiw_iwi均为非负数),背包容量为WWW(非负数)。需选择物品装入背包,使总价值最大。形式化描述为:
- 目标函数:max∑i=1nvixi\max\sum_{i = 1}^{n} v_i x_imax∑i=1nvixi
- 约束条件:∑i=1nwixi≤W\sum_{i = 1}^{n} w_i x_i \leq W∑i=1nwixi≤W,xi∈{0,1}x_i \in \{0, 1\}xi∈{0,1}(xi=1x_i = 1xi=1表示装入第iii个物品,xi=0x_i = 0xi=0表示不装入)
- 求解步骤
- 刻画0 - 1背包问题最优解的结构;
- 递归定义最优解的值:设c[i,w]c[i, w]c[i,w]表示前iii个物品放入容量为www的背包的最大价值,则
c[i,w]={0,i=0 或 w=0c[i−1,w],wi>wmax{c[i−1,w−wi]+vi,c[i−1,w]},i>0 且 wi≤w c[i, w] = \begin{cases} 0, & i = 0 \text{ 或 } w = 0 \\ c[i - 1, w], & w_i > w \\ \max\{c[i - 1, w - w_i] + v_i, c[i - 1, w]\}, & i > 0 \text{ 且 } w_i \leq w \end{cases} c[i,w]=⎩⎨⎧0,c[i−1,w],max{c[i−1,w−wi]+vi,c[i−1,w]},i=0 或 w=0wi>wi>0 且 wi≤w - 计算最优解并构造最优解;
- 问题描述:有nnn个物品,第iii个物品价值为viv_ivi,重量为wiw_iwi(viv_ivi、wiw_iwi均为非负数),背包容量为WWW(非负数)。需选择物品装入背包,使总价值最大。形式化描述为:
- 最长公共子序列(LCS):用于寻找两个序列的最长公共子序列,子序列不需要连续但顺序需一致,也是动态规划的经典应用场景。
5 贪心法
5.1 基本思想
- 核心思想:贪心法在每个决策点做出当前看来最佳的选择,每一步都取最优,且做出决策后不会再改变。贪心法能做到局部最优,期望以此达到全局最优;
- 使用贪心法的重要性质
- 最优子结构:当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构;
- 贪心选择性质:指问题的整体最优解可以通过一系列局部最优的选择(即贪心选择)来得到。这是贪心法和动态规划法的主要区别。
5.2 典型实例
- 活动选择问题:若干个具有竞争性的活动要求互斥使用某一公共资源时,如何选择最大的相容活动集合;
- 背包问题:与0 - 1背包问题不同,在该背包问题里,每个物品可以部分装入背包。
5.3 练习
C A
每次选择权值最小的边连接,直至连接所有顶点,且不能出现环路。
6 回溯法
6.1 算法框架
- 别称与作用:回溯法有“通用的解题法”之称,可系统地搜索一个问题的所有解或任一解;
- 搜索策略:在包含问题所有解的解空间树中,按照深度优先的策略,从根结点出发搜索解空间树。搜索至任一结点时,先判断该结点是否肯定不包含问题的解,若不包含,则跳过对以该结点为根的子树的搜索,逐层向其祖先结点回溯;若包含,则进入该子树,继续按深度优先的策略进行搜索;
- 直观理解:先进行深度优先搜索,一直向下探测,当此路不通时,返回上一层搜索另外的分支,重复此步骤,这就是回溯,即先一直探测,当不成功时再返回上一层。
6.2 典型实例
- 0 - 1背包问题:是回溯法的典型应用场景之一;
- (n)皇后问题:来源于国际象棋的问题,要求在一个n∗nn*nn∗n格的棋盘上放置nnn个皇后,使得任何两个皇后不能被放在同一行或同一列或同一条斜线上。
6.3 练习
-
在求解某问题时,经过分析发现该问题具有最优子结构性质,求解过程中子问题被重复求解,则采用()算法设计策略;若定义问题的解空间,以深度优先的方式搜索解空间,则采用()算法设计策略。
-
A.分治
-
B.动态规划
-
C.贪心
-
D.回溯
-
A.动态规划
-
B.贪心
-
C.回溯
-
D.分支限界
-
B C
7 分支界限法
- 与回溯法的联系:分支限界法类似于回溯法,都是在问题的解空间树TTT上搜索问题解的算法;
- 求解目标差异:
- 回溯法的求解目标是找出解空间树中满足约束条件的所有解;
- 而分支限界法的求解目标是找出满足约束条件的一个解,或是在满足约束条件的解中找出使某一目标函数值达到极大或极小的解(即在某种意义下的最优解);
- 搜索方式差异:
- 回溯法以深度优先的方式搜索解空间树TTT;
- 分支限界法以广度优先或最小耗费优先的方式搜索解空间树。并且,分支限界法可用于解决大量离散最优化的实际问题。
8 概率算法
- 适用场景:当一个问题没有有效的确定性算法能在合理时间内给出解,但该问题能接受小概率错误时,可采用概率算法快速找到问题的解;
- 分类:概率算法大致分为4类,分别是数值概率算法、蒙特卡罗(Monte Carlo)算法、拉斯维加斯(Las Vegas)算法和舍伍德(Sherwood)算法。
9 近似算法
- 基本思想:近似算法是解决难解问题的有效策略,其核心是放弃求最优解,改用近似最优解来替代,以此换取算法设计的简化以及时间复杂度的降低;
- 性能衡量标准:衡量近似算法性能最重要的两个标准为:
- 算法的时间复杂度;
- 解的近似程度。
10 数据挖掘算法
-
数据挖掘概述:随着数据爆炸式增长,需用技术发现数据中隐含的有价值信息。数据挖掘利用机器学习方法,对数据库数据、数据仓库数据、Web数据等多种数据进行分析挖掘。其核心是算法,主要功能包含分类、回归、关联规则和聚类等;
-
分类
-
分类是有监督的学习过程,依据历史数据预测未来数据的模型。涉及的数据有训练数据集、测试数据集和未知数据;
-
分类步骤:
- 学习模型:基于训练数据集,采用分类算法建立学习模型;
- 应用模型:将测试数据集的数据应用到学习模型中,评估模型好坏;还可将未知数据输入学习模型,预测数据类型;
-
分类算法:决策树(ID3、C4.5、CART)、支持向量机(SVM)、朴素贝叶斯、后向传播(BP);
-
-
频繁模式和关联规则挖掘
-
挖掘海量数据中的频繁模式和关联规则,能有效指导企业发现交叉销售机会、进行决策分析与商务管理(如沃尔玛 - 啤酒尿布的故事);
-
流程:先求出数据集中的频繁模式,再由频繁模式产生关联规则;
-
关联规则挖掘算法:类Apriori算法、基于频繁模式增长的方法(如FP - growth)、使用垂直数据格式的算法(如ECLAT);
-
-
聚类
-
聚类是无监督学习过程,依据数据特征,把相似的数据对象归为一类,不相似的归到不同类中,体现“物以类聚,人以群分”的特点;
-
聚类算法:基于划分的方法、基于层次的方法、基于密度的方法、基于网格的方法、基于统计模型的方法。
-
11 智能优化算法
-
智能优化算法概述:优化技术以数学为基础,用于求解各种工程问题的优化解,是一种应用技术;
-
人工神经网络:人工神经网络(ANN)是一个以有向图为拓扑结构的动态系统,通过对连续或断续的输入作状态响应来进行信息处理。它从信息处理角度对人脑神经元网络进行抽象,建立简单模型,按不同连接方式组成不同网络。深度学习的概念源于人工神经网络的研究,是机器学习研究中的一个新领域;
-
遗传算法:遗传算法模拟达尔文“优胜劣汰、适者生存”的进化论和孟德尔 - 摩根的遗传变异理论,在迭代过程中保持已有的结构,同时寻找更好的结构。其本意是在人工适应系统中设计一种基于自然的演化机制;
-
模拟退火算法:模拟退火算法(SA)是一种全局优化算法,基本思想来源于物理退火过程,包含加温阶段、等温阶段、冷却阶段。将固体加温至充分高,再徐徐冷却,加温时固体内部粒子随温度升高变为无序状,内能增大;徐徐冷却时粒子渐趋有序,在每个温度都达到平衡态,最后在常温时达到基态,内能减为最小;
-
禁忌搜索算法:禁忌搜索算法(TS)是模拟人类智力过程的一种全局搜索算法,是对局部邻域搜索的扩展。从一个初始可行解出发,选择一系列使目标函数值减少最多(假设求极小值问题)的特定搜索方向(即移动)作为试探,同时为避免陷入局部最优解,采用灵活的“记忆”技术(建立禁忌表),对已进行的优化过程进行记录和选择,指导下一步搜索方向;
-
蚁群算法:蚁群算法是一种用来寻找优化路径的概率型算法。单个蚂蚁行为简单,但蚁群整体能体现智能行为。蚂蚁会在经过的路径上释放“信息素”,蚁群内蚂蚁对“信息素”有感知能力,会沿着“信息素”浓度较高的路径行走,且每只路过的蚂蚁都会留下“信息素”,形成类似正反馈的机制,经过一段时间后,整个蚁群会沿着最短路径到达食物源;
-
粒子群优化算法:粒子群优化算法又称鸟群觅食算法,鸟群觅食飞行时会突然改变方向、散开、聚集,行为不可预测,但整体总保持一致性,个体间也保持最适宜的距离。通过对类似生物群体行为的研究,发现生物群体中存在信息共享机制,为群体进化提供优势,这是基本粒子群算法形成的基础。