try ai
科普
编辑
分享
反馈
  • 结构化编程

结构化编程

SciencePedia玻尔百科
核心要点
  • 结构化编程用三种基本结构——顺序、选择和迭代——取代混乱的 goto 语句,以创建可理解、可验证的代码。
  • 动态规划通过将复杂问题分解为重叠的子问题来解决它们,每个子问题只解决一次,并存储其结果以构建最优解。
  • 这种结构化方法是解决不同领域问题的基础,包括生物学中的序列比对、网络中的最短路径查找以及数学中的组合计数。
  • 结构化方法的威力取决于问题固有的结构;破坏这种结构可能使问题在计算上变得棘手或成为 NP-难题。

引言

在数字时代,复杂性是终极的敌人。无论是构建庞大的软件系统、解码生命的遗传蓝图,还是优化全球物流网络,挑战都在于管理数量惊人的相互作用部分。早期的编程直接面临了这场危机,代码杂乱无章,以至于被称为“面条式代码”,几乎不可能理解或信任。这种混乱催生了一场思想革命:结构化编程。本文探讨了这一强大范式的原则,它为计算逻辑带来了秩序。

在第一章“原则与机制”中,我们将揭示结构化编程的核心信条及其最强有力的分支——动态规划。动态规划是一种通过将庞大问题分解成可管理的小块来解决问题的方法。随后,在“应用与跨学科联系”一章中,我们将踏上一段穿越数学、生物学和网络理论的旅程,看这种优雅的方法如何为解决一些科学界最紧迫的挑战提供统一的框架。

原则与机制

goto 的暴政:在混乱中寻找秩序

想象一下,你正在照着一份食谱做菜,但说明书不是“第一步、第二步、第三步”,而是“从这里开始。现在,跳到第 7 页第三段。之后,回到第 2 页第 5 行,除非你用的是无盐黄油,那种情况请跳到附录。”你很快就会陷入一团乱麻。早期的计算机编程与此非常相似。程序员控制程序流程的主要工具是 goto 语句,这是一条无条件地告诉计算机跳转到另一行代码的指令。这导致程序变得异常复杂,逻辑交错混乱,甚至程序员自己都难以理解或修改。这种噩梦般的场景被贴切地称为​​面条式代码​​。

革命源于一个简单而深刻的认识:任何算法,无论多么复杂,都可以仅由三种基本模式构建而成。首先是​​顺序​​(Sequence):一件接一件地做事。其次是​​选择​​(Selection):根据一个条件在两条路径之间做出选择(if-then-else 语句)。第三是​​迭代​​(Iteration):重复一个代码块(while 或 for 循环)。仅此而已。这就是​​结构化编程​​的基础三元体。

让我们看看实际的例子。思考一个我们熟悉的 for 循环,这是一种常见的迭代结构。程序员可能会写下类似 for(initialization; condition; increment) { body } 的代码。这看起来像一个单一、整体的命令。但在底层,编译器必须将其翻译成机器的原始语言,即条件跳转和无条件跳转。其美妙之处在于,这种翻译不是一团乱麻,而是一个完全有序的结构。for 循环被解构为一系列基本操作:初始化代码运行一次,然后一个标签标记出条件检查的位置。如果条件为真,循环体执行,接着是增量代码,最后一条无条件的跳转将执行流带回条件检查处。如果条件为假,一条条件跳转则将执行流送到一个退出标签,完全跳过循环。这种分解,将一个高级抽象转化为标签和跳转的有序舞蹈,是编译器设计中的一项核心任务。它表明,即使是我们最复杂的控制结构,其核心也是对三种基本模式的优雅组合。结构化编程就是用这些简单、可理解、可验证的部件构建复杂逻辑的纪律,它将我们从 goto 的暴政中解放出来。

“分治”策略:动态规划

