算法分析与设计 - 作业2

问题一

给定正整数 a,b,求 gcd(a,b)

以下将给出四种解法。

解法一

我会暴力!

考虑枚举 i(1imin(a,b)),检查 i 是否为 a,b 的公约数,即检查 (amodi=0)(bmodi=0) 是否成立,在所有公约数中取最大值即为最大公约数。

空间复杂度 O(1),时间复杂度 O(min(a,b)) 级别。

复制
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";

解法二

我学过高中数学!

考虑更相减损术,有:

gcd(a,b)={a(a=b)gcd(b,a)b>agcd(ab,b)otherwise

上式的数学证明:

  • 对于 b>agcd(a,b)=gcd(b,a) 显然成立。
  • 对于 a>b,有 dZ,d|a,d|b,容易证明 d|ab,将 a,b 均表示为 d 的倍数即证。则 a,b 的所有公因数均为 abb 的公因数,则上式成立。

时间复杂度 O(max(a,b)) 级别,当 a>>b 时可达到最坏情况。

int gcd(int a_, int b_) {
if (a_ == b_) return b_;
if (a_ < b_) return gcd(b_, a_);
return gcd(a_ - b_, b_);
}

解法三

我提前看了课本!

考虑更相减损术的优化,有辗转相除法:

gcd(a,b)={b(a=0)gcd(b,amodb)otherwise

上式的数学证明:

c=gcd(a,b),则 n,mZ,a=nc,b=nc

k=ab,r=amodb,则有:r=ak×b=(nkm)c

要证 gcd(b,amodb)=c,仅需证 b=m×cr=(nkm)c 中,m,nkm 互质,即 gcd(m,nkm)=1。考虑反证法,设 d=gcd(m,nkm)>1

p,qZ,m=pd,nkm=qd,则有 b=pdca=dc(kp+q)。则 dc 也为 a,b 的公约数,且 dc>c,与 c=gcd(a,b) 矛盾,反证 m,nkm 互质,则原式成立。

使用递归实现即可。空间复杂度 O(1),时间复杂度 O(logmax(a,b)) 级别。

时间复杂度证明:

a,b 同阶,即设 a,b 均为长为 n 的二进制整数时,辗转相除法的时间复杂度为 O(n),时间复杂度为 O(logmax(a,b))。考虑递归求最大公约数的过程:

  • a<b,此时有 gcd(a,b)=gcd(b,a)
  • ab,此时 gcd(a,b)=gcd(b,amodb),对 a 取模会让 a 至少折半,则此过程至多发生 O(loga)=O(n) 次。

第一种情况发生后一定会发生第二种情况,因此第一种情况的发生次数一定 不多于 第二种情况的发生次数,则最多递归 O(n) 次就可以得出结果。

而当 a>>b 时,经过一次递归即可转化为 a,b 同阶的情况,同样最多递归 O(n) 次即可得出结果。

int gcd(int a_, int b_) {
return b_ ? gcd(b_, a_ % b_) : a_;
}

解法四

我学过数论!

考虑算数基本定理,考虑 a,b 的标准质因数分解式:

(p1<p2<), a=p1α1p2α2,b=p1β1p2β2

则有:

gcd(a,b)=p1min(α1,β1)p2min(α2,β2)

a,b 进行质因数分解后,求得各质因数幂次的较小值相乘即得最大公约数;也可以仅对其中一方进行质因数分解,每次分解出新的质因数后则在另一方中试除,这样求得各质因数幂次的较小值从而求得最大公约数。

时间复杂度 O(max(a,b)) 级别。若采用了第一种实现方法需要存储各质因数幂次,空间复杂度 O(n) 级别,若采用第二种实现方法不需要则仅需常数级别的空间即可,空间复杂度 O(1) 级别。

该解法涉及到了最大公约数的本质,在解决其他较复杂的有关最大公约数问题时常使用到。

第二种实现方式:

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";

问题二

给定集合 S,求 S 的所有子集。

解法一

我刚上完大一上!

直接写 |S| 重循环,第 i 层循环枚举第 i 个元素是否存在于子集中即可。

时间复杂度 O(2|s|) 级别,但是很难写,而且只能处理 |s| 固定的情况。

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();
}

解法二

我会搜索!

枚举每个元素是否存在于子集中,深度优先搜索即可。

时间复杂度 O(2|s|) 级别,但是不难写。

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;
}

解法三

我会位运算!

考虑用长度为 |s| 的 01 序列表示 s 的一个子集,01 序列的第 i 个位置表示 s 的第 i 个元素是否存在于子集中。

记集合 ti(0in) 代表使用 s 中的前 i 个元素组成的所有子集代表的 01 序列组成的集合,显然 ti 中均为长度为 i 的 01 序列,则有:

ti={(i=0)ti={x+0|xti1}+{x+1|xti1}otherwise

t|s| 代表的所有 01 序列转化为子集形式即为所求。

循环实现即可,时间复杂度 O(2|s|) 级别。

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";
}

写在最后

上述解法本质均为按顺序考虑每个元素是否存在于子集中,从而构造出所有子集。

posted @   Rainycolor  阅读(20)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示