算法设计与分析-算法效率分析基础-习题1.1

目录

设计一个计算的算法,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的输入,欧几里得算法最多要做几次除法?

在欧几里得的书里,欧几里得算法用的不是整数除法,而是减法。请用伪代码描述这个版本的欧几里得算法。

欧几里得游戏(参见[Bog])一开始,板上写有两个不相等的正整数。两个玩家交替写数字,每一次,当前玩家都必须在板上写出任意两个板上数字的差,而且这个数字必须是新的,也就是说,不能与板上任何一个已有的数字相同。当玩家再也写不出新数字时,他就输了。请问,你是选择先行动还是后行动呢?

答:

[扩展欧几里得算法 不仅能够求出两个正整数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=31415n=14142计算31415 mod 1414214142 × 2 = 2828431415 - 28284 = 3131gcd(31415,14142) = gcd(14142, 3131)

  • 下一步:m=14142n=3131计算14142 mod 31313131 × 4 = 1252414142 - 12524 = 1618gcd(14142, 3131) = gcd(3131, 1618)

  • 下一步:m=3131n=1618计算3131 mod 16183131 - 1618 = 1513gcd(3131, 1618) = gcd(1618, 1513)

  • 下一步:m=1618n=1513计算1618 mod 15131618 - 1513 = 105gcd(1618, 1513) = gcd(1513, 105)

  • 下一步:m=1513n=105计算1513 mod 105105 × 14 = 14701513 - 1470 = 43gcd(1513, 105) = gcd(105, 43)

  • 下一步:m=105n=43计算105 mod 4343 × 2 = 86105 - 86 = 19gcd(105, 43) = gcd(43, 19)

  • 下一步:m=43n=19计算43 mod 1919 × 2 = 3843 - 38 = 5gcd(43, 19) = gcd(19, 5)

  • 下一步:m=19n=5计算19 mod 55 × 3 = 1519 - 15 = 4gcd(19, 5) = gcd(5, 4)

  • 下一步:m=5n=4计算5 mod 4 = 1gcd(5, 4) = gcd(4, 1)

  • 下一步:m=4n=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是否能同时整除mn,第一个满足条件的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) 的整数 xy。递归关系:

  • b = 0,则 gcd(a, 0) = a,此时 x=1y=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 的最大完全平方数的平方根 =

相关推荐
abant22 小时前
leetcode 739 单调栈模板题
算法·leetcode·职场和发展
Felven2 小时前
C. Dora and Search
c语言·开发语言
宝贝儿好7 小时前
【强化学习实战】第十一章:Gymnasium库的介绍和使用(1)、出租车游戏代码详解(Sarsa & Q learning)
人工智能·python·深度学习·算法·游戏·机器学习
炒鸡菜66610 小时前
程序人生-Hello’s P2P
c语言·程序人生·职场和发展
2401_8846022710 小时前
程序人生-Hello’s P2P
c语言·c++
weixin_4588726110 小时前
东华复试OJ二刷复盘2
算法
Charlie_lll10 小时前
力扣解题-637. 二叉树的层平均值
算法·leetcode
初中就开始混世的大魔王10 小时前
2 Fast DDS Library概述
c++·中间件·信息与通信
爱淋雨的男人11 小时前
自动驾驶感知相关算法
人工智能·算法·自动驾驶