强加结构的哲学并不仅限于控制程序流程,它延伸到解决问题的行为本身。科学和工程领域中许多最具挑战性的问题,乍一看似乎都庞大得可怕且相互关联。这里的结构化方法是一种“分治”,但带有一个巧妙的转折。这种策略被称为​​动态规划(Dynamic Programming, DP)​​。

动态规划的核心思想是将一个庞大、复杂的问题分解成一系列更小、更简单的子问题,然后通过组合这些子问题的解来解决大问题。关键的洞见在于,这些子问题中有许多是重叠的。我们不是一遍又一遍地解决同一个子问题,而是只解决它一次,并将其解存储在一个表中。当再次需要这个解时,我们只需查找即可。

让我们想象一个简单的、近乎玩具的问题。你身处一个网格中,就像一个棋盘,想要找到从一个起始角落到目标角落是否存在路径,但你只被允许向右或向下移动,并且某些方格是障碍物。有多少条路径呢?这个数字可能是天文数字!试图检查所有路径将是徒劳的。

但让我们换一种方式思考。与其询问整个路径,不如问一个更简单的局部问题:“是否可能到达位置 (i,j)(i, j)(i,j) 的方格?”如果我们使用结构,答案出奇地简单。你只能从它上方的方格 (i−1,j)(i-1, j)(i−1,j) 或左侧的方格 (i,j−1)(i, j-1)(i,j−1) 到达方格 (i,j)(i, j)(i,j)。因此,如果我们已经知道那些方格是否可达,我们就可以一步之内确定 (i,j)(i, j)(i,j) 的答案。这个问题的解具有一个优美的结构:is_reachable[i][j]=is_reachable[i−1][j]∨is_reachable[i][j−1]is\_reachable[i][j] = is\_reachable[i-1][j] \lor is\_reachable[i][j-1]is_reachable[i][j]=is_reachable[i−1][j]∨is_reachable[i][j−1]。我们可以从起点开始,让这个简单的规则像波浪一样在网格中传播,填满一张答案表。关于路径的全局复杂问题被简化为一系列简单的局部计算。

这种计算的“波浪”是一个极其强大的思想。找到网格路径的同一个基本模式可以用来揭示生命本身的秘密。例如,在生物学中,预测一条 RNA 链如何折叠成三维形状是一个出了名的难题。分子“想要”稳定在尽可能低的能量状态,但可能的构型数量是巨大的。利用动态规划,科学家可以将其分解。他们计算出 RNA 每个可能的小子序列的最小自由能。然后,一个较大结构(比如从核苷酸 iii 到 jjj)的能量可以通过组合其较小组成部分(如一个发夹环或其中一个较小的堆叠对)的已知能量来计算。这个复杂的全局优化问题通过构建一个由简单局部子问题的解组成的表格来解决,就像我们在网格上所做的一样。

这个原则不仅限于线性序列或二维网格。它在更复杂的结构(如树)上也同样有效。假设你有一棵树,每个节点都有一个权重。要找到以特定节点为根的子树的总权重,你不需要每次都遍历整个子树。你可以使用​​后序遍历​​(post-order traversal),这是一种结构化的节点访问方式。它保证你在访问一个节点之前,已经访问了该节点的所有子节点。这意味着当你到达一个父节点时,它所有子节点的子树和都已经被计算并最终确定。你只需将它们的值相加,再加上父节点自身的权重即可。这种后序遍历提供了一个完美的计算调度,尊重了问题自然的依赖结构。

最优性、保证及其局限

我们为什么要费尽周折去寻找结构?仅仅是为了优雅吗?不。像动态规划这样的结构化方法之所以强大,是因为它通常带有一个保证:它找到的解不仅仅是一个好的解,而且是​​可证明为最优​​的。

想象一下比较两种蛋白质结构以判断它们有多相似的任务。一种方法可能是​​贪心算法​​(greedy algorithm):找到一对小的、对齐良好的片段,将其锁定,然后贪心地尝试扩展这个对齐。这很直观,就像在迷宫中找路,总是选择当前看起来最有希望的路径。但你可能会走上一条起初看起来很好,结果却通向死胡同的岔路,导致你错过了本可以走的一条更长、更好的路径。

