编程的本质

编程表达式

算法 + 数据结构 = 程序Algorithms + Data Structures = Programs1976年,瑞士计算机科学家,Algol W、Modula、Oberon 和 Niklaus Emil Wirth提出
算法 = 逻辑 + 控制Algorithm = Logic + Control1979年,英国逻辑学家和计算机科学家 Robert Kowalski 提出
程序 = 逻辑 + 控制 + 数据结构Program = Logic + Control + Data Structrue

编程的本质是由逻辑、控制和数据结构构成的,其中逻辑是关键。逻辑是业务逻辑和逻辑过程的抽象,再加上一个由术语表示的数据结构的定义。

graph TD; A[Algorithm] --> B[Logic]; A --> C[Control]; B --> 逻辑过程抽象; B --> 由术语表示的数据结构的定义; C --> 程序流转的方式自顶向下或自底向上; C --> 执行时的策略并行或串行; C --> 调度不同的执行路径或模块; C --> 数据间的存储关系;


混乱不堪的代码,通常是由于 Logic 和 Control 纠缠在一起导致的。不仅难以维护,而且 Bug 很多。

代码的复杂度取决于业务逻辑的复杂度和控制逻辑的复杂度,如果业务逻辑和控制逻辑耦合,那么程序就会很复杂,需花费大量的时间去维护和调试代码。

例子

举例说明:程序=逻辑+控制+数据结构,比如树的层级遍历的递归和非递归实现。

ChatGPT

树的层级遍历是一个经典的问题,能够很好地展示“程序 = 逻辑 + 控制 + 数据结构”的概念。接下来我将通过树的层级遍历的递归和非递归实现,来详细解释这个公式。

在树的层级遍历中,我们需要逐层访问树的节点,首先访问根节点,然后访问根节点的子节点,再访问子节点的子节点,依此类推。

1. 递归实现树的层级遍历

递归方法本质上通过分而治之的思想,将树的层级遍历问题分解为多个子问题。

# 假设有一个TreeNode类定义如下:
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

def level_order_recursive(root):
    levels = []
    if not root:
        return levels

    def traverse(node, level):
        if len(levels) == level:
            levels.append([])

        levels[level].append(node.val)

        if node.left:
            traverse(node.left, level + 1)
        if node.right:
            traverse(node.right, level + 1)

    traverse(root, 0)
    return levels

程序 = 逻辑 + 控制 + 数据结构

  1. 逻辑
    • 递归的方法遵循了树的层级遍历的本质:逐层访问节点,依次从左到右处理每一层的节点。
  2. 控制
    • 递归控制树的遍历,traverse 函数不断调用自身来处理每一层的节点,且递归深度等于树的高度。
    • 控制流程通过if判断决定如何深入到子节点并在合适时刻停止递归。
  3. 数据结构
    • levels 是一个二维列表,每一层的数据存储在不同的子列表中。
    • 树的每一个节点通过 TreeNode 对象表示,包含 val, left, right 属性。
    • 函数调用栈也隐式地扮演了数据结构的角色,递归调用的层次决定了节点的层级关系。

2. 非递归实现树的层级遍历

非递归方法通常使用队列来实现层级遍历。这种方法依赖于显式的数据结构(队列)来保存当前层级的节点。

from collections import deque

def level_order_iterative(root):
    levels = []
    if not root:
        return levels

    queue = deque([root])

    while queue:
        level_size = len(queue)
        level = []
        for _ in range(level_size):
            node = queue.popleft()
            level.append(node.val)
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)
        levels.append(level)

    return levels

程序 = 逻辑 + 控制 + 数据结构

  1. 逻辑
    • 和递归一样,非递归方法的逻辑也是依照层级遍历的定义:逐层访问树的节点。
    • 每次从队列中取出一层的节点,然后处理它们的左右子节点,将它们加入下一层的队列中。
  2. 控制
    • 控制结构通过 while 循环来遍历树的每一层,并通过 for 循环处理每层的节点。
    • 控制的核心是维护一个队列,队列保证了节点按层级顺序处理。queue.popleft() 提取当前节点,queue.append() 将子节点按顺序加入队列。
  3. 数据结构
    • 使用了一个队列 (deque),来保存当前层级的节点,并逐层处理。
    • levels 列表存储了每一层的结果,队列保证了层级的顺序。
    • 树的节点仍然是 TreeNode 对象,保存节点的值以及它的左右子节点。

对比总结

  • 逻辑
    • 递归和非递归方法的逻辑都是基于层级遍历的定义,依次访问每一层的节点。
  • 控制
    • 递归方法通过函数调用栈隐式地控制遍历,调用栈深度等于树的高度。
    • 非递归方法使用 whilefor 循环,配合队列显式控制遍历的顺序。
  • 数据结构
    • 递归方法隐式依赖于函数调用栈,以及 levels 列表来保存结果。
    • 非递归方法使用 deque 队列来保存当前层的节点,并按层次进行遍历。

这两种实现都展示了程序中逻辑、控制、数据结构的不同组合,以及如何根据具体问题选择适合的实现方式。

文档信息

Search

    Table of Contents