程序设计实习2023复习
程序设计实习2023复习
这个东西也许和 OI 有关?反正也要复习,干脆写篇博客。
introduction
传统算法设计一般考虑有效算法(多项式时间),以及追求“精确解”,尤其是“经典”算法或者数据结构。这节课一般考虑那些不那么传统的、更加偏向现代的算法设计。
一种就是近似算法,比如探索 NP-hard 问题在多项式时间内可以近似到多么精确,或者是本来存在多项式时间算法的问题,能否使用近似解改良复杂度。
对于最小化问题,一般使用近似比来衡量一个解的质量,如果对于某个输入 ,你得到的解为 而最优解为 ,那么我们可以通过 来衡量你给出的解的质量,这个值的取值范围显然是 ,越小说明质量越好。
称该算法是 近似的,当对任意 ,都有 。
例如 TSP 问题(旅行商问题,就是经过所有点并且回到出发地的最短长度),找到最小生成树上的最优解就是一个 近似算法;找平面点集直径随便选择一个点然后找到离这个点最远的也是一个 近似算法。
另一种意义上的近似是算法本身具有随机性,对于确定的输入得到随机的输出,比如有一定概率出错,但是不出错时有很好的性能保证。
近似 + 随机是现代算法设计的大趋势。
random
随机数
要在程序中生成随机数,可以考虑 C++ 中的 random
库,预设的几个标准随机数生成器如基于线性同余的 minstd_rand0
、 minstd_rand
和 knuth_b
,基于 Mersenne Twister 的 mt19937
,还有基于 Lagged Fibonacci 的 ranlux24
和 ranlux48
,一般使用 minstd_rand
就行了。
然后还有从很多不同的特殊分布中采样,例如均匀独立采样 uniform_int_distribution
和 uniform_real_distribution
分别是在一个区间里面均匀采整数或者实数,其他例如两点分布的 bernoulli_distribution
以 的概率取 其余取 ,还有二项分布 binomial_distribution
,另外一个较常用到的是正态分布(高斯分布) normal_distribution
。
例如下面程序可以生成 个从标准正态分布 中的采样。
#include<random>
using namespace std;
int main(){
std::minstd_rand gen(114514);
std::normal_distribution<double> dis(0.0,1.0);
for(int i=1;i<=10;++i)
printf("%.5lf\n",dis(gen));
return 0;
}
而更加一般的离散分布采样就是 discrete_distribution
,例如下图:
#include<random>
using namespace std;
int main(){
std::minstd_rand gen(114514);
std::discrete_distribution<> dis({1,4,6,4,1});
for(int i=1;i<=10;++i)
printf("%d\n",dis(gen));
return 0;
}
这里传入的参数是一个长度为 的数组 ,下标从 到 ,其中 数字生成的概率为 。
随机算法
考虑一个随机算法,如果有 的出错概率,也就是说正确概率为 ,如果算法只需要运行一次 取足够小的常数就行了,如果要多次运行且每次成功,可能就需要让 与运行次数相关。如果运行 次且每次失败概率为 ,根据 Union Bound ,那么存在一次失败概率至多 。
算法具体分析的时候也许需要用到各种理论推导得出需要运行的次数,但是实际上只要试试够用就行了。
一些典型随机算法
测试矩阵相乘结果是否正确
给定三个 的矩阵 ,问是否有 。
考虑生成一个长度为 的随机向量 ,每个值从集合 中等概率选取,如果 那么必须有 ,如果 ,那么考虑 和 中至少有一列不完全相同,不妨假设是第 列,先确定 其他位置的值,然后考虑 位置的值,容易发现 和 中至少有一个会导致 ,所以可以看出此时 的概率不低于 。
重复随机 次,那么容易发现出错概率不超过 ,更一般地,如果有一次出错概率 ,那么 次都出错的概率就是 ,要有 的出错概率或者说 的成功率就只需要有 即可。
取常数的话这个算法便可以做到 判断。
Median Trick
现在有⼀个⿊盒能以 的概率正确回答某个 Yes/No 问题的答案,能否将其强化成任意的 ?
同样的,重复随机 次,取出现次数较多的作为答案,要选多大才行?
Chernoff Bound:设独立随机变量 ,令 ,则对 有 。
随机 次回答正确次数期望时 ,所以也就要使得 ,视 为常数容易发现 取到 即可。
Hoeffding inequality:设独立随机变量 ,令 ,则对任意 有 。
recursion(递归)
这里是介绍经典算法,没啥好说的()
hash
哈希算法
是随机哈希,指对于每个 ,有 是 上的均匀随机分布。一般要求是对于 ,有 ,其中每个 称为 bucket。
考虑分布式环境的负载均衡问题,有 个请求要丢给 台服务器处理,如何使得每台服务器处理的请求个数尽量平均?可以使用上面讲到的随机哈希。
那么如果需要新增服务器呢?重构哈希函数太麻烦。
可以使用一致性哈希算法:构造一个足够大的哈希函数 ,然后把请求和服务器都哈希一下到一个环上,然后每个请求丢给这个环上对应后面第一个服务器做,只需要一个支持增删和查询后继操作的有序表即可。可以证明大概率负载均衡。
Count-min Sketch
考虑这样一个问题:输入 个 上的整数,输入结束后给定 ,要求近似回答 出现的次数。
Count-min Sketch 是一个数据结构,支持对于任意的误差参数 ,满足插入 个 上的元素后给定 查询 共出现多少次,设 表示估计量, 表示真实量,可以以大概率( 的概率)满足 。
空间使用 ,且单次查询和插入单个元素的时间为 。
大体思路就是设置一个随即哈希函数 ,然后对每一个 统计 为满足 的数量。容易发现如果不存在哈希冲突最后 就是 出现次数,如果出现冲突那么 只会更大。
设 真实出现次数为 ,那么有:
Markov inequality:若 ,有 。
对 用 Markov inequality,有 ,这里令 取 即可得到 的概率满足条件。
由于 总是高估,可以多次实验取最小值,每次至少 概率满足条件,所以独立运行 次即可以 的概率满足条件。
所以整体思路就是设置 个独立随机哈希 ,其中 ,并初始化 个 元 counter,然后插入删除元素就在每个 counter 里面都做就行了,查询就返回所有 counter 对应位置的最小值。
并且可以发现由于我们仅仅只是统计了元素数量,所以 count-min sketch 是可以接受相加相减的。
Count-min Sketch 应用
近似 k-HH
考虑 k-Heavy hitter(k-HH) 问题:给定长度为 的在 上的数组,求出现次数前 多的元素。这个问题的应用例如用来查找那些异常流量或者访问较多的页面。
近似 k-HH 问题:你需要找到所有出现至少 次的元素,并且你还可以返回其他元素,返回的其他元素出现不少于 次。
基于 count-min 的方法做数据流上的近似 k-HH,直接使用 count-min sketch 来存每个元素出现次数就行了,每次新加一个元素就查一下出现次数看是否满足条件即可,你找到的所有元素数量大概率是不超过 的,所以直接用表记录这些元素即可,动态增加 也没有任何问题。
近似 Inner Product
考虑近似 Inner Product 问题:给定两个在 上的 frequency vector 和 ,估计 。frequency vector 就是一个 为向量,统计每个 出现多少次。
直接维护对应 和 的 count-min sketch,然后对于每个 直接求对应 bucket 的 inner product,得到 ,直接令 作为最后估计。最后有保证大概率 。
实现哈希函数(Universal Hashing)
要构造 使得 。
不妨 为素数,然后令 ,取 个 的均匀独立随机数 ,给定 ,把 表成 进制数,定义 ,容易证明满足条件。
当然这样不能保证 与 完全独立,但是做 count-min 足够了。
Bloom Filter
维护一个集合 ,全集为 ,支持插入操作和询问 是否在 内。
Bloom filter 以 空间(乘以极小的常数),和 时间,大概率 成功回答询问。
简单来说就是维护一个长度 的 0-1 数组 ,用 个随机哈希函数 ,插入 时把 都置为 ,然后认为 在集合内当且仅当对于任意的 都有 。
如果不插入 ,对于每个 ,考察 的概率。当插入某个 时, 个哈希函数都有可能将其置为 ,所以仍然为 的概率就是 ,最多插入 个,所以最终 的概率不超过 。于是错误回答 Yes 的概率至多为 , 固定求导可以得到 时概率最小,令失败概率为 得到 。
如果要支持删除就不能仅仅维护 0-1 变量而要维护出现次数。
Karp-Rabin Hash
就是普通的字符串哈希。
分析略。
distance
集合相似度度量
两个集合 ,至少有一个非空集,定义两个集合的 Jaccard Similarity 为 。
如果数据有很多集合,要快速查询两个集合间的相似度,有什么好做法?
MinHash
考虑构造一个随机哈希函数 ,注意这里映射到实数,然后对于两个集合 ,显然有 。
所以完整做法就是采用 个独立随机哈希 ,然后对于每个 和 ,计算 。查询 。
分析略,对于确定误差范围,有 时失败概率 。
向量相似度度量
考虑两个向量 ,定义 cosine similarity 为 。
SimHash
类似 MinHash,尝试找到一个 有 和 相关。
生成一个 维随机高斯向量 (每一维独立从标准正态分布中取然后归一化,等价于在 为单位球面取一个均匀随机点),定义 ( 是符号函数,这里不妨令 ),那么有 ,其中 为两个向量的夹角。
同样的,取 个独立的 SimHash 取平均值,也就是认为 ,令 ,可以看作是将 上的点对应到 维的 Hamming 距离,也就是一个二进制串,两者距离定义为不同的位置数量,即可以认为有 。
Hamming空间近似最近邻查询
梦回 NOI2021 day2t1()
有 个 维 Hamming 空间 上的点,误差参数 ,预处理时间 ,给定任何 要求在 时间内找到 近似最近邻查询。
预处理过程,设 ,找到 个 个维度的随机排列 ,然后对每个排列 ,将所有坐标按照 排列后的 个数据点按字典序排序。
查询过程,假如查询串是 ,对每个排列 ,将 坐标按照 排列后找到它前后字典序相邻最近的串,然后看这两个距离更新答案。
分析略。
Locality Snesitive Hashing (LSH)
称一个哈希函数 是一种 LSH,若 可以反映 和 的一种相似度。容易发现前面所说的 MinHash 和 SimHash 都是 LSH 的一种。
一般 LSH 都可以映射到 Hamming 空间做。具体做法就是考虑一个映射到 的随机哈希 ,然后将 和 复合。我们知道如果 则 否则 ,所以有 。于是用 组 和 的复合即可映射到 Hamming 空间了。
euclidean(欧式距离)
欧氏距离,也即 范数,最常用的一个距离:
格点离散化
最简单最直接的近似思想,把整个空间划分成 的小正方形,然后把所有数据点 round 到它所在格子的正中心,容易发现每个点最多移动 ,并且在一个 的区域内最多有 个正方形。
另外欧式空间还有一个比较重要的性质就是一个半径 的 维球中最多可以存在 个两两距离 的点,这个用体积的思想很好说明。
近似欧式点集直径:先找到一个直径的 近似值 ,然后做 的网格,在新的点集的暴力求直径,总的复杂度为 ,于是可以在线性时间内求出 近似直径。
类似算法可以用来求近似最小包围球。
四分树
四分树是二维情况下的特殊称法,这里以二维为例。
构建方法很简单,就从最大的 正方形开始,每次正中间划开两刀分成四个部分,一直递归下去构造。构建的时空复杂度可以为 或者 。
四分树可以用来做 近似最近邻查询,问题的准确描述是给定点集 ,对于任意的 你要回答出 使得 。
算法很简单,就是在四分树上每个节点维护一个代表点,每次到一个节点就看一下代表点到 的最短距离,尝试更新答案,接着往下搜索,如果再往下的节点无法让答案更优就剪掉,可以证明查询时间复杂度是 。
点对离散化:WSPD
输入点集 ,设 ,考虑所有点对距离有 对,如果两个点集 和 ,它们间的最短距离要比内部距离大太多,那么我们就可以把 上的距离等同于同一个值。
对于一个点集 ,一个 WSPD 是一系列点集对 ,满足 ,并且 。
基于四分树构造 WSPD 十分简单,就是直接递归构造两个点集 与 之间的 WSPD,其中 和 对应的都是四分树上的节点,如果 可以直接放入 WSPD 就直接放,否则就把直径较大的点集拆分成它的子节点,然后递归构造即可。可以证明采取压缩四分树得到的 WSPD 共有 个。
使用 WSPD 可以精确求最近点对,构造 的 WSPD,然后可能成为最近点对的 显然只有可能是那些单点对着单点的点对。
另外 WSPD 好像也可以用来做之前第五届图灵杯的 T3,到时候再补吧。
WSPD 也可以近似直径,构造 WSPD,然后对于每个点集对,任意取左边一个点和右边一个点看看距离更新答案,就可以得到直径的 近似。
WSPD 还可以用来求 Spanner。 Spanner 是数据集 的一个欧式子图,边集是 的子集并且边权是两端点的欧氏距离,并且满足 有 。做法也很简单,就是求 WSPD,然后每个点集对任意找代表点连边,可以证明满足条件。
在构造得到的 spanner 上求 MST 即可得到 上的一个 近似的 MST。
Tree Embedding
Tree embedding 是将数据集 从 映射到一棵树 上,其中 ,目的是用树上的距离来近似欧式距离,首先要求 ,性能衡量为 Distortion(),越小越好。
随机平移构造一个四分树,然后只有父亲孩子连边,其中第 层边权重为 ,结论是 ,其中 是四分树的高度。
虽然这个近似比大,但是每对点期望距离都能保持,并且构造的是一棵树,而且也没有 。
dim reduction - JL
简要介绍
在做各类问题的时候,可能会遇到维数爆炸(Curse of Dimensionality)的问题,就是维数大一点可能就会导致算法运行效率等急速增加,所以我们希望将维数降下来。
Johnson-Lindenstrauss Transform 是一种针对距离的有效降维方法,可以维持 个 上的数据点集 中任意两点的距离。
具体来说,给定误差 ,JL 给定了一个映射 ,这里 ,使得 ,并且可以在 的时间计算 。
可以发现非常神奇的一点就是降到的维度和 无关,而只和 与 有关,这说明对于近似算法来说,“高维”就是 维。
构造方法
把 JL 限制放大,令 。
对于一对点 ,考虑 ,设计一个线性随机哈希(随机映射),使得 ,然后多次实验,对于 对距离应用该结论,保证所有距离都成立。
具体而言,构造 个独立的向量 ,其中 的每一维都是从标准正态分布 中采样,然后令 (注意这里是直接拼接)。
固定 ,首先考虑对于某个 ,有 ,而 ,而我们知道 ,所以有 。
于是有 ,起到了多次实验取平均的效果。
JL 的核心结论是,。
然后对 对点对距离使用 Union Bound,一对失败概率 , 失败概率不超过 ,令该值小于 为常数,则有 。
当然实际中没人管 具体要多大,一般是试试哪个好用能用就行。
更简单的 JL 是将随机向量 替换成每维是独立 均匀随机数的向量,因为这个随机也满足期望为 ,方差为 。
应用:Linear regression
最小二乘法,给定矩阵 和向量 ,则要求向量 使得 尽量小,相当于是要解方程 。当 可逆时有唯一解 。
方便起见不妨假设 可逆,那么直接做的复杂度为 。优化复杂度考虑降维,这里的 和 都是 维向量,我们优化的目标是这两个向量的距离,想到构造一个 JL 矩阵 ,使得 。这样可以让 复杂度降为 。
但是考虑到我们相当于是要对所有向量使得它们有距离保证,不能再用 Union Bound 来分析 取多大,不过我们有子空间版本的 JL,设 是 的一个 维子空间,则存在 的 JL 矩阵 满足 。如果 都认为是常数,那么就可以认为 ,这样带入求 的复杂度就可以降为 ,但是如果得到的 JL 十分稠密还是要求 是 的,没有改进。
(其实这里不太严谨,因为我们要最小化的是 和 的距离,而所有可能的 不能说是一个 维子空间,而应该是一个 维子空间整体往一个方向平移了部分,不过不影响结论成立)
使用一个随机 的矩阵,这个矩阵每列只有一个均匀随机位置非 ,且该元素均匀随机取 或 ,然后用 替代原来的 JL 矩阵,可以证明 时有类似上面子空间版本的 JL 矩阵保证。
这个矩阵是十分稀疏的,以至于我们可以以 的优秀复杂度计算 ,其中 表示 的非 元素数量(number of non-zero)。
dim Reduction - PCA
简要介绍
上面提到的 JL 降维方法容易发现是和数据无关的,这是好事但是也说明降维过程并没有用到数据本身的隐藏的依赖关系,这里要讲的 PCA 降维是通过数据间的隐含关系实现的。
PCA 的大体目标是给定 个 维数据点 ,你要找到 个 维“基” ,使得每个数据点可以近似用这些基表示,或者说分布在这个基张成的子空间附近。
更加具体地说,目的是找到 个单位正交(orthonormal)的向量 ,最大化 。最优的这 个向量叫做 的前 个主成分(principal components,其实 PCA 全称就是 principal component analysis 主成分分析)。
PCA 求解
先考虑 的情况,即找到单位向量 ,并最大化 ,把 拍成一列得到一个矩阵 ,则就是要最大化 ,令 ,则目标等价于最大化 。
容易发现 是实对称矩阵,并且 还是半正定矩阵,所以 可以正交对角化成 ,且 。存在正交矩阵 ,有 ,于是最大化 ,而 显然最优,所以 是就是取 的第一列即可。
事实上,前 个主成分就是 的前 列。
求 的所有特征向量,精确求解就是 的。
近似求解
近似求解可以做到线性时间 。考虑到 本质上就是把单位球拉伸成椭球,所以任取一个向量 ,多次应用 之后那么 的方向就会十分贴近最大的主成分的方向,所以算法就是随便选一个方向向量 ,然后令 等到 即可。
结论是若 是最大的主成分,那么对于任意的 ,随机 每一维独立取 中的一个采样然后归一化,有常数概率使得 (这里 是满足 的最小的 )。
如果要找第二大的主成分就先找最大的,然后数据点 ,将该分量消除,然后再找最大的即可。前 大同理。
也可以把 取成每一维均匀 然后归一化,同样有类似保证。
linear programming(线性规划)
压缩感知简介
稀疏数据,非 元素较少的数据在现实中很常见,前面说的利用稀疏性都是拿到原始数据然后考虑降维,不过我们可以考虑不把所有数据读进来,而仅读需要的部分,也就是这里提到的压缩感知(compressive sensing)算法。
具体来说就是设计 个线性测量(linear measurements),有一个未知信号 ,给定线性测量上的值 ,其中 ,要从 恢复 。或者说构造矩阵 ,从 恢复出 。
事实上,求解 本质上相当于求 的解,如果 且 可逆就可以精确解出 。一般来说我们都是考虑 的情况,解不唯一,还需要利用 的稀疏性来找到 。
压缩感知定理是设 ,其中 且 的每个元素从 中独立采样,则 有高概率同时对所有 维 稀疏的 从 中精确恢复出 。 稀疏的定义是至多有 个非零位置,并且一般假定 。
当 中最大的 个元素绝对值和“远大于”其它元素绝对值和可以近似恢复出 。
具体如何恢复呢?当 时 的 是不唯一的,想到利用稀疏性我们便希望找到一个 满足非零坐标尽可能少,非零坐标数量也叫做 的 范数,所以这个优化问题也叫做 最小化问题。
但是 最小化问题是 NP-hard 的,考虑用 范数替换,所以我们最终的问题就是给定 ,找到 最小的 满足 。
线性规划
给定 个变量 ,和若干个线性约束 ,最小化目标函数 。
单纯性法不会,寄了()
streaming(数据流算法)
不会,反正不考()
MPC(大数据并行计算模型)
不会,反正不考()
final(期末总结安排)
直接开摆!!!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人