而基于动态规划的结构化方法则不同。它将问题视为在有向无环图(DAG)中寻找最大权重路径,其中节点是潜在的对齐片段,边连接着可以有序链接在一起的片段。通过系统地构建以每个片段结尾的最佳链,动态规划以一种有组织的方式探索了所有可能性。这就像拥有一张完整的迷宫地图,而不仅仅是一个指南针。这保证了能找到全局最优的对脱,而这正是贪心策略可能会错过的路径。

然而,这种能力有其边界。动态规划之所以有效,是因为问题具有清晰、有序的结构——可能扩展的图是一个 DAG。如果我们放宽这个约束会怎样?如果我们允许片段不按顺序对齐,以考虑像环状排列这样的生物学现象呢?结构就会被打破。图不再是 DAG,寻找片段最佳组合的问题突然转变为臭名昭著的“最大权独立集”问题,这是一个 NP-难题。这意味着可能没有高效的算法来解决它。这是一个深刻的教训:结构化编程中的“结构”不仅仅是为了方便;它往往是使一个问题在计算上变得可解的根本原因。

即使我们能保证得到最优解,大自然仍然可能笑到最后。在某些情况下,可能不存在唯一的最佳答案。例如,当使用维特比算法(一种 DP 技术)来解码隐马尔可夫模型中的隐藏状态序列时,可能会有两种或更多不同的状态序列具有完全相同且最大的概率。算法找到了最佳分数,但可能有多条路径可以达到这个分数。我们的结构化方法为我们提供了一个强大的透镜,但它们揭示的是世界的本来面目,包括其所有的模糊性。

数据的结构与计算的结构

到目前为止,我们一直在讨论如何为我们的算法强加结构。但是,如果我们能以结构化的方式来构建我们的数据本身呢?这是​​函数式编程​​(functional programming)的一个核心思想,它与动态规划产生了美妙的协同效应。

让我们首先看看记忆化(memoization),它本质上是动态规划的另一种表现形式。你不是自底向上地构建一个表,而是编写一个递归函数,并让它自动缓存其结果。第一次调用 f(x)f(x)f(x) 时,它会计算值并存储起来;下一次调用时,它只需检索即可。

现在,考虑一种​​持久化数据结构​​(persistent data structure),这是函数式编程的一个标志。在这种范式中,数据是​​不可变的​​(immutable)——它永远不能被改变。当你“更新”一个结构时,你不会修改旧的结构。相反,你会创建一个新版本,它与旧版本共享大部分结构。例如,在平衡二叉搜索树中更改一个键,可能涉及创建一个新的根和一条通往变更处的新路径节点,但所有未修改的子树都只是被新结构指向。这被称为​​结构共享​​(structural sharing)。

记忆化和持久化数据结构的结合,就是奇迹发生的地方。想象我们有一个纯函数 g,它计算树的某个属性。我们在持久化树的版本 T0T_0T0​ 上对它求值,记忆化表会填满树中每个节点的结果。现在,我们执行一次更新,创建版本 T1T_1T1​。这个新版本由少数新节点(被复制的路径)和大量从 T0T_0T0​ 共享过来的旧节点组成。当我们现在对 g(T1)g(T_1)g(T1​) 求值时,计算会沿着新路径进行。但一旦它碰到一个共享的、未修改的子树,它会发现该节点的结果已经存在于记忆化表中!对那个庞大子树的整个计算都被跳过了。总工作量只与新创建的节点数量成正比,而不是整个树的大小。这是一个惊人的效率提升,是我们的数据结构与计算结构对齐的直接结果。这个原则是如此强大和通用,以至于它在截然不同的领域都有应用,例如在计算经济学中,像策略函数迭代(Policy Function Iteration)这样的迭代方法被用来寻找随时间推移的最优经济策略。

