📘 CodingMarble Learn

算法设计方法

设计算法时,先把问题说清楚:输入是什么数据,要得到什么输出,有什么条件。然后用自然语言、步骤列表、伪代码或流程图,写出清楚且有限的步骤。大问题可以自顶向下逐层拆小(逐步求精),也可以自底向上,先做好并测试小零件再拼起来。常用方法有:暴力法(全部试一遍)、分治(拆开、解决、合并;像二分查找和归并排序那样每次减半)、贪心(每次选当前看起来最好的,速度快,但不一定最优)、动态规划(每个小问题只算一次,并存进表里)和回溯(先试一个选择,走进死路就撤销)。选方法和数据结构(数组、栈、二叉树)时,要检查它是否正确、是否高效(时间复杂度)。

🎬 分步故事

  1. 先说清问题。输入:8个数。输出:最大的那个。步骤:逐个检查每个盒子,记住目前最大的。
  2. 分治:要在1到16中找一个数,就问中间那个数,每次扔掉一半。只需要问4次。
  3. 贪心:每次拿最大的、放得下的硬币。它很快,但硬币是1、3、4时,凑6要用3枚,而最少只要2枚。
  4. 动态规划:先算小金额,把每个答案存进表里。这张表会找到 6 = 3 + 3。
  5. 回溯:沿着一条路走;遇到死路,就退回上一个岔路口,换另一条路,直到走到出口。
  6. 轮到你了:选一个金额和一组硬币。猜一猜:贪心会给出最少的硬币数吗?再和动态规划比一比。

提示:拖动3D画面可以旋转,用两根手指可以缩放。

🤔 常见疑问,一次讲清

为什么写代码之前要先写输入和输出?

如果你不清楚到底输入什么、必须输出什么,就无法检验步骤对不对。

为什么问4个问题就够找出16个数中的一个?

每个回答都会扔掉一半:16、8、4、2、1。变灰的盒子就是被扔掉的那一半。

既然贪心可能出错,为什么还要用?

它非常快、也很简单,而且对很多问题(普通硬币、最早结束的活动)已经证明是正确的。

动态规划和把所有可能都试一遍有什么不同?

它把每个小金额只算一次并重复使用,所以表是一步一步变大的,而不用去探索每一种组合。

回溯是不是每次都从头开始?

不是。它只退回到上一个还有没试过的路的岔路口,然后继续走。

怎么知道贪心对我的硬币有没有用?

对很多金额,把它和动态规划比较。在自由玩耍里试试硬币 1、7、10。

说明问题:输入、输出和步骤

写代码之前,先把问题准确地描述出来。这叫做问题说明。

写出步骤

算法是一串有限而清楚的步骤,能把任何合法的输入变成正确的输出。可以这样写:

用小的输入手动试一试,也要试特殊情况(所有数都相等、有负数、只有一个数)。

自顶向下和自底向上设计

自顶向下(逐步求精):从整个任务开始,先拆成几个大步骤,再把每个步骤继续拆,直到每一小块都容易写成代码。例如:“做成绩单”→ 读入分数 → 计算平均分 → 评定等级 → 打印。

自底向上:先做好并测试小的、可重复使用的零件(比如求最大值的函数、排序的函数),再把它们拼成完整的程序。

真正的项目两种都用:自顶向下做计划,自底向上来制作和测试。

分治法和对半查找

分治有三步:把问题拆成同类的更小部分,解决每一部分(常常用递归),再把答案合并。

贪心算法

贪心算法每次选当下看起来最好的,而且选了就不再改。

但贪心不一定正确:硬币是 1、3、4 时,用贪心付 6 会得到 4 + 1 + 1(3枚),而 3 + 3 只要 2 枚。要相信一个贪心方法,必须证明它,或者拿它和一定正确的方法对比测试。

动态规划和回溯

动态规划(DP)

当同样的小问题反复出现时,每个只解一次,把答案存进表里。凑出金额 a 的最少硬币数:best[a] = 1 + min(best[a - c]),对所有 c ≤ a 的硬币 c 取最小,从 best[0] = 0 开始。硬币为 1、3、4 时:best = 0, 1, 2, 1, 1, 2, 2。从小到大填表是自底向上;带记忆的递归是自顶向下(记忆化)。更多内容见单独的动态规划课程。

回溯

一次做一个选择来构造解。如果某个选择违反规则或走进死路,就撤销它,再试下一个选项。可以用于迷宫、数独、N皇后问题和列出所有子集。它是一种小心的暴力法:会跳过所有不可能成功的整条分支。

暴力法

把每一种可能的答案都试一遍。总是正确,但往往慢得无法接受(2ⁿ 个子集,n! 种排列)。

选择方法:正确性、效率和数据结构

