二分搜索算法的时间复杂度是 O(logn),但前提是数组必须是有序的
Dijkstra 算法适用于带权有向图中求解单源最短路径,但前提是边的权值非负
哈夫曼编码对于给定的字符集,各字符编码不是唯一的
? 求解活动安排问题的贪心算法GreedySelector的时间复杂性为O(n)
0-1 背包问题的解空间树不是排列树,是一棵子集树
在分支限界法中,每个活结点 不一定 只有2个机会成为扩展结点。
通常,我们通过最坏情况来分析算法。通过最坏情况分析,我们能够保证一个算法的运行时间上限。
假如装载问题的两艘轮船B1和B2的承载重量分别是C1和C2(C1>C2),那么解决装载问题可以先解决其中一艘轮船的0-1背包问题,请问选择哪一艘船更合理?
(装载问题是要将一批集装箱装上两艘轮船,使两艘轮船的载重量尽可能接近)
- 选B2更合理
归并排序、快速排序、二分搜索算法使用了分治思想;分治法可以处理棋盘覆盖问题
最长公共子序列、0/1背包问题使用的是动态规划算法
动态规划算法自底向上(从最小的子问题向原始问题),贪心算法自顶向下(从原始问题向逐步产生的子问题)
回溯法解TSP问题时的解空间树是排列树
贪心算法与动态规划算法的主要区别是( B )。
A、最优子结构 B、贪心选择性质 C、构造最优解 D、定义最优解
采用最大效益优先搜索方式的算法是( A )。
A、分支界限法 B、动态规划法 C、贪心法 D、回溯法
( D )是贪心算法与动态规划算法的共同点。
A、重叠子问题 B、构造最优解 C、贪心选择性质 D、最优子结构性质
下列是动态规划算法基本要素的是( D )。
A、定义最优解 B、构造最优解 C、算出最优解 D、子问题重叠性质
分支限界法两种常见方法为:队列式分支限界法和优先队列式分支限界法。
回溯法的效率不依赖于 确定解空间的时间
算法是对特定问题求解步骤的一种描述,是由若干条指令组成的有限序列。要满足输入、输出、确定性和有限性四条性质。
算法的求解步骤需要满足1.有穷性2.确定性3.可行性
好算法:正确性、健壮性、可理解性、抽象分级、高效性
算法设计的一般过程:1.分析问题2.选择算法设计技术3.设计并描述算法4.手工运行算法5.分析算法的效率6.实现算法
度量算法效率:事先统计、事后分析
算法的时间复杂性指算法中 基础语句 的执行次数
算法的三要素1、操作2、控制结构3、数据结构
限界剪枝法适用于求解最优化问题
问题变换的主要目的不是给出解决一个问题的算法,而是给出比较两个问题计算复杂性的一种方式
汉诺塔问题不是 NP类问题
计算的实质:从一个符号串 f(输入)得出另一个符号串 g(输出)
虽然程序只能按线性顺序来表示指令序列,但程序的实际执行可以与表示的顺序不同
实现最大子段和利用的算法是“动态规划算法”
解析:最大子段和可以用暴力算法、分治算法和动态规划算法来解决,
可是这三种算法的时间复杂度不一样,分别为O(n2)、O(nlogn)、O(n);
根据三者的时间复杂度比较:O(n2)>O(nlogn)>O(n)
可以得知动态规划算法解决最大子段和是最好的算法。
算法设计->算法的正确性证明->算法的复杂性分析->程序设计
模拟法:是指以一定的假设条件和数据为前提,借助仿真技术来模拟现实世界中的某个过程、系统或现象。它通过运行一系列的步骤或规则来模拟目标对象的行为,并生成与真实情况相似的结果。 用模拟法求解问题的基本思想是对问题进行抽象
递推法:通过初始条件,根据递推关系式,按照一定的规律逐项进行计算,直至得到结果。
蛮力法:也称穷举法或枚举法,一种简单直接地解决问题的方法,采用一定的策略依次处理待求解问题的所有元素,从而找出问题的解。
蛮力法的关键:依次处理所有元素
分治法:将一个难以直接解决的大问题划分成一些规模较小的子问题,分别求解各个子问题,再合并子问题的解得到原问题的解。
启发式原则:(1)平衡子问题(2)各子问题之间相互独立
图灵机是一种虚拟的机器,用于模拟计算和决策过程。它通过将人们使用纸笔进行数学运算的过程进行抽象,从而用一个虚拟的机器替代人们进行数学运算。
组成部分:图灵机主要由一条无限长的纸带、一个读写头、一套控制规则和一个状态寄存器组成
反向读取的优势:
保证稳定性:反向读取使得具有相同键值的元素的相对顺序在排序后保持不变。
简化算法:不需要额外的逻辑来处理重复元素的位置,因为每个元素只会被放置一次,且是从后向前放置的。
动态规划算法基本步骤
(1)找出最优解性质
(2)递归定义最优值
(3)算出最优值
(4)构造最优解
其中(1)-(3)为基本步骤,在只需要求出最优值的情形,(4)-构造最优解 可以省去
动态规划法的求解过程由以下三个阶段组成:
(1)划分子问题:将原问题的求解过程划分为若干个阶段,每个阶段对应一个子问题,并且子问题之间具有重叠关系;
(2)设计动态规划函数:根据子问题之间的重叠关系找到子问题满足的递推关系式,这是动态规划法的关键;
(3)填写表格:根据动态规划函数设计表格,以自底向上的方式计算各个子问题的最优值并填表,实现动态规划过程。
启发式算法是一种基于经验和直觉的求解问题的技术,旨在在可接受的时间内找到一个足够好的近似解。常见的包括贪心法、爬山法、模拟退火法、遗传算法等。
分治法所能解决的问题一般具有以下几个特征:
1) 该问题的规模缩小到一定的程度就可以容易地解决
2) 该问题可以分解为若干个规模较小的 相同问题,即该问题具有最优子结构性质。
3) 利用该问题分解出的子问题的解可以合并为该问题的解;
4) 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题。
为什么把多项式时间复杂性作为易解问题和难解问题的分界线呢?
1.多项式函数与指数函数的增长率有本质的差别。
2.计算机性能的提高对多项式时间算法和指数时间算法的影响不同。
3.多项式时间复杂性忽略了系数,但不影响易解问题和难解问题的划分。
不可计算问题的典型例子:
停机问题;
判断一个程序中是否包含计算机病毒
应用限界剪枝法的关键问题如下:
(1)确定合适的限界函数。好的限界函数要求:① 计算简单;②保证最优解在搜索空间中;③能在搜索的早期对超出目标函数界的结点进行丢弃,减少搜索空间,从而尽快找到问题的最优解。
(2)open表的存储结构。为了能快速在open表中选取使目标函数取得极值的结点,通常采用优先队列存储open表。如果open表的结点数不是很多,也可以简单的采用数组存储。
(3)确定最优解的各个分量。限界剪枝法跳跃式处理搜索空间中的结点,需要对每个扩展结点保存该结点到根结点的路径,或者在搜索过程中构建搜索经过的树结构。
简述动态规划法与分治法的异同。
两者的共同点:是将待求解的问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。
两者的不同点如下:
适合用动态规划法求解的问题,分解得到的各子问题往往不是相互独立的(重叠子问题性质),而分治法中的子问题相互独立;
另外,动态规划法用表保存已求解过的子问题的解,再次碰到同样的子问题时不必重新求解,只需查询答案,故可获得多项式级时间复杂度,效率较高,而分治法中对于每次出现的子问题均求解,导致同样的子问题被反复求解,故产生指数增长的时间复杂度,效率较低。
分支限界法与回溯法的异同。
回溯法是一种搜索算法,它们的目的都是在一个给定的解空间内搜索最优解。分支限界法是基于搜索树的剪枝技术,它通过计算搜索树中节点的价值,从而确定搜索树中可用节点的最大数量,以避免无效搜索,而回溯法则是一种暴力搜索,它会搜索所有可能的解空间,然后逐步消除不可能的解,最终找到最优解。因此,分支限界法和回溯法的最大区别在于,前者是基于算法模型的,而后者是基于暴力搜索的。
分治和减治的区别
分治法将一个难以直接解决的大问题划分成一些规模较小的子问题,分别求解各个子问题,再合并子问题的解得到原问题的解。
减治法把一个大问题划分为若干个子问题,但是只需求解其中的一个子问题,也无需对子问题的解进行合并。
所以,严格地说,减治法应该是一种退化了的分治法。
第三章 模拟法
模拟法通常对某一类事件进行描述,然后经过简单计算给出符合要求的结果。通过对真实事物或过程的虚拟来解决问题。
用模拟法求解问题的基本思想是对问题进行抽象
(1)将现实世界的问题映射成计算机能够识别的符号表示
(2)将事物之间的关系映射成运算或逻辑控制
一、定义与特点
定义:
模拟法是指以一定的假设条件和数据为前提,借助仿真技术来估算任务的工期或模拟现实世界中的某个过程、系统或现象。它通过运行一系列的步骤或规则来模拟目标对象的行为,并生成与真实情况相似的结果。
特点:
思路简单:模拟法的核心在于根据题目或问题的要求,直接模拟出相应的过程或行为。
代码量大:由于需要详细模拟每一个步骤或行为,因此模拟法的代码量通常较大。
操作多:模拟过程中可能涉及多个操作或步骤,需要仔细处理每一个细节。
思路复杂:对于一些复杂的模拟问题,可能需要考虑多种情况和边界条件,使得思路变得复杂。
二、种类与分类
根据模型和原型之间的相似关系,模拟法可分为物理模拟和数学模拟两种。
物理模拟是通过构建物理模型来模拟原型的行为和特征;
数学模拟则是通过数学方程或算法来模拟原型的行为和特征。
三、应用与实例
模拟法在算法设计中有着广泛的应用,以下是一些具体的实例:
字符串替换:
如LeetCode中的“替换所有的问号”问题,通过遍历字符串并替换其中的“?”字符来避免连续重复字符的出现。
时间计算:
如“提莫攻击”问题,通过模拟提莫的攻击时间和中毒状态来计算艾希处于中毒状态的总秒数。
四、优点与缺点
优点:
经济:模拟法可以在不直接操作原型的情况下进行研究,从而节省成本。
易于控制:通过模拟过程可以方便地控制各种变量和条件。
可观察:模拟过程的结果可以直接观察和分析。
缺点:
人为性:模拟过程中可能存在人为的假设和简化,导致结果不够准确。
复杂性:对于复杂的系统或过程,模拟法的实现可能非常困难。
局限性:模拟法只能模拟出已知或可预测的情况,对于未知或不可预测的情况则无能为力。
鸡兔同笼问题
约瑟夫环问题
埃拉托色尼筛法(埃氏筛法,素数筛)
基本思想是,假定区间[1, n]内的所有数都是素数,再去掉所有合数,剩下的就是所有素数。判断合数的方法是从 2 开始依次过筛,如果是 2 的倍数则该数不是素数,进行标记处理,直至将 n/2 过筛,将所有合数打上标记。
循环变量 i 从 2 ~ n/2
时间复杂度O(n log log n)
计数排序
第一步:统计值为 i(1 ≤ i ≤ k)的记录个数存储在 num1[i]中
第二步:统计小于等于i(1 ≤ i ≤ k)的记录个数存储在 num2[i]中
第三步:反向读取数组 A[n] 填到数组 B中
时间复杂度O(n+k)
为什么要反向读取数组?
保证排序的稳定性,使得具有相同键值的元素在排序前后保持原有的相对顺序不变的性质
反向读取的优势:
- 保证稳定性:反向读取使得具有相同键值的元素的相对顺序在排序后保持不变。
- 简化算法:不需要额外的逻辑来处理重复元素的位置,因为每个元素只会被放置一次,且是从后向前放置的。
颜色排序
要求重新排列一个由 Red、Green 和 Blue 构成的数组,使得所有的 Red 都排在最前面,Green 排在其次,Blue 排在最后。
想法:设置三个参数 i、j、k,其中 i 之前的元素(不包括 a[i])全部为 Red;k 之后的元素(不包括 a[k])全部为Blue;a[j] 表示当前元素
- 初始化 i = 0; k = n–1; j = 0;
- 当j <= k 时,依次考查元素 a[j],有以下三种情况:
- 如果 a[j] 是 Red,则交换 a[i] 和 a[j];i++; j++;
- 如果 a[j] 是 Green,则 j++;
- 如果 a[j] 是 Blue,则交换 a[k] 和 a[j];k--;
时间复杂度 O(n)
装箱问题
先装入长宽为 3×3、4×4、5×5、6×6,然后计算长宽为 2 和 1 的空位数量
时间复杂度O(1)
数字回转方阵
时间复杂度O(n^2)
第四章 递推法
一种根据递推关系进行问题求解的方法。
递推法:通过初始条件,根据递推关系式,按照一定的规律逐项进行计算,直至得到结果。
递推法有正推和逆推两种形式:
(1)正推:从前向后递推,已知小规模问题的解递推到大规模问题的解;
(2)逆推:从后向前递推,已知大规模问题的解递推到小规模问题的解。
无论正推还是逆推,关键都是要找到递推关系式。
------------------------------------------------------------------------------------------------------------------------
猴子吃桃问题
for (i = n - 1; i >= 1; i--)
num = (num + 1) * 2
时间复杂度O(n)
------------------------------------------------------------------------------------------------------------------------
斐波那契数列
f(n) = f(n - 1) + f(n - 2)
时间复杂度O(n)
------------------------------------------------------------------------------------------------------------------------
Catalan数列
计算凸多边形的三角形剖分问题
通过插入内部不相交对角线将其剖分成一些三角形区域,问有多少种不同的分法?
Catalan数列的前 5 项是{1, 2, 5, 14, 42, …}
取凸 n 边形的任一个顶点 Ak(2 ≤ k ≤ n-1),将 Ak 分别与 A1 和 An 连线得到三角形 T,则三角形 T 将凸 n 边形分成 R1、T 和 R2 三个部分,其中 R1 为凸 k 边形,R2 为凸 n-k+1 边形
for (i = 4; i <= n; i++)
int c[n+1] = {0, 0, 1, 1}
temp = 0;
for (k = 2; k < i; k++)
temp += c[k] * c[i-k+1];
时间复杂度O(n^2)
------------------------------------------------------------------------------------------------------------------------
伯努利错装信封问题
递推公式:Dn=( n−1 ) (Dn−1+Dn−2 ) ( n>2 )
时间复杂度O(n)
------------------------------------------------------------------------------------------------------------------------
旋转的万花筒
时间复杂度O(n)
------------------------------------------------------------------------------------------------------------------------
整数划分
对于一个大于 2 的整数 n,要求仅使用 2 的若干次幂的整数集合进行划分,使得集合中所有整数之和等于 n,有多少种划分方法?
时间复杂度O(n)
------------------------------------------------------------------------------------------------------------------------
一阶自回归模型(AR(1))是时间序列分析中的一种基本模型,用于描述一个变量与其自身过去值之间的关系。在AR(1)模型中,当前值被假定为是其前一期值和一个随机误差项的线性组合。这种模型特别适用于那些表现出一定惯性或记忆性的时间序列数据。
AR(1)模型的形式如下:
Xt=ϕXt−1+ϵt
Xt 是时间序列在时刻 t的值。
ϕ 是自回归系数,它决定了前一期值 Xt−1 对当前值 Xt 的影响程度。
ϵt 是随机误差项,通常假设它是独立同分布的,均值为0,方差为 σ2。
自回归系数 ϕ:
当 ∣ϕ∣<1时,模型是稳定的,意味着过去的值对当前值的影响会逐渐减弱,时间序列会趋向于一个常数(如果 ϕ 接近0)或围绕某个均值波动(如果 ϕ在0和1之间)。
当 ∣ϕ∣=1时,模型处于单位根状态,可能表现出随机游走或趋势行为。
当 ∣ϕ∣>1时,模型是不稳定的,过去的值对当前值的影响会无限放大,导致时间序列发散
第五章 蛮力法
也称穷举法或枚举法,一种简单直接地解决问题的方法,采用一定的策略依次处理待求解问题的所有元素,从而找出问题的解。
蛮力法的关键:依次处理所有元素
(1)确定穷举的范围
(2)保证处理过的元素不再被处理(为了避免陷入重复试探)
用蛮力法解决问题,通常可以从两个方面进行算法设计:
(1)找出枚举范围:分析问题所涉及的各种情况。
(2)找出约束条件:分析问题的解需要满足的条件,并用逻辑表达式表示
理论上,蛮力法可以解决可计算领域的各种问题
------------------------------------------------------------------------------------------------------------------------
百元买百鸡问题
时间复杂度O(n^2)
------------------------------------------------------------------------------------------------------------------------
顺序查找
在一个整数集合中查找值为 k 的元素。
将集合中的元素逐个与给定值 k 进行比较,下标 i 初始化在数组的高端
为了避免在查找过程中每一次比较后都要判断查找位置是否越界,可以设置观察哨(sentinel),即将待查值放在查找方向的“尽头”处,则比较位置i至多移动到下标0处,也就是“哨兵”的位。(改进算法进行一次顺序查找的时间几乎减少一半。)
时间复杂度O(n)
------------------------------------------------------------------------------------------------------------------------
串匹配(模式匹配)
在主串 S 中寻找子串 T 的过程
串匹配问题的特点:
(1) 算法的一次执行时间:问题规模通常很大,常常在大量信息中进行匹配
(2) 算法改进所取得的积累效益:串匹配操作经常被调用,执行频率高
蛮力法:BF算法
时间复杂度O(m * n)
缺点:在每趟匹配不成功时存在大量回溯,没有利用已经部分匹配的结果
KMP 算法:对于 BF 算法进行了很大改进,基本思想是主串不进行回溯,模式 T 要回溯到某一个字符。
-1 j = 0
max{k | 1≤k<j 且T[0] … T[k-1] = T[j-k] … T[j-1]} 集合非空
0 其它情况
下标: 0 1 2 3 4
模式串T: a b a b c
k = next[j]: -1 0 0 1 2
j=0时, k=-1
j=1时, k=0
j=2时, T[0]≠T[1],因此,k = 0
j=3时, T[0]=T[2],T[0]T[1] ≠T[1]T[2],因此,k = 1
j=4时, T[0] ≠ T[3],T[0]T[1] = T[2]T[3],T[0]T[1]T[2]≠T[1]T[2]T[3],因此,k = 2
时间复杂度O(n)
------------------------------------------------------------------------------------------------------------------------
选择排序
(selection sort)的基本思想是:
第 i 趟排序在无序序列 ri ~ rn中找到值最小的记录,并和第 i 个记录交换作为有序序列的第 i 个记录。
时间复杂度O(n^2)
------------------------------------------------------------------------------------------------------------------------
冒泡排序
(bubble sort)的基本思想是:两两比较相邻记录,如果反序则交换,直至没有反序的记录。
时间复杂度O(n^2)
------------------------------------------------------------------------------------------------------------------------
用蛮力法设计的算法,一般来说,都可以对算法的第一个版本进行一定程度的改良,改进其时间性能,但只能减少系数,而数量级不会改变
------------------------------------------------------------------------------------------------------------------------
哈密顿回路问题
要求从一个城市出发,经过每个城市恰好一次,然后回到出发城市
蛮力法:考察图中所有顶点的全排列
时间复杂度:O(n!)
------------------------------------------------------------------------------------------------------------------------
TSP问题
(traveling salesman problem)是指旅行家要旅行 n 个城市然后回到出发城市,要求各个城市经历且仅经历一次,并要求所走的路程最短。
蛮力法:考察图中所有顶点的全排列
时间复杂度:O(n!)
------------------------------------------------------------------------------------------------------------------------
最近对问题
要求在包含n个点的集合中找出距离最近的两个点。严格地讲,距离最近的点对可能多于一对,简单起见,只找出其中的一对即可。
在求欧几里得距离时,可以免去求平方根操作。
时间复杂度O(n^2)
------------------------------------------------------------------------------------------------------------------------
凸包问题
要求为平面上具有 n 个点的集合S构造最小凸多边形。
(如果以集合中任意两点 P 和 Q 为端点的线段上的点都属于该集合,则称该集合是凸集合)
一个点集 S 的凸包(convex hull)是包含 S 的最小凸集合,其中,最小是指 S 的凸包一定是所有包含 S 的凸集合的子集。最小凸多边形上的点称为凸包的极点(extreme dot)。
想法:设经过集合 S 中两个点 (xi, yi) 和 (xj, yj) 的线段是 lij,如果该集合中的其他点都位于线段 lij 的同一侧(假定不存在三点同线),则线段 lij 是该集合凸包边界的一部分。时间复杂度O(n^2)
------------------------------------------------------------------------------------------------------------------------
KMP算法中next值的计算
设模式的长度为 m,用蛮力法求解 KMP算法中的 next 值时,最坏情况下的时间代价是:O(m^2)
实际上,只需将模式扫描一遍,能够在线性时间内求得模式的 next 值。
例如,模式T="abaababc"的next值计算如下:
j=0 时,next[0]=-1
j=1 时,k=next[0]=-1,next[1]=0
j=2 时,k=next[1]=0,T[0]≠T[1];k=next[k]=next[0]=-1,next[2]=0
j=3 时,k=next[2]=0,T[0]=T[2],next[3]=k+1=0+1=1
j=4 时,k=next[3]=1,T[1]≠T[3];k=next[k]=next[1]=0,T[0]=T[3],next[4]=k+1=1
j=5 时,k=next[4]=1,T[1]=T[4],next[5]=k+1=1+1=2
j=6 时,k=next[5]=2,T[2]=T[5],next[6]=k+1=2+1=3
j=7 时,k=next[6]=3,T[3]≠T[6];k=next[k]=next[3]=1,T[1]=T[6],next[7]=k+1=2
------------------------------------------------------------------------------------------------------------------------
0/1背包问题
蛮力法:考虑给定n个物品集合的所有子集,找出所有总重量不超过背包容量的子集,计算每个可能子集的总价值,然后找到价值最大的子集。
------------------------------------------------------------------------------------------------------------------------
第六章 分治法
分治法将一个难以直接解决的大问题划分成一些规模较小的子问题,分别求解各个子问题,再合并子问题的解得到原问题的解。一般来说,分治法的求解过程由以下三个阶段组成:
(1)划分:把规模为 n 的原问题划分为 k 个(通常 k = 2)规模较小的子问题。
(2)求解子问题:分别求解各个子问题。
(3)合并:把各个子问题的解合并起来。
从大量实践中发现,在进行问题划分时,遵循以下启发式原则:
(1)平衡子问题(balancing sub-problom):子问题的规模最好大致相同。
(2)各子问题之间相互独立:如果当子问题不是独立的,分治法可能会导致大量的重复计算,因为相同的子问题可能会被多次求解。
当各子问题不是独立的情况下,动态规划法相较于分治法更为优越
从静态行文的角度看,在定义一个函数时,若在函数体中出现对函数自身的调用,则称该函数是递归函数;
从动态执行的角度看,在调用一个函数时,若被调用函数尚未结束,又出现对函数自身的调用,则称该调用是递归调用。
递归必须具备以下两个基本要素,才能在有限次计算后得出结果:
(1)递归出口:确定递归到何时终止,即递归的结束条件;
(2)递归体:确定递归的方式,即原问题是如何分解为子问题的。
------------------------------------------------------------------------------------------------------------------------
汉诺塔问题
Hanio(n-1, A, C, B);
cout<<A<<"-->"<<C<<"\t";
Hanio(n-1, B, A, C);
2次递归 + 1次计数
时间复杂度O(2^n)
------------------------------------------------------------------------------------------------------------------------
归并排序
(1)划分:将待排序序列从中间位置划分为两个长度相等的子序列;
(2)求解子问题:分别对这两个子序列进行排序,得到两个有序子序列;
(3)合并:将这两个有序子序列合并成一个有序序列
合并可以就地进行吗?
不能。
int m = (s + t)/2; //划分
MergeSort(r, s, m); //归并排序前半个子序列
MergeSort(r, m+1, t); //归并排序后半个子序列
Merge(r, s, m, t); //合并两个有序子序列
时间复杂度O(nlog2n)
------------------------------------------------------------------------------------------------------------------------
快速排序
(1)划分:选定一个记录作为轴值,以轴值为基准将整个序列划分为两个子序列,轴值的位置在划分的过程中确定,并且左侧子序列的所有记录均小于或等于轴值,右侧子序列的所有记录均大于或等于轴值;
(2)求解子问题:分别对划分后的每一个子序列递归处理;
(3)合并:由于对子序列的排序是就地进行的,所以合并不需要执行任何操作。
int pivot = Partition(r, first, end); //划分,pivot是轴值的位置
QuickSort(r, first, pivot-1); //对左侧子序列进行快速排序
QuickSort(r, pivot+1, end); //对右侧子序列进行快速排序
时间复杂度O(nlog2n)
------------------------------------------------------------------------------------------------------------------------
最大子段和问题
给定由 n 个整数(可能有负整数)组成的序列(a1, a2, …, an),最大子段和问题要求该序列的累积的最大值。
(1)划分:划分为(a1, …, an/2 )和(an/2+1, …, an),则会出现以下三种情况:
① (a1, a2, …, an)的最大子段和 = (a1, …, an/2 )的最大子段和;
② (a1, a2, …, an)的最大子段和 = (an/2+1, …, an)的最大子段和;
③ (a1, a2, …, an)的最大子段和 = ai+...+aj,且1 ≤ i ≤ n/2,n/2+1 ≤ j ≤ n。
(2)求解子问题:分别求解划分阶段的三种情况。
(3)合并:取三者之中的较大者为原问题的解
时间复杂度O(nlog2n)
------------------------------------------------------------------------------------------------------------------------
棋盘覆盖问题
在一个2k×2k 个方格组成的棋盘中,恰有一个方格与其他方格不同,称该方格为一特殊方格,称该棋盘为一特殊棋盘。在棋盘覆盖问题中,要用 4 种不同形态的L型骨牌覆盖给定的特殊棋盘上除特殊方格以外的所有方格,且任何2 个L型骨牌不得重叠覆盖。
想法:
当 k > 0 时,将 2k×2k 棋盘分割为 4 个 2k-1×2k-1 子棋盘,特殊方格必位于4 个较小子棋盘之一中。为了将这 3 个无特殊方格的子棋盘转化为特殊棋盘,可以用一个 L 型骨牌覆盖这 3 个较小棋盘的会合处,从而将原问题转化为 4 个较小规模的棋盘覆盖问题。递归地使用这种分割,直至棋盘简化为 1×1 棋盘。
... ...略
时间复杂度O(4^k)
------------------------------------------------------------------------------------------------------------------------
最近对问题
要求在包含n个点的集合中找出距离最近的两个点。严格地讲,距离最近的点对可能多于一对,简单起见,只找出其中的一对即可。
最近对问题的分治策略是:
(1)划分:将集合 S 分成子集 S1 和 S2。S1={p∈S | xp≤m}和S2={q∈S | xq>m}。设集合 S 的最近点对是 pi和pj(1<=i,j<=n),则会出现以下三种情况:
① pi∈S1,pj∈S1;② pi∈S2,pj∈S2;③ pi∈S1,pj∈S2。
(2)求解子问题:分别求解以上三种情况的解。
(3)合并:比较在划分阶段三种情况下的最近点对,取三者之中的距离较小者为原问题的解。
6. 依次考察集合 S 中的点 p(x, y),
6.1 如果(x <= xm 并且 x >= xm-d),则将点 p 放入集合 P 中;
6.2 如果(x > xm 并且 x <= xm+d),则将点 p 放入集合 P 中;
7. 将集合 P 按 y 坐标升序排列;
8. 对集合 P 中的每个点 p(x, y),在 y 坐标区间[y, y+d]内计算与点 p 的最近距离 d3;
9. 返回min{d, d3}
时间复杂度O(nlog2n)
------------------------------------------------------------------------------------------------------------------------
凸包问题
要求为平面上具有 n 个点的集合 S 构造最小凸多边形
按照 x 轴坐标升序排列,则最左边的点p1和最右边的点pn一定是该集合的凸包极点
(1)划分:设 p1pn 是经过最左边的点 p1 和最右边的点 pn 的直线,则直线 p1pn 把集合 S 分成两个子集 S1 和 S2;
(2)求解子问题:求集合 S1 的上包和集合 S2 的下包;
(3)合并解:求解过程中得到凸包的极点,因此,合并步无须执行任何操作。
对于集合 S1,首先找到 S1中距离直线 p1pn 最远的点pmax。S1 中所有在直线 p1pmax上侧的点构成集合 S1,1,S1中所有在直线 pmaxpn 上侧的点构成集合 S1,2,包含在三角形 pmaxp1pn 之中的点可以不考虑了。递归地继续构造集合 S1,1 的上包和集合 S1,2 的上包,然后将求解过程中得到的所有最远距离的点连接起来,就可以得到集合 S1 的上包。
------------------------------------------------------------------------------------------------------------------------
扩展欧几里得算法
若 d 能整除 a 且 d 能整除 b,则 d 能整除 a 和 b 的任意线性组合 ax + by
令gcd(a, b)表示自然数 a 和 b 的最大公约数,则gcd(a, b)是a 和b 的最小正线性组合
a 和 b 的最小正线性组合是在ax + by的所有值中,值最小的正整数
想法:扩展欧几里得算法是用辗转相除法求ax + by = gcd(a, b)的整数解。在递归执行过程中,每一次辗转相除后变换 ax + by = gcd(a, b) 中 a 和 b 的值得到等价的线性组合,在达到递归结束条件求得gcb(a, b)的值后再依次返回,求得每一个等价线性组合的整数解,最终求得使ax + by = gcd(a, b)成立的整数解。注意,这个整数解可能是负整数。
if (0 == b) x = 1; y = 0; return a; ExGcd(b, a % b, x, y)
------------------------------------------------------------------------------------------------------------------------
中国剩余定理
令线性同余方程 ax = 1(mod n) 表示 ax 除以 n 的余数等于 1,a 和 n 的线性组合方程 ax' + ny' = gcd(a, n),如果 a 和 n 互质,则有 x = x' mod n
问题:《孙子算经》中的问题(简称孙子问题):有物不知其数,三个一数余二,五个一数余三,七个一数又余二,问该物总数几何?
孙子问题的关键是从 5 和 7 的公倍数中找一个除以 3余 1的数 c1(70),从3 和 7 的公倍数中找一个除以 5 余 1 的数 c2(21),从 3 和 5 的公倍数中找一个除以 7 余 1 的数 c3(15),然后计算
(70 × 2 + 21 × 3 + 15 × 2) % (3 × 5 × 7) = 23
第七章 减治法
减治法(reduce and conquer method)把一个大问题划分为若干个子问题,但是只需求解其中的一个子问题,也无需对子问题的解进行合并。所以,严格地说,减治法应该是一种退化了的分治法
比如:斐波那契数列
虽然斐波那契数列本身更多是用递归来实现,但这里使用带备忘录的递归可以看作是一种减治法,通过缓存已计算的结果来减少重复计算
------------------------------------------------------------------------------------------------------------------------
俄式乘法
(russian multiplication)用来计算两个正整数 n 和 m 的乘积,运算规则:
如果 n 是偶数,计算 n/2×2m;
如果 n 是奇数,计算(n-1)/2×2m+m;
当 n 等于 1 时,返回 m 的值
俄式乘法的优点:
1、算法简洁性:
俄式乘法只包括折半、加倍和相加这几个简单的操作。这种简洁性使得算法易于理解和实现,特别是在硬件层面上,使用移位(机器层次上最基本的操作)就可以完成二进制数的折半和加倍,从而提高了执行效率。
2、高效性:
由于算法简洁,且每次迭代都能将问题规模减半(对于偶数n,直接折半处理;对于奇数n,通过减去1后折半,再额外加上m来处理余数),因此算法的时间复杂度较低,计算速度相对较快。
3、无需乘法表:
俄式乘法不需要预先知道乘法表,这使得它在某些特定环境或应用场景中(如计算机内部的快速乘法计算、教育领域的数学算法教学等)具有独特的优势。
4、易于硬件实现:
如前所述,算法中的折半和加倍操作可以通过移位来实现,这在硬件电路中是非常高效的操作。因此,俄式乘法在硬件实现上具有较高的速度和效率。
5、适用性广泛:
尽管俄式乘法是一种非主流的乘法算法,但它在某些特定领域(如计算机科学、数学教育等)中仍然具有广泛的应用价值。此外,它还可以与其他算法(如快速幂算法)结合使用,以解决更复杂的数学问题。
时间复杂度O(log2n)
------------------------------------------------------------------------------------------------------------------------
折半查找
应用折半查找方法在一个升序序列中查找值为k的元素。若查找成功,返回元素 k 在序列中的位置,若查找失败,返回失败信息。
时间复杂度O(log2n)
------------------------------------------------------------------------------------------------------------------------
选择问题
设无序序列 T=(r1, r2, …, rn),T 的第 k(1 ≤ k ≤ n)小元素定义为 T 按升序排列后在第 k 个位置上的元素。给定一个序列 T 和一个整数 k,寻找 T 的第 k 小元素的问题称为选择问题(choice problem)
想法:选定一个轴值对序列 r1 ~ rn 进行划分,使得比轴值小的元素都位于轴值的左侧,比轴值大的元素都位于轴值的右侧,假定轴值的最终位置是 s,则:
(类似于快速排序O(nlog2n))
(1)若 k=s,则 rs 就是第 k 小元素;
(2)若 k<s,则第 k 小元素一定在序列 r1 ~ rs-1 中;
(3)若 k>s,则第 k 小元素一定在序列 rs+1 ~ rn 中。
时间复杂度:O(n) ###
------------------------------------------------------------------------------------------------------------------------
插入排序
依次将待排序序列中的每一个记录插入到一个已排好序的序列中,直至全部记录都排好序
在插入第 i(i>1)个记录时,前面的 i-1个记录已经排好序
时间复杂度:O(n^2)
------------------------------------------------------------------------------------------------------------------------
堆排序
是利用堆(假设利用大根堆)的特性进行排序的方法,基本思想是:首先将待排序的记录序列构造成一个堆(一个近似完全二叉树的结构,并同时满足堆积的性质,即子节点的键值或索引总是小于(或者大于)它的父节点),此时,堆顶记录是堆中所有记录的最大者,将堆顶记录和堆中最后一个记录交换,然后将剩余记录再调整成堆,这样又找出了次大记录,以此类推,直至堆中只有一个记录。
小根堆:每个结点的值都小于等于其左右孩子结点的完全二叉树
大根堆:每个结点的值都大于等于其左右孩子结点的完全二叉树
较大值的结点靠近根结点,但不绝对。
堆采用顺序存储,则对应一个(无序)序列
------------------------------------------------------------------------------------------------------------------------
淘汰赛冠军问题
假设有 n=2k 个选手进行竞技淘汰赛,最后决出冠军的选手,设计竞技淘汰比赛的过程。
n个人,最终要淘汰n-1个人,每一场比赛淘汰一个人
时间复杂度:O(n)
------------------------------------------------------------------------------------------------------------------------
假币问题
在 n 枚外观相同的硬币中,有一枚是假币,并且已知假币较轻。通过一架天平来任意比较两组硬币,从而得知两组硬币的重量是否相同,或者哪一组更轻一些,假币问题要求设计一个高效的算法来检测出这枚假币。
分成3组,前两组有n/3(向上取整)枚硬币,其余作为第三组。
2. 计算 3 组的硬币个数 num1、num2 和 num3;
3. add1 = 第 1 组硬币的重量和;add2 = 第 2 组硬币的重量和;
4. 根据情况执行下述三种操作之一:
4.1 如果 add1<add2,则在第 1 组硬币中查找;
4.2 如果 add1>add2,则在第 2 组硬币中查找;
4.3 如果 add1=add2,则在第 3 组硬币中查找;
------------------------------------------------------------------------------------------------------------------------
两个序列的中位数
想法:分别求出序列A和B的中位数,记为 a 和 b,有下列三种情况:
(1)a = b:则 a 即为两个序列的中位数;
(2)a < b:则中位数只能出现在 a 和 b 之间,在序列 A 中舍弃 a 之前的元素得到序列 A1,在序列 B 中舍弃 b 之后的元素得到序列 B1;
(3)a > b:则中位数只能出现在 b 和 a 之间,在序列 A 中舍弃 a 之后的元素得到序列 A1,在序列 B 中舍弃 b 之前的元素得到序列 B1;
重复上述过程,直至两个序列都只有一个元素,则较小者即为所求。
------------------------------------------------------------------------------------------------------------------------
topK问题
如:从大批量数据中寻找最大的前 k 个数据,比如从 10 万个数据中,寻找最大的前 1000 个数。
想法:使用优先队列可以很好的解决这个问题。优先队列(priority queue)是按照某种优先级进行排列的队列,通常采用堆来实现。
首先用前 k 个数据构建极小优先队列,然后依次取每一个数据 ai(k<i ≤ n)与队头元素进行比较:
(1)大于队头元素,则将 ai 覆盖队头元素(相当于将队头元素删除);
(2)小于队头元素,则将 ai 丢弃掉。
如此操作,直至所有数据都取完,最后极小优先队列中的 k 个元素就是最大的前 k 个数。
时间复杂度:O(nlog2k)
------------------------------------------------------------------------------------------------------------------------