压力下的结构:当现实来袭

我们描绘了一幅图景,将结构化编程描绘成一种驯服复杂性的强大而优雅的方式。但我们的抽象算法最终必须在具有现实世界限制的物理机器上运行。当硬件冷酷无情的约束与问题优美抽象的结构发生冲突时,会发生什么?

让我们回到动态规划。一个标准的 DP 算法,用于寻找长度为 nnn 的字符串中的最长回文子序列,需要一个大小为 O(n2)O(n^2)O(n2) 的表。这在理论上没有问题。但如果你的计算机有内存限制呢?假设你只有 O(n)O(\sqrt{n})O(n​) 的可用内存。你甚至无法存储 DP 表的单行或单条对角线!这是否意味着我们的结构化方法毫无用处?

完全不是。这意味着我们必须更聪明。我们不能放弃 DP 逻辑——它是解决这个问题的正确结构。但我们必须改变我们的​​计算调度​​(computation schedule)。解决方案是将大的 n×nn \times nn×n DP 表划分为一个由更小的瓦片组成的网格,比如大小为 n×n\sqrt{n} \times \sqrt{n}n​×n​。我们可以一次计算一个瓦片,而这样做所需的内存与其边界大小成正比,即 O(n)O(\sqrt{n})O(n​)——这符合要求!

但这里有一个陷阱。要计算一个瓦片,我们需要来自其“下方”和“左侧”瓦片的结果。虽然来自紧邻左侧瓦片的结果可以传递过来,但来自下方整行瓦片的结果无法存储在内存中。令人心碎的结论是,为了计算每个瓦片,我们可能不得不从头重新计算我们需要的边界值。我们被迫用时间换取空间。为了遵守内存限制,我们优雅的 O(n2)O(n^2)O(n2) 算法变慢了。仔细分析表明,这种瓦片划分和重新计算的策略导致总运行时间为 O(n5/2)O(n^{5/2})O(n5/2)。

这也许是最终的教训。结构化编程为我们提供了一张问题逻辑景观的地图。它揭示了依赖关系、子问题和优雅的递推关系。但这张抽象地图必须铺设在真实机器的物理领土上,机器有着有限的内存和处理速度。编程的真正艺术不仅在于发现抽象结构,还在于找到在现实无情的约束下,导航该结构的最有效、最美丽的方式。

应用与跨学科联系

在上次的讨论中,我们揭示了结构化编程及其强大分支——动态规划的美丽核心。这个想法似乎简单得近乎骗人:如果你有一个庞大的问题,就把它分解成更小、更易于管理的部分。如果这些部分重叠,不要重复解决它们;每个部分只解决一次,并记住答案。这就像建造一座宏伟的城堡,不是一次性举起整个建筑,而是一次一块石头地精心放置,每块石头都稳固地安放在已经铺好的石头上。

现在,你可能会想,“这对解谜来说是个不错的技巧,但在现实世界中有什么用呢?”这正是魔法真正开始的地方。这个单一、优雅的原则不仅仅是程序员的工具;它是一把万能钥匙,能解开横跨人类探究的惊人领域中的问题,从纯粹数学最深奥的问题到生命本身错综复杂的舞蹈。让我们踏上旅程,看看这个简单的想法能带我们去向何方。

计数与排序的艺术

从本质上讲,许多科学和数学都与计数和排序有关。不仅仅是“一、二、三”,而是计算事物可以排列的方式有多少种,或在表面上的混乱中找到最合乎逻辑的顺序。这正是动态规划首次展现其威力的地方。