方法适合的情况例子常见时间
暴力法输入很小试遍3位数的所有密码常常是 2ⁿ 或 n!
分治各部分互相独立二分查找、归并排序O(log n)、O(n log n)
贪心已证明局部最优选择是安全的活动选择、找零O(n log n)
动态规划小问题会重复硬币找零、最短路径表的大小
回溯带规则的搜索迷宫、数独指数级,但会剪枝

要说明理由

正确性:说明算法一定会停下来,并给出正确的输出(用循环不变式、证明,或者对边界情况的测试)。效率:数一数当 n 变大时步数怎么增长(大O),并和其他方法比较。

数据结构来帮忙

数组用来做表(动态规划),栈用来做回溯(记住该退回到哪里),队列用来一层一层地搜索。二叉树存放数据时,每个结点最多有两个孩子;在二叉搜索树里,较小的键放左边,较大的放右边,所以每往下一层,查找的工作就减半,就像二分查找一样。

动手试试:硬币和猜数游戏

  1. 和朋友玩1到100的猜数游戏。每次都问中间的数。你能保证7次以内猜中吗?(2⁷ = 128。)
  2. 硬币为 1、3、4,在纸上写出金额0到10的动态规划表。贪心在哪里出错?
  3. 打开最后一个3D步骤。试试硬币 1、7、10,金额 14。贪心给出 10 + 1 + 1 + 1 + 1;动态规划给出 7 + 7。
  4. 为“找出列表中最小的数”写一个步骤列表,并用 5, 5, 5 和只有一个数的情况来测试。

重点公式和概念

例题讲解

1. 为“找出n个数中最大的数”写出问题说明和步骤列表。

输入:n ≥ 1 个数。输出:最大的那个数。步骤:1. best ← 第一个数。2. 对其他每个数 x:如果 x > best,就令 best ← x。3. 输出 best。对于 4, 9, 2, 7, 12, 5, 10, 3,比较7次后输出 12。

2. 要在1到1000中找出一个数,对半查找需要问几个问题?

每个问题都把范围减半:1000 → 500 → 250 → 125 → 63 → 32 → 16 → 8 → 4 → 2 → 1。一共10个问题(2¹⁰ = 1024 ≥ 1000)。

3. 用 50、20、10、5、2、1 的硬币,按贪心方法付 87。

50(剩 37),20(剩 17),10(剩 7),5(剩 2),2(剩 0):50 + 20 + 10 + 5 + 2 = 5 枚硬币。

4. 硬币为 1、3、4,填出金额到 7 的DP表。

best[0]=0,[1]=1,[2]=2,[3]=1,[4]=1,[5]=min(best4, best2, best1)+1=2,[6]=min(best5, best3, best2)+1=2,[7]=min(best6, best4, best3)+1=2(3 + 4)。

5. 活动(开始-结束):A 9-11,B 10-12,C 11-13,D 12-14,E 13-15。选出互不重叠、数量最多的活动。

按最早结束的贪心:A(11结束),然后 C(11开始,13结束),再选 E(13开始)。共3个活动:A、C、E。

6. 用自顶向下的方法设计:一个告诉全班平均分和最高分同学的程序。

第1层:读入数据 → 计算 → 打印。第2层:把姓名和分数读入列表;计算总分和平均分;找出最高分及其姓名;打印两者。然后每一块自底向上地编写和测试。

常见错误

练习测验

1. 问题说明必须写清:
2. 在16个有序数据上做二分查找,最多大约需要:
3. 哪种方法总是选当前看起来最好的?
4. 动态规划适合在什么时候使用:
5. 在迷宫搜索中,遇到死路后退回上一个岔路口,这是:

练习:自己动手回答

输入或选择你的答案,然后点“检查”。卡住了就看提示;回答后会显示完整解答。

常见问题

算法设计的主要方法有哪些?

暴力法、分治、贪心、动态规划和回溯,要在说明了输入和输出之后再选择。

贪心和动态规划有什么区别?

贪心每一步只选一个看起来最好的,而且不回头。动态规划会考虑小问题的所有选择并存下最好的答案,所以在子问题重叠时能找到真正的最优解。

自顶向下和自底向上设计是什么意思?

自顶向下是把整个任务拆成更小的步骤;自底向上是先做好并测试小的部分,再把它们拼起来。大多数程序两种都用。

哪里会学到

PolandSzkoła podstawowa, klasa VIIUnderstanding, analysing and solving problems
PolandSzkoła podstawowa, klasa VIIIUnderstanding, analysing and solving problems
PolandLiceum ogólnokształcące, klasa IUnderstanding, analysing and solving problems
China高三Electives (选修)

先学这些

接着学

相关课程

全部Computer Science课程