算法设计与分析-习题4.4

目录

[1.切割木棍 一根n英寸长的木棍需要切割成n段1英寸长的小段。描述以最小切割次数完成该任务的算法。如果一次能切多根木棍,再给出最小切割次数的公式。](#1.切割木棍 一根n英寸长的木棍需要切割成n段1英寸长的小段。描述以最小切割次数完成该任务的算法。如果一次能切多根木棍,再给出最小切割次数的公式。)

2.设计一个减半算法来计算[log₂n],并确定它的时间效率。

3.

a.在下面的数组中查找一个键时,折半查找最多需要进行多少次键值比较?

b.请列出所有这样的键,对于它们,折半查找在查找该数组时,需要进行最多次的键值比较。

c.在对该数组折半查找成功的前提下,求键值比较的平均次数(假设查找每一个键的概率都是相同的)。

d.在对该数组折半查找失败的前提下,求键值比较的平均次数(假设查找键位于该数组构成的14个区间内的概率都是相同的)。

4.请估计一下,对于一个包含1000000个元素的有序数组进行成功查找,折半查找比顺序查找平均快多少倍?

5.无论是用数组还是用链表实现一个列表,使用顺序查找的效率都是基本相同的。这对于折半查找来说也成立吗?

6.

a.设计一个只使用两路比较的折半查找的版本,例如只用≤和=。可以任选一种语言来实现,并认真地调试:众所周知,这类程序很容易有错误。

b.对于a中设计的两路比较算法,分析其时间效率。

[7. 猜图片 一个非常流行的解题游戏是这样的:给选手出示42张图片,每行6张,共7行。选手可以给大家做一些是非题,来确定他要寻找的图片。然后进一步要求选手用尽可能少的问题来确定目标图片。给出解决该问题的最有效的算法,并指出需要提问的最大次数。](#7. 猜图片 一个非常流行的解题游戏是这样的:给选手出示42张图片,每行6张,共7行。选手可以给大家做一些是非题,来确定他要寻找的图片。然后进一步要求选手用尽可能少的问题来确定目标图片。给出解决该问题的最有效的算法,并指出需要提问的最大次数。)

[8. 请考虑三重查找(ternary search)------------就是下面这个查找有序数组A[0.. n-1]的算法:如果n=1,就把数组中的唯一元素和查找键 K 进行比较。否则,通过比较 K 和A[[n/3]]来进行递归查找。如果 K 较大,把它和A[[2n/3]]进行比较,以确定在数组的三段中的哪一段中继续查找过程。](#8. 请考虑三重查找(ternary search)————就是下面这个查找有序数组A[0.. n-1]的算法:如果n=1,就把数组中的唯一元素和查找键 K 进行比较。否则,通过比较 K 和A[[n/3]]来进行递归查找。如果 K 较大,把它和A[[2n/3]]进行比较,以确定在数组的三段中的哪一段中继续查找过程。)

a.该算法是以哪种算法思想为基础的?

b.为最差情况下的键值比较次数建立一个递推式(我们可以假设n=3*)。

[c. 在​编辑 的情况下解该递推式。](#c. 在编辑 的情况下解该递推式。)

d.将该算法的效率和折半查找的进行比较。

9.一个数组A[0..n-2]包含n-1个从1到n的整数(因此在这个范围内缺少一个整数),元素升序排列。尽你所能设计一个求缺失整数的最有效算法,并说明它的时间效率。

10.

a.为假币问题的三分算法写一段伪代码。请确保该算法会正确处理所有的n值,而不仅仅是那些3的倍数。

[b.为假币问题的三分算法的称重次数建立一个递推关系,并在​编辑 的情况下对它求解。](#b.为假币问题的三分算法的称重次数建立一个递推关系,并在编辑 的情况下对它求解。)

c.当n的值非常大时,该算法要比把硬币分成两堆的算法快多少倍?这个答案应该与n无关。

11.

[a. 应用俄式乘法来计算26×47。](#a. 应用俄式乘法来计算26×47。)

b.从时间效率的角度看,我们用俄式乘法算法计算n×m和m×n有区别吗?

12.

a.为俄式乘法算法编写伪代码。

b.说明俄式乘法的时间效率类型。

[13. 求J(40)------------在n=40的情况下, 约瑟夫斯问题的解。](#13. 求J(40)————在n=40的情况下, 约瑟夫斯问题的解。)

14.请证明,对于所有为2的乘方的n来说,它的约瑟夫斯问题的解是1。

15.对于约瑟夫斯问题

[a. 当n=1,2,...,15, 计算J(n)。](#a. 当n=1,2,…,15, 计算J(n)。)

b.通过观察,从前15个n值的解中发现一个模式,然后证明它在一般情况下的正确性。

c.有一种做法是将n的二进制表示向左循环移一位来得到J(n),证明它的正确性。


1.切割木棍 一根n英寸长的木棍需要切割成n段1英寸长的小段。描述以最小切割次数完成该任务的算法。如果一次能切多根木棍,再给出最小切割次数的公式。

对于第一个要求,最小切割次数 = n − 1

一次切多根时,有以下递推式:

最小切割次数为 ⌈ log₂n ⌉

2.设计一个减半算法来计算[log₂n],并确定它的时间效率。

我们要算的是:把 n 连续除以 2,直到变成 0,一共能除多少次 。这个次数就是 ⌊log₂n⌋

复制代码
算法:FloorLog2(n)
// 输入:正整数 n
// 输出:⌊log₂n⌋
count ← 0
while n > 1 do
    n ← ⌊n / 2⌋   // 减半操作
    count ← count + 1
return count

时间效率:Θ (log n)。

3.

a.在下面的数组中查找一个键时,折半查找最多需要进行多少次键值比较?

折半查找最大比较次数 = ⌈ log₂(n+1) ⌉

b.请列出所有这样的键,对于它们,折半查找在查找该数组时,需要进行最多次的键值比较。

c.在对该数组折半查找成功的前提下,求键值比较的平均次数(假设查找每一个键的概率都是相同的)。

p=1/13,所以

d.在对该数组折半查找失败的前提下,求键值比较的平均次数(假设查找键位于该数组构成的14个区间内的概率都是相同的)。

p=1/14, 所以

4.请估计一下,对于一个包含1000000个元素的有序数组进行成功查找,折半查找比顺序查找平均快多少倍?

  • 顺序查找成功平均比较次数:Cseq≈n/2
  • 折半查找成功平均比较次数:Cbin≈
  • 顺序查找​=500000

  • 折半查找≈20

  • 快约25000倍

5.无论是用数组还是用链表实现一个列表,使用顺序查找的效率都是基本相同的。这对于折半查找来说也成立吗?

不成立,与数组不同,数组中的任何元素都可以在常数时间内访问,而在链表中访问中间元素是一个 Θ(n) 操作。因此,虽然原则上可以实现,但二分查找对于在(已排序的)链表中搜索将是一个极其低效的算法。

6.

a.设计一个只使用两路比较的折半查找的版本,例如只用≤和=。可以任选一种语言来实现,并认真地调试:众所周知,这类程序很容易有错误。

二路如下:

复制代码
算法:BinarySearch2Way(A[0..n-1], key)
// 两路比较折半查找,只用 ≤ 和 =
low = 0
high = n - 1
pos = -1

while low ≤ high:
    mid = (low + high) // 2
    if key ≤ A[mid]:
        high = mid - 1
        if key == A[mid]:
            pos = mid  // 记录找到的位置
    else:
        low = mid + 1

return pos

c版本:

复制代码
int binarySearch(int A[], int n, int key) {
    int low = 0, high = n - 1;
    int pos = -1;

    while (low <= high) {
        int mid = (low + high) / 2;

        // 两路比较:只用 ≤ 和 =
        if (key <= A[mid]) {
            high = mid - 1;
            if (key == A[mid])
                pos = mid;
        } else {
            low = mid + 1;
        }
    }
    return pos;
}

b.对于a中设计的两路比较算法,分析其时间效率。

时间效率:O (log n),因为每次迭代将问题规模减半,循环执行 Θ(log n) 次。

7. 猜图片 一个非常流行的解题游戏是这样的:给选手出示42张图片,每行6张,共7行。选手可以给大家做一些是非题,来确定他要寻找的图片。然后进一步要求选手用尽可能少的问题来确定目标图片。给出解决该问题的最有效的算法,并指出需要提问的最大次数。

先对行二分,再对列二分

最大提问次数⌈log₂42⌉ = 6 次

8. 请考虑三重查找(ternary search)------------就是下面这个查找有序数组A[0.. n-1]的算法:如果n=1,就把数组中的唯一元素和查找键 K 进行比较。否则,通过比较 K 和A[[n/3]]来进行递归查找。如果 K 较大,把它和A[[2n/3]]进行比较,以确定在数组的三段中的哪一段中继续查找过程。

a.该算法是以哪种算法思想为基础的?

减治(减而治之),减常数因子的算法思想

b.为最差情况下的键值比较次数建立一个递推式(我们可以假设n=3*)。

每次要查三段:

  • 先比较 1 次:K 与 A[n/3]
  • 若更大,再比较 1 次:K 与 A[2n/3]最坏每次比较 2 次,然后进入规模 n/3 的子问题。

则递推式为:,C(1)=1

c. 在 的情况下解该递推式。

带入n=3^k,则

d.将该算法的效率和折半查找的进行比较。

三重查找的比较次数略多于折半查找 ,效率比折半查找稍差 。两者都是对数级别 Θ(log n) ,但折半查找更快

9.一个数组A[0..n-2]包含n-1个从1到n的整数(因此在这个范围内缺少一个整数),元素升序排列。尽你所能设计一个求缺失整数的最有效算法,并说明它的时间效率。

复制代码
算法:FindMissing(A, n)
    low = 0
    high = n - 2
    while low <= high:
        mid = (low + high) // 2
        // 正常应该满足 A[mid] = mid + 1
        if A[mid] > mid + 1:
            // 缺失在左边
            high = mid - 1
        else:
            // 缺失在右边
            low = mid + 1
    return low + 1

10.

a.为假币问题的三分算法写一段伪代码。请确保该算法会正确处理所有的n值,而不仅仅是那些3的倍数。

把硬币分成 三堆,数量尽量相等:

  • 堆 1:a 枚
  • 堆 2:a 枚
  • 堆 3:剩下的(n-2a)枚

称1和2,如果1和2不等,对轻的那一边再进行划分,如果相等,对3进行划分,不断减少规模。

复制代码
算法:FakeCoin(A[1..n])
// 输入:n 枚硬币,1 枚较轻
// 输出:假币编号

if n == 1:
    return 唯一那枚硬币

// 分成三堆(尽可能平均)
a = n // 3
堆1 = A[1 ... a]
堆2 = A[a+1 ... 2a]
堆3 = A[2a+1 ... n]

称 堆1 vs 堆2

if 堆1 < 堆2:
    return FakeCoin(堆1)
else if 堆2 < 堆1:
    return FakeCoin(堆2)
else:
    return FakeCoin(堆3)

b.为假币问题的三分算法的称重次数建立一个递推关系,并在 的情况下对它求解。

n=3^k时,进行递推得:

c.当n的值非常大时,该算法要比把硬币分成两堆的算法快多少倍?这个答案应该与n无关。

约1.58倍

11.

a. 应用俄式乘法来计算26×47。

b.从时间效率的角度看,我们用俄式乘法算法计算n×m和m×n有区别吗?

俄式乘法的循环次数 = 左边那个数不断除以 2 直到 1 的次数

因此较小的数在左边会使得循环次数减少

12.

a.为俄式乘法算法编写伪代码。

复制代码
算法:RussianMultiplication(n, m)
// 输入:两个正整数 n, m
// 输出:n × m 的结果
// 功能:使用俄式乘法(俄罗斯农民乘法)计算乘积

product ← 0          // 初始化乘积为0

while n ≥ 1 do
    if n 是 奇数 then
        product ← product + m
    n ← ⌊ n / 2 ⌋    // n 除以2,向下取整
    m ← m × 2        // m 乘以2

return product

b.说明俄式乘法的时间效率类型。

时间效率为Θ()

13. 求J(40)------------在n=40的情况下, 约瑟夫斯问题的解。

  • 找不超过 40 的最大 2 的幂:2^5=32

  • L=40−32=8

  • J(40)=2×8+1=17

14.请证明,对于所有为2的乘方的n来说,它的约瑟夫斯问题的解是1。

  • 因为n=2^m
  • 所以不超过 n 的最大 2 的幂就是它自己:2^m=n
  • 因此L=n−2^m=0
  • 代入公式:J(n)=2L+1=2⋅0+1=1

当人数 n=2m 时:

  • 第一轮:杀掉所有偶数位 的人,剩下全是奇数位:1,3,5,...
  • 剩下人数正好是 2^(m-1),仍然是 2 的乘方。
  • 每一轮都如此,每次剩下的都是 2 的乘方 ,且1 号永远不会被杀
  • 最后只会剩下 1 号

15.对于约瑟夫斯问题

a. 当n=1,2,...,15, 计算J(n)。

b.通过观察,从前15个n值的解中发现一个模式,然后证明它在一般情况下的正确性。

当 n = 2^m + L 时,先杀掉 L 个人,剩下 2^m 个人,而 2^m 个人的约瑟夫斯解是 1,对应原来的编号就是 2L+1。所以 J (n)=2L+1。

c.有一种做法是将n的二进制表示向左循环移一位来得到J(n),证明它的正确性。

对于二进制来说:2^m 是 1 后面一串 0 L 是 后面那串数

所以 n = 1 + L 的二进制循环左移一位 = L 的二进制后面加个 1= 2L + 1= J(n)

相关推荐
x_xbx2 小时前
LeetCode:102. 二叉树的层序遍历
算法·leetcode
2401_889884662 小时前
嵌入式C++测试框架
开发语言·c++·算法
月明长歌2 小时前
【码道初阶-Hot100】LeetCode 128. 最长连续序列:从排序双指针扫描到 HashSet,一文讲透为什么 O(n) 解法要用哈希
算法·leetcode·哈希算法
Z9fish2 小时前
C语言算法专题总结(一)排序
c语言·算法·排序算法
美式请加冰2 小时前
模拟的介绍和使用
java·开发语言·算法
云泽8082 小时前
蓝桥杯算法精讲:贪心算法之区间问题深度剖析
算法·贪心算法·蓝桥杯
tankeven2 小时前
HJ129 小红的双生数
c++·算法
万能的小裴同学2 小时前
C++ 简易的FBX查看工具
开发语言·c++·算法
Boop_wu2 小时前
[Java 算法] 前缀和(2)
算法·哈希算法·散列表