考虑一个数论中的经典问题:一个整数 nnn 可以用多少种方式写成更小整数的和?例如,数字 444 可以写成 444、3+13+13+1、2+22+22+2、2+1+12+1+12+1+1 或 1+1+1+11+1+1+11+1+1+1。这些被称为整数分拆(partitions)。随着 nnn 变大,分拆的数量以一种令人困惑的复杂方式爆炸性增长。我们如何才能追踪这一切?动态规划给了我们一个切入点。我们可以通过提出一个结构化的问题来系统地构建答案:使用不超过某个大小(比如 kkk)的数字,有多少种 nnn 的分拆?p≤k(n)p_{\le k}(n)p≤k​(n) 的答案可以巧妙地从我们已知的答案中构建出来——即使用不超过 k−1k-1k−1 的数字的分拆数量,以及一个更小的数 n−kn-kn−k 的分拆数量。这个递推关系,p≤k(n)=p≤k−1(n)+p≤k(n−k)p_{\le k}(n) = p_{\le k-1}(n) + p_{\le k}(n-k)p≤k​(n)=p≤k−1​(n)+p≤k​(n−k),让我们能够一步步地填充一个解的表格,将组合爆炸变成一个可管理、优雅的计算。这是用结构驯服无限的一个美丽例子。

这种寻找秩序的思想超越了纯粹的数字。想想你正在阅读的这段文字。如果我做了一些修改,像文字处理器的“追踪修订”或开发者的 diff 工具这样的计算机程序是如何准确地找出新旧版本之间的差异的?它试图找到没有改变的最长公共单词或字符序列。这就是著名的​​最长公共子序列(LCS)​​问题。动态规划通过构建一个二维网格,逐个字符地比较两个文本来解决这个问题。网格中的每个单元格回答了这个问题:“到目前为止,这两个文本的前缀的 LCS 是什么?”而每个答案都是由相邻单元格中的答案构建而成的。

更妙的是,我们可以让这个过程更智能。如果一个文本中有一长串相同的字符,比如一千个‘a’,我们真的需要执行一千次相同的比较吗?当然不需要!一个优化的 DP 算法可以识别这种重复,并理解这一千个‘a’最多只能匹配另一个文本中存在的‘a’的数量。这种将相同项分组为“运行”(run)的洞见,极大地加快了计算速度,使其对于巨大的真实世界文件也变得实用。

穿梭于网络之中

世界充满了网络:道路网、计算机网、社交网,甚至是你在一天中做出的决策“网络”。我们常常想找到从 A 到 B 的“最佳”路径。动态规划为几乎所有的最短路径算法提供了基本逻辑。

想象一下,你正在设计一个通信网络,其中每个链接都有一定的成功概率,即其“可靠性”。你想找到两个节点之间最可靠的路径。一条路径的总可靠性是其所有边的可靠性的乘积。这看起来不像一个最短路径问题,后者通常涉及距离的和。但核心的 DP 思想比简单的加法更通用!像 Floyd-Warshall 这样的算法通过测试从 iii 到 jjj 的路径上每一个可能的中间点 kkk 来工作。标准的更新是 d(i,j)=min⁡(d(i,j),d(i,k)+d(k,j))d(i,j) = \min(d(i,j), d(i,k) + d(k,j))d(i,j)=min(d(i,j),d(i,k)+d(k,j))。对于我们的可靠性问题,我们只需交换操作符!更新变成了 rel(i,j)=max⁡(rel(i,j),rel(i,k)×rel(k,j))\text{rel}(i,j) = \max(\text{rel}(i,j), \text{rel}(i,k) \times \text{rel}(k,j))rel(i,j)=max(rel(i,j),rel(i,k)×rel(k,j))。其基本原则——通过中间站点构建最优路径——保持不变。这优美地展示了 DP 概念的代数灵活性。

