算法分析与设计 - 作业2
问题一
给定正整数 ,求 。
以下将给出四种解法。
解法一
我会暴力!
考虑枚举 ,检查 是否为 的公约数,即检查 是否成立,在所有公约数中取最大值即为最大公约数。
空间复杂度 ,时间复杂度 级别。
复制int g = 0; for (int i = 1; i <= std::min(a, b); ++ i) { if (a % i == 0 && b % i == 0) g = i; } std::cout << g << "\n";
解法二
我学过高中数学!
考虑更相减损术,有:
上式的数学证明:
- 对于 , 显然成立。
- 对于 ,有 ,容易证明 ,将 均表示为 的倍数即证。则 的所有公因数均为 与 的公因数,则上式成立。
时间复杂度 级别,当 时可达到最坏情况。
int gcd(int a_, int b_) { if (a_ == b_) return b_; if (a_ < b_) return gcd(b_, a_); return gcd(a_ - b_, b_); }
解法三
我提前看了课本!
考虑更相减损术的优化,有辗转相除法:
上式的数学证明:
设 ,则 。
设 ,则有:。
要证 ,仅需证 , 中, 互质,即 。考虑反证法,设 。
设 ,则有 ,。则 也为 的公约数,且 ,与 矛盾,反证 互质,则原式成立。
使用递归实现即可。空间复杂度 ,时间复杂度 级别。
时间复杂度证明:
当 同阶,即设 均为长为 的二进制整数时,辗转相除法的时间复杂度为 ,时间复杂度为 。考虑递归求最大公约数的过程:
- ,此时有 ;
- ,此时 ,对 取模会让 至少折半,则此过程至多发生 次。
第一种情况发生后一定会发生第二种情况,因此第一种情况的发生次数一定 不多于 第二种情况的发生次数,则最多递归 次就可以得出结果。
而当 时,经过一次递归即可转化为 同阶的情况,同样最多递归 次即可得出结果。
int gcd(int a_, int b_) { return b_ ? gcd(b_, a_ % b_) : a_; }
解法四
我学过数论!
考虑算数基本定理,考虑 的标准质因数分解式:
则有:
将 进行质因数分解后,求得各质因数幂次的较小值相乘即得最大公约数;也可以仅对其中一方进行质因数分解,每次分解出新的质因数后则在另一方中试除,这样求得各质因数幂次的较小值从而求得最大公约数。
时间复杂度 级别。若采用了第一种实现方法需要存储各质因数幂次,空间复杂度 级别,若采用第二种实现方法不需要则仅需常数级别的空间即可,空间复杂度 级别。
该解法涉及到了最大公约数的本质,在解决其他较复杂的有关最大公约数问题时常使用到。
第二种实现方式:
int g = 1; if (a < b) std::swap(a, b); for (int i = 2; i * i <= b; ++ i) { if (b % i != 0) continue; int pb = 1, pa = 1; while (b % i == 0) b /= i, pb *= i; while (a % i == 0) a /= i, pa *= i; g *= std::min(pa, pb); } if (b != 1) { if (a % b == 0) g *= b; } std::cout << g << "\n";
问题二
给定集合 ,求 的所有子集。
解法一
我刚上完大一上!
直接写 重循环,第 层循环枚举第 个元素是否存在于子集中即可。
时间复杂度 级别,但是很难写,而且只能处理 固定的情况。
int n; std::cin >> n; std::vector <int> s; for (int i = 1; i <= n; ++ i) { int x; std::cin >> x; s.push_back(x); } std::vector <int> t; for (int i0 = 0; i0 <= 1; ++ i0) { if (i0 == 1) t.push_back(s[0]); for (int i1 = 0; i1 <= 1; ++ i1) { if (i1 == 1) t.push_back(s[1]); for (int i2 = 0; i2 <= 1; ++ i2) { //... } if (i1 == 1) t.pop_back(); } if (i0 == 1) t.pop_back(); }
解法二
我会搜索!
枚举每个元素是否存在于子集中,深度优先搜索即可。
时间复杂度 级别,但是不难写。
void DFS(int pos_, int lim_, std::vector <int> &s_, std::vector <int> &t_) { if (pos_ >= lim_) { for (auto x: t_) std::cout << x << " "; std::cout << "\n"; return ; } DFS(pos_ + 1, lim_, s_, t_); t_.push_back(s_[pos_]); DFS(pos_ + 1, lim_, s_, t_); t_.pop_back(); } int main() { int n; std::cin >> n; std::vector <int> s, t; for (int i = 1; i <= n; ++ i) { int x; std::cin >> x; s.push_back(x); } DFS(0, n, s, t); return 0; }
解法三
我会位运算!
考虑用长度为 的 01 序列表示 的一个子集,01 序列的第 个位置表示 的第 个元素是否存在于子集中。
记集合 代表使用 中的前 个元素组成的所有子集代表的 01 序列组成的集合,显然 中均为长度为 的 01 序列,则有:
将 代表的所有 01 序列转化为子集形式即为所求。
循环实现即可,时间复杂度 级别。
int n; std::cin >> n; std::vector <int> s; for (int i = 1; i <= n; ++ i) { int x; std::cin >> x; s.push_back(x); } std::vector <std::string> t, temp; t.push_back(""); for (int i = 0; i < n; ++ i) { temp.clear(); for (auto x: t) { temp.push_back(x + "0"); temp.push_back(x + "1"); } std::swap(t, temp); } for (auto x: t) { for (int i = 0; i < n; ++ i) { if (x[i] == '1') std::cout << s[i] << " "; } std::cout << "\n"; }
写在最后
上述解法本质均为按顺序考虑每个元素是否存在于子集中,从而构造出所有子集。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现