目录
设计一个计算的算法,n是任意正整数。除了赋值和比较运算,该算法只能用到基本的四则运算操作。
设计一个算法,在已经排序的两个列表中,找出所有相同的元素。例如,列表2,5,5,5和2,2,3,5,5,7,应该输出2,5,5。如果给定的两个列表的长度分别为m和n,你设计的算法的最大比较次数是多少?
[a. 用欧几里得算法求 gcd(31415,14142)。](#a. 用欧几里得算法求 gcd(31415,14142)。)
[b. 用欧几里得算法求 gcd(31415,14142), 速度是检查 min{m,n}和 gcd(m,n)间连续整数的算法的多少倍?请估算一下。](#b. 用欧几里得算法求 gcd(31415,14142), 速度是检查 min{m,n}和 gcd(m,n)间连续整数的算法的多少倍?请估算一下。)
[证明等式 gcd(m,n)= gcd(n,m mod n)对每一对正整数(m,n)都成立。](#证明等式 gcd(m,n)= gcd(n,m mod n)对每一对正整数(m,n)都成立。)
[接下来正式证明 gcd (m,n) = gcd (n, m mod n):](#接下来正式证明 gcd (m,n) = gcd (n, m mod n):)
[1)若 d 是 m 和 n 的公约数,则 d 一定是 n 和 r 的公约数](#1)若 d 是 m 和 n 的公约数,则 d 一定是 n 和 r 的公约数)
[2)若 d 是 n 和 r 的公约数,则 d 一定是 m 和 n 的公约数](#2)若 d 是 n 和 r 的公约数,则 d 一定是 m 和 n 的公约数)
对于第一个数小于第二个数的一对数字,欧几里得算法将会如何处理?该算法在处理这种输入的过程中,上述情况最多会发生几次?
a.对于所有m≥1,n≤10的输入,欧几里得算法最少要做几次除法?
b.对于所有m≥1,n≤10的输入,欧几里得算法最多要做几次除法?
在欧几里得的书里,欧几里得算法用的不是整数除法,而是减法。请用伪代码描述这个版本的欧几里得算法。
[扩展欧几里得算法 不仅能够求出两个正整数m和n的最大公约数d,还能求出两个整数x和y(不一定为正),使得 mx+ny=d。](#扩展欧几里得算法 不仅能够求出两个正整数m和n的最大公约数d,还能求出两个整数x和y(不一定为正),使得 mx+ny=d。)
a.在参考资料中查阅扩展欧几里得算法的描述(参见[KnuI]),然后任选一种语言实现它。
[b.改写上述程序以对丢番图方程 ax+by=c求解,系数a,b,c为任意整数。](#b.改写上述程序以对丢番图方程 ax+by=c求解,系数a,b,c为任意整数。)
[带锁的门 在走廊上有n个带锁的门,从1到n依次编号。最初所有的门都是关着的。我们从门前经过n次,每一次都从1号门开始。在第i次经过时(i=1,2,...,n)我们改变 i的整数倍号锁的状态:如果门是关的,就打开它;如果门是打开的,就关上它。在最后一次经过后,哪些门是打开的,哪些门是关上的?有多少打开的门?](#带锁的门 在走廊上有n个带锁的门,从1到n依次编号。最初所有的门都是关着的。我们从门前经过n次,每一次都从1号门开始。在第i次经过时(i=1,2,…,n)我们改变 i的整数倍号锁的状态:如果门是关的,就打开它;如果门是打开的,就关上它。在最后一次经过后,哪些门是打开的,哪些门是关上的?有多少打开的门?)
设计一个计算
的算法,n是任意正整数。除了赋值和比较运算,该算法只能用到基本的四则运算操作。
答:
蛮力法:利用 "整数平方根的单调性",暴力遍历找到最大的i满足i2≤n
伪代码:
// 输入:正整数n
// 输出:floor(√n)
Algorithm BruteForceSqrt(n):
if n == 1: // 特殊值处理
return 1
i ← 0
// 循环条件:(i+1)² ≤n 时,继续尝试更大的i
while (i + 1) * (i + 1) ≤ n:
i ← i + 1
return i
C:
int BruteForceSqrt(int n){
if(n==0||n==1)
return n;
int i =0;
for(;(i+1)*(i+1)<n;i++){
}
return i;
}
二分:
// 输入:正整数n
// 输出:floor(√n)
Algorithm BinarySearchSqrt(n):
if n == 0 or n == 1: // 特殊值:√0=0,√1=1
return n
left ← 0
right ← n
res ← 0 // 保存最终结果
while left ≤ right:
mid ← (left + right) // 2 // 整数除法(四则运算)
mid_square ← mid * mid
if mid_square == n: // 恰好是完全平方数
return mid
elif mid_square < n: // mid可能是候选,尝试更大的值
res ← mid // 记录当前符合条件的最大mid
left ← mid + 1
else: // mid² >n,尝试更小的值
right ← mid - 1
return res
C:
int BinarySearchSqrt(int n){
if(n==0||n==1)
return n;
int left = 0;
int right = n;
int res = 0;
while(left<=right){
int mid = (left+right)/2;
if(mid*mid == n){
return mid;
}else if(mid*mid<n){
res = mid;
left = mid+1;
}else{
right=mid-1;
}
}
return res;
}
设计一个算法,在已经排序的两个列表中,找出所有相同的元素。例如,列表2,5,5,5和2,2,3,5,5,7,应该输出2,5,5。如果给定的两个列表的长度分别为m和n,你设计的算法的最大比较次数是多少?
答
利用 "列表已排序" 的特性,用两个指针分别遍历两个列表,通过比较指针指向的元素大小,移动指针:
- 若 A [i] < B [j]:A [i] 更小,不可能在 B 中出现,i 右移;
- 若 A [i] > B [j]:B [j] 更小,不可能在 A 中出现,j 右移;
- 若 A [i] = B [j]:找到相同元素,加入结果,i 和 j 都右移(处理重复)。
最坏的比较次数为m+n,即没有重复的元素。
// 输入:已排序的列表A(长度m)、已排序的列表B(长度n)
// 输出:两个列表中相同的元素组成的列表
Algorithm FindCommonElements(A, B):
i ← 0 // 遍历A的指针
j ← 0 // 遍历B的指针
result ← [] // 存储相同元素
m ← length(A)
n ← length(B)
// 循环条件:两个指针都未越界
while i < m and j < n:
if A[i] < B[j]:
i ← i + 1 // A[i]更小,跳过
elif A[i] > B[j]:
j ← j + 1 // B[j]更小,跳过
else:
// 找到相同元素,加入结果
result.append(A[i])
i ← i + 1
j ← j + 1
return result
C++:
vector<int> FindCommonElements(vector<int>&A,vector<int>&B){
int ai=0;int bi=0;
int k=0;
int an = A.size();int bn = B.size();
vector<int>res(max(an,bn));
for(;ai<an&&bi<bn;){
if(A[ai]==B[bi])
{
res[k++]=A[ai];
ai++;bi++;
}
else if(A[ai]>B[bi]){
bi++;
}else{
ai++;
}
}
return res;
}
a. 用欧几里得算法求 gcd(31415,14142)。
代码:
int gcd(int n,int m){
if(n%m==0)
return m;
else return gcd(m,n%m);
}
手算过程:
-
初始值:
m=31415,n=14142计算31415 mod 14142:14142 × 2 = 28284,31415 - 28284 = 3131→gcd(31415,14142) = gcd(14142, 3131) -
下一步:
m=14142,n=3131计算14142 mod 3131:3131 × 4 = 12524,14142 - 12524 = 1618→gcd(14142, 3131) = gcd(3131, 1618) -
下一步:
m=3131,n=1618计算3131 mod 1618:3131 - 1618 = 1513→gcd(3131, 1618) = gcd(1618, 1513) -
下一步:
m=1618,n=1513计算1618 mod 1513:1618 - 1513 = 105→gcd(1618, 1513) = gcd(1513, 105) -
下一步:
m=1513,n=105计算1513 mod 105:105 × 14 = 1470,1513 - 1470 = 43→gcd(1513, 105) = gcd(105, 43) -
下一步:
m=105,n=43计算105 mod 43:43 × 2 = 86,105 - 86 = 19→gcd(105, 43) = gcd(43, 19) -
下一步:
m=43,n=19计算43 mod 19:19 × 2 = 38,43 - 38 = 5→gcd(43, 19) = gcd(19, 5) -
下一步:
m=19,n=5计算19 mod 5:5 × 3 = 15,19 - 15 = 4→gcd(19, 5) = gcd(5, 4) -
下一步:
m=5,n=4计算5 mod 4 = 1→gcd(5, 4) = gcd(4, 1) -
下一步:
m=4,n=1计算4 mod 1 = 0→ 余数为 0,此时非零数1即为最大公约数。
b. 用欧几里得算法求 gcd(31415,14142), 速度是检查 min{m,n}和 gcd(m,n)间连续整数的算法的多少倍?请估算一下。
答:
连续整数检查算法的逻辑是:
- 取
k = min(m, n)(此处min(31415,14142)=14142); - 从
k开始向下遍历,依次检查k是否能同时整除m和n,第一个满足条件的k即为gcd; - 本题中
gcd=1,因此需要遍历14142 次(从 14142 到 1),每次遍历需执行 2 次除法取余判断。
而a题中总共计算了10次(英文版的答案这里给出的是11次,因为会最后再算一遍gcd(1,0),本质上是代码实现不同 ),所以倍数 ≈ 14142 / 11 = 1414.2 倍
如果每次都进行两次除法,一次n一次m,那倍数≈ 2*14142 / 11 = 2828.4 倍
证明等式 gcd(m,n)= gcd(n,m mod n)对每一对正整数(m,n)都成立。
结合律:如果整数 d 同时整除 u 和 v,那么 d 也一定整除 u+v 和 u−v。
根据整除的定义:如果 d | u 且 d | v,那么存在整数 s、t,使得u = s・d, v = t・d。
于是u ± v = s・d ± t・d = (s ± t)・d这说明 d 也整除 u ± v。
另外再证明:如果 d 整除 u,那么 d 也整除 u 的任意整数倍 k・u。
因为 d | u,所以存在整数 s,使得 u = s・d。于是k・u = k・(s・d) = (k・s)・d即 d 整除 k・u。
接下来正式证明 gcd (m,n) = gcd (n, m mod n):
r = m mod n根据取模的定义,r 可以写成:r = m − q・n其中 q 是某个整数(商)。
我们要证明:(m, n) 和 (n, r) 拥有完全相同的公约数集合。
1)若 d 是 m 和 n 的公约数,则 d 一定是 n 和 r 的公约数
设 d | m 且 d | n。
由前面的引理:
- d | n
- d | q・n(d 整除 n 的倍数)
- d | m − q・n(d 整除 m 和 qn 的差)
而 r = m − q・n,所以d | r
因此 d 是 n 和 r 的公约数。
2)若 d 是 n 和 r 的公约数,则 d 一定是 m 和 n 的公约数
设 d | n 且 d | r。
因为m = r + q・n
由引理:
- d | n ⇒ d | q·n
- d | r 且 d | q・n ⇒ d | r + q・n
即d | m
因此 d 是 m 和 n 的公约数。
由(1)和(2)可知:m 和 n 的所有公约数 = n 和 r 的所有公约数
既然公约数完全一样,它们的最大公约数自然也相等:
gcd(m, n) = gcd(n, m mod n)
对于第一个数小于第二个数的一对数字,欧几里得算法将会如何处理?该算法在处理这种输入的过程中,上述情况最多会发生几次?
答:
我们拿前面(5,10)来举例子,也就是m=5,n=10;
第一轮:r =5%10=5; m=n=10,n=r=5;此时便已经调换完毕,后续的每一步都不会再有m>n的情况
因此上述情况最多发生一次
a.对于所有m≥1,n≤10的输入,欧几里得算法最少要做几次除法?
答:最少的情况则是m>n>1时且m是n的倍数,此时仅做一次除法
b.对于所有m≥1,n≤10的输入,欧几里得算法最多要做几次除法?
答:我们直接蛮力搜索一遍,得到当m=5,n=8的时候,最多要做5次除法
在欧几里得的书里,欧几里得算法用的不是整数除法,而是减法。请用伪代码描述这个版本的欧几里得算法。
Algorithm EuclideanAlgorithm_Subtraction_Opt(m, n):
while m ≠ 0 and n ≠ 0:
if m > n:
m = m - n * (m // n) // 批量减,等价于 m = m mod n
else:
n = n - m * (n // m) // 批量减,等价于 n = n mod m
return max(m, n)
欧几里得游戏(参见[Bog])一开始,板上写有两个不相等的正整数。两个玩家交替写数字,每一次,当前玩家都必须在板上写出任意两个板上数字的差,而且这个数字必须是新的,也就是说,不能与板上任何一个已有的数字相同。当玩家再也写不出新数字时,他就输了。请问,你是选择先行动还是后行动呢?
答:
d=gcd(a,b),M=max(a,b)
最终能出现在板上的所有数 :d, 2d, 3d, ..., M总个数:K=dM
- 一开始已经有 2 个数
- 所以 还能写的步数 = 总可写数 - 2 = K−2
当k为偶数时,最后一步会被后手走完,所以后手胜,反之先手必胜
扩展欧几里得算法 不仅能够求出两个正整数m和n的最大公约数d,还能求出两个整数x和y(不一定为正),使得 mx+ny=d。
a.在参考资料中查阅扩展欧几里得算法的描述(参见[KnuI]),然后任选一种语言实现它。
答:
扩展欧几里得算法基于普通欧几里得算法的递归逻辑,在求 gcd(a, b) 的同时,反向推导满足 ax + by = gcd(a, b) 的整数 x 和 y。递归关系:
-
若
b = 0,则gcd(a, 0) = a,此时x=1,y=0(因为a*1 + 0*0 = a); -
若
b ≠ 0,先求gcd(b, a%b) = d,且有b*x' + (a%b)*y' = d;由于a%b = a - (a//b)*b,代入得:a*y' + b*(x' - (a//b)*y') = d,因此x = y',y = x' - (a//b)*y'。// 扩展欧几里得算法:返回 (d, x, y),d = gcd(a,b),且 ax + by = d
tuple<long long, long long, long long> extended_gcd(long long a, long long b) {
// 基线条件:b=0时,gcd(a,0)=a,x=1,y=0
if (b == 0) {
return {a, 1, 0};
}
// 递归求解 gcd(b, a%b),得到 (d, x', y')
auto [d, x_prime, y_prime] = extended_gcd(b, a % b);
// 反向推导当前层的 x 和 y
long long x = y_prime;
long long y = x_prime - (a / b) * y_prime;
return {d, x, y};
}
b.改写上述程序以对丢番图方程 ax+by=c求解,系数a,b,c为任意整数。
不会,直接跳了。
带锁的门 在走廊上有n个带锁的门,从1到n依次编号。最初所有的门都是关着的。我们从门前经过n次,每一次都从1号门开始。在第i次经过时(i=1,2,...,n)我们改变 i的整数倍号锁的状态:如果门是关的,就打开它;如果门是打开的,就关上它。在最后一次经过后,哪些门是打开的,哪些门是关上的?有多少打开的门?
答:
当第 i 次经过时,只改变 "i 的倍数" 号门的状态;
对于编号为 k 的门,它的状态会被所有 k 的约数对应的 i 值改变(比如 k=4,约数是 1、2、4,所以会在 i=1、2、4 时各改变 1 次);
初始状态是 "关",改变偶数次 → 回到 "关";改变奇数次 → 变成 "开"。
此时,我们只需要知道哪些数会改变奇数次即可,而完全平方数正好满足这个条件,即能被1、它的开方、它本身改变三次,因此编号为完全平方数 的门:约数个数奇数 → 状态改变奇数次 → 最终打开;所以打开的门数量 = 不大于 n 的最大完全平方数的平方根 =