当问题变得更加复杂时,情节也随之加深。考虑“中国邮递员问题”:一名邮递员必须走遍一个社区的每一条街道并返回起点,同时行驶的距离要最短。如果每个交叉口(顶点)都有偶数条街道(边),那么解很简单——存在一个欧拉回路,总距离就是所有街道长度之和。但如果某些交叉口有奇数条街道呢?邮递员将不得不重复走某些街道。哪些街道呢?这个问题可以优雅地归结为找到一种最经济的方式来添加“虚拟”边,使所有顶点的度数都变为偶数。这又变成了一个在奇数度顶点集合上寻找最小代价完美匹配的问题。而我们如何解决那个问题呢?当然是用动态规划!我们可以使用一个通用的但较慢的 DP,尝试所有可能的配对。或者,如果奇数顶点恰好位于一条路径上,我们可以使用一个快得多的、专门的 DP,利用这种线性结构。这段从实际路由问题到图论,再到匹配,最后到动态规划的旅程,证明了 DP 帮助我们在不同数学领域之间建立的深刻联系。

也许网络中最惊人的应用是解决那些被认为是计算上“棘手”的问题。像寻找图的最大割(将顶点分成两组以最大化它们之间的边数)或寻找图的最小着色数(其色数)这样的问题是 NP-难的。这意味着对于一个一般的图,找到一个完美解所需的时间呈指数级增长,我们没有已知的“巧妙”算法。但许多现实世界的网络,从传感器网格到蛋白质相互作用网络,并不仅仅是随机纠缠的连接。它们通常具有“树状”结构,这可以用一个称为​​树宽​​(treewidth)的概念来正式捕捉。一个具有低树宽的图,无论多大,都可以被“驯服”。在图的树分解上进行动态规划,使我们能够高效地解决这些原本不可能的问题。DP 算法在分解的层级“袋”(bag)中移动,记录每个袋中顶点所定义的小子问题的解。这使我们能够保证,例如,一个树宽为 kkk 的网络,永远不会需要超过 k+1k+1k+1 个频率信道来无干扰地运行。这是一个深刻的结果:DP 通过利用隐藏的结构,将不可能变为可能。

解码生命之书

动态规划在生物学领域的影响无疑是最具变革性的。生物的基因组是由大量字母(A、C、G、T)组成的序列,理解它们是我们时代的一大挑战。

一项基本任务是比较两个基因,比如来自人类和小鼠的基因,以探究它们的亲缘关系。这就是序列比对问题,LCS 的一个近亲。经典的 Needleman-Wunsch 算法是 DP 的纯粹应用,它创建一个二维网格,通过考虑匹配、错配和空位来找到最优的比对分数。这个框架的美妙之处在于其适应性。如果一台 DNA 测序仪无法识别某个碱基,而报告了一个“通配符”字符 N,我们该如何比对它?DP 的公式能够优雅地处理这种情况。我们不需要新算法;我们只需修改替换计分函数,来定义将‘N’与‘A’、‘N’与‘G’或‘N’与另一个‘N’对齐意味着什么。DP 引擎则完全不受干扰地继续运行,使用新规则找到最优解。这种鲁棒性使其成为如此强大的科学工具。

但生命并非仅仅是一维序列。分子会折叠成复杂的三维机器。一个 RNA 分子,一条单链核苷酸,会自我折叠,形成对其功能至关重要的茎环二级结构。我们如何仅从其序列预测这种结构?可能的折叠方式数量是天文数字。但如果我们禁止“假结”(一种复杂的交叉相互作用),问题就变得可以用 DP 解决了。该算法,采用 Nussinov 的风格,对 RNA 的任何片段提问:“最好的可能结构是什么?”答案是四种可能性中的最大值:第一个碱基未配对,最后一个碱基未配对,第一个和最后一个碱基配对(形成一个新的茎),或者该片段分裂成两个独立的子结构。这正是我们之前见过的逻辑,现在应用于分子折叠!这使我们能够预测 RNA 分子的形状,从而预测其功能。我们甚至可以整合实验证据,例如,通过强制一个已知的茎环成为结构的一部分,DP 机器会无缝适应,解决剩余未约束区域的最佳结构问题。

ഇതിനുള്ള ഏറ്റവും വലിയ വേദി താരതമ്യ ജനിതകശാസ്ത്രത്തിലാണ്. ഒരു പ്രത്യേക ജീനിന്റെ ആർ‌എൻ‌എ സീക്വൻസുകൾ പലതരം ജീവികളിൽ നിന്ന് യോജിപ്പിച്ചാൽ, നമുക്ക് ഒരു സംരക്ഷിത ഘടനയ്ക്കായി തിരയാൻ കഴിയും. ഒരു കാണ്ഡത്തിൽ G-C യിൽ നിന്ന് A-C യിലേക്ക് ഒരു ക്രമരഹിതമായ മ്യൂട്ടേഷൻ ഘടനയെ തകർക്കാൻ സാധ്യതയുണ്ട്. എന്നാൽ A-U ലേക്ക് ഒരു നഷ്ടപരിഹാര മ്യൂട്ടേഷൻ കാണ്ഡത്തെ സംരക്ഷിക്കുന്നു. പരിണാമ ചരിത്രത്തിലുടനീളം അത്തരം ഏകോപിത മാറ്റങ്ങൾ കാണുന്നത് ഒരു പ്രവർത്തനക്ഷമമായ ആർ‌എൻ‌എ ഘടനയുടെ വ്യക്തമായ തെളിവാണ്. ഒരു സങ്കീർണ്ണമായ ഡിപി അൽഗോരിതം ഒരു മൾട്ടിപ്പിൾ സീക്വൻസ് അലൈൻമെന്റിന് സ്കോർ നൽകാൻ രൂപകൽപ്പന ചെയ്യാൻ കഴിയും, ഇത് സംരക്ഷിത ബേസ് ജോഡികൾക്ക് മാത്രമല്ല, പ്രത്യേകിച്ചും ഈ കോവറിംഗ് ജോഡികൾക്കും ബോണസ് പോയിന്റുകൾ നൽകുന്നു. ഈ പരിണാമ-അവബോധമുള്ള സ്കോർ പരമാവധിയാക്കുന്ന ഘടന കണ്ടെത്തുന്നതിലൂടെ, ദശലക്ഷക്കണക്കിന് വർഷങ്ങളായി സംരക്ഷിക്കപ്പെട്ട പ്രവർത്തനക്ഷമമായ ആർ‌എൻ‌എ ഘടകങ്ങളെ നമുക്ക് തിരിച്ചറിയാൻ കഴിയും, അവ കാഴ്ചയിൽ മറഞ്ഞിരിക്കുന്ന ജീവന്റെ യന്ത്രങ്ങളാണ്.

为复杂系统建模

最后,动态规划帮助我们理解和控制整个系统。考虑一个相互调节彼此活动的基因网络。一些基因是激活剂,一些是抑制剂。一个基因可能只有在收到来自(比如说)至少两个其调控者的信号时才会开启。这就形成了一个复杂的动态系统。

现在,假设我们希望一组特定的“目标”基因变得活跃,也许是为了对抗一种疾病。我们不能直接将它们全部打开。我们只能对少数“种子”基因提供外部刺激。我们需要激活的最小种子基因集合是什么,才能触发一个级联反应,最终激活我们的整个目标集合?对于少量基因,我们可以使用一种类似 DP 的方法,在所有可能的种子集上进行搜索,这些种子集由位掩码表示。对于具有特殊结构(如树)的系统,我们可以再次使用一个高效的基于树的动态规划算法。这种方法,即为复杂网络寻找一个最小的“控制核”(control kernel),其应用远超生物学,延伸至流行病学(如何以最小的隔离措施阻止一场大流行)和经济学(如何以最小的干预措施创造期望的市场效应)等领域。

从整数分拆的抽象世界到折叠分子的具体任务,主旋律始终如一。识别结构。分解问题。先解决最小的部分并存储它们的解。在此基础上构建,解决越来越大的部分,直到整个谜题完成。这是一个惊人的例子,说明了一个单一、优美的计算思想如何能够为思考一个广阔而多样的宇宙问题提供统一的方式。