21、递归与格式化:编程中的重要概念

递归与格式化:编程中的重要概念

递归的概念与应用

递归是一种以自身来定义对象或过程的方法。就像一个孩子睡不着,妈妈给她讲一只小青蛙睡不着的故事,小青蛙的妈妈又给它讲小熊睡不着的故事,小熊的妈妈再给它讲小鼬鼠的故事,直到小鼬鼠睡着,然后小熊、小青蛙、孩子依次睡着。

递归在很多方面都有应用:
- 数学模式 :阶乘和斐波那契数列等数学模式是递归的。例如阶乘,(n!) 当 (n = 0) 时为 (1),当 (n > 0) 时为 (n\times(n - 1)!)。
- 文档结构 :网页等文档具有自然的递归性。一个网页可以包含在一个盒子中,这个盒子又可以包含另一个网页,如此递归下去。
- 分形图像 :分形图像的工作原理也是递归的。

递归函数常用于高级搜索和排序算法中。即使不是程序员,理解递归系统的概念也很重要。比如在设计食谱的网页程序时,熟悉递归的人会考虑到每个食材本身也可以是包含其他食材的食谱,这样的系统会更强大。

递归的编码实现

在编程中,函数可以调用其他函数,也可以调用自身,这就是递归。例如:

def f():
    print("Hello")
    f()

f()

这个例子会不断打印 “Hello” 并调用 f() 函数,直到计算机耗尽栈空间,Python 会输出错误 “RuntimeError: maximum recursion depth exceeded”。

为了控制递归深度,需要有方法防止函数无限调用自身。以下是一个控制递归深度的例子:

def f(level):
    print("Recursion call, level", level)
    if level < 10:
        f(level + 1)

f(1)

这个函数会打印递归调用的层级,当达到 10 层时停止调用。

递归阶乘计算

计算阶乘是递归的经典应用。以下是使用递归和非递归方法计算阶乘的代码:

# 非递归方法计算阶乘
def factorial_nonrecursive(n):
    answer = 1
    for i in range(2, n + 1):
        print(i, "*", answer, "=", i * answer)
        answer = answer * i
    return answer

print("I can calculate a factorial!")
user_input = input("Enter a number:")
n = int(user_input)
answer = factorial_nonrecursive(n)
print(answer)

# 递归方法计算阶乘
def factorial_recursive(n):
    if n == 1:
        return n
    else:
        x = factorial_recursive(n - 1)
        print(n, "*", x, "=", n * x)
        return n * x

print("I can calculate a factorial!")
user_input = input("Enter a number:")
n = int(user_input)
answer = factorial_recursive(n)
print(answer)
递归矩形绘制

pygame 程序中,也可以看到递归的应用。以下是一个递归绘制矩形的例子:

import pygame

BLACK = (0, 0, 0)
WHITE = (255, 255, 255)

def recursive_draw(x, y, width, height):
    pygame.draw.rect(screen, BLACK, [x, y, width, height], 1)
    if width > 14:
        x += width * .1
        y += height * .1
        width *= .8
        height *= .8
        recursive_draw(x, y, width, height)

pygame.init()
size = [700, 500]
screen = pygame.display.set_mode(size)
pygame.display.set_caption("My Game")
done = False
clock = pygame.time.Clock()

while not done:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            done = True
    screen.fill(WHITE)
    recursive_draw(0, 0, 700, 500)
    pygame.display.flip()
    clock.tick(60)

pygame.quit()

这个程序会递归地在一个大矩形内绘制逐渐变小的矩形,每个矩形比其父矩形小 20%。

递归分形绘制

分形也可以通过递归实现。以下是一个简单分形的绘制代码:

import pygame

black = (0, 0, 0)
white = (255, 255, 255)
green = (0, 255, 0)
red = (255, 0, 0)

def recursive_draw(x, y, width, height, count):
    pygame.draw.line(screen, black, [x + width*.25, height // 2 + y], [x + width*.75, height // 2 + y], 3)
    pygame.draw.line(screen, black, [x + width * .25, (height * .5) // 2 + y], [x + width * .25,  (height * 1.5) // 2 + y], 3)
    pygame.draw.line(screen, black, [x + width * .75, (height * .5) // 2 + y], [x + width * .75, (height * 1.5) // 2 + y], 3)
    if count > 0:
        count -= 1
        recursive_draw(x, y, width // 2, height // 2, count)
        recursive_draw(x + width // 2, y, width // 2, height // 2, count)
        recursive_draw(x, y + width // 2, width // 2, height // 2, count)
        recursive_draw(x + width // 2, y + width // 2, width // 2, height // 2, count)

pygame.init()
size = [700, 700]
screen = pygame.display.set_mode(size)
pygame.display.set_caption("My Game")
done = False
clock = pygame.time.Clock()

while not done:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            done = True
    screen.fill(white)
    fractal_level = 3
    recursive_draw(0, 0, 700, 700, fractal_level)
    pygame.display.flip()
    clock.tick(20)

pygame.quit()
递归二分查找

递归也可以用于二分查找。以下是非递归和递归的二分查找代码:

# 非递归二分查找
def binary_search_nonrecursive(search_list, key):
    lower_bound = 0
    upper_bound = len(search_list) - 1
    found = False
    while lower_bound < upper_bound and found == False:
        middle_pos = (lower_bound + upper_bound) // 2
        if search_list[middle_pos] < key:
            lower_bound = middle_pos + 1
        elif search_list[middle_pos] > key:
            upper_bound = middle_pos
        else:
            found = True
    if found:
        print("The name is at position", middle_pos)
    else:
        print("The name was not in the list.")

# 递归二分查找
def binary_search_recursive(search_list, key, lower_bound, upper_bound):
    middle_pos = (lower_bound + upper_bound) // 2
    if search_list[middle_pos] < key:
        binary_search_recursive(search_list, key, middle_pos + 1, upper_bound)
    elif search_list[middle_pos] > key:
        binary_search_recursive(search_list, key, lower_bound, middle_pos)
    else:
        print("Found at position", middle_pos)
格式化的基本概念

在编程中,格式化可以让输出的文本更加美观和易读。以下是一个文本格式化的参考表格:
| 数字 | 格式 | 输出 | 描述 |
| ---- | ---- | ---- | ---- |
| 3.1415926 | {:.2f} | 3.14 | 保留两位小数 |
| 3.1415926 | {:+.2f} | +3.14 | 保留两位小数并显示符号 |
| -1 | {:+.2f} | -1.00 | 保留两位小数并显示符号 |
| 3.1415926 | {:.0f} | 3 | 不保留小数(四舍五入) |
| 5 | {:0>2d} | 05 | 左侧用零填充 |
| 1000000 | {:,} | 1,000,000 | 用逗号分隔数字 |
| 0.25 | {:.2%} | 25.00% | 格式化为百分比 |
| 1000000000 | {:.2e} | 1.00e+09 | 科学计数法 |
| 11 | {:>10d} | 11 | 右对齐 |
| 11 | {:<10d} | 11 | 左对齐 |
| 11 | {:^10d} | 11 | 居中对齐 |

十进制数字的格式化

以下是一个打印随机数字的程序,未格式化时输出很不美观:

import random

for i in range(10):
    x = random.randrange(20)
    print(x)

使用字符串格式化可以让数字右对齐:

import random

for i in range(10):
    x = random.randrange(20)
    print("{:2}".format(x))

如果要处理大数字并添加逗号分隔符,可以这样做:

import random

for i in range(10):
    x = random.randrange(100000)
    print("{:6,}".format(x))
字符串的格式化

对于字符串的格式化,以下是一个例子:

my_fruit = ["Apples", "Oranges", "Grapes", "Pears"]
my_calories = [4, 300, 70, 30]

for i in range(4):
    print("{:7} are {:3} calories.".format(my_fruit[i], my_calories[i]))

这个例子将水果名称和卡路里数进行了格式化输出。如果要改变对齐方式,可以使用 < > 字符:

my_fruit = ["Apples", "Oranges", "Grapes", "Pears"]
my_calories = [4, 300, 70, 30]

for i in range(4):
    print("{:>7} are {:<3} calories.".format(my_fruit[i], my_calories[i]))
前导零的使用

在显示时钟时间时,需要使用前导零。以下是未使用前导零和使用前导零的对比:

# 未使用前导零
for hours in range(1, 13):
    for minutes in range(0, 60):
        print("Time {}:{}".format(hours, minutes))

# 使用前导零
for hours in range(1, 13):
    for minutes in range(0, 60):
        print("Time {:02}:{:02}".format(hours, minutes))
浮点数的格式化

对于浮点数的格式化,可以控制精度和小数位数。以下是一个例子:

x = 0.1
y = 123.456789

print("{:.1}  {:.1}".format(x, y))
print("{:.2}  {:.2}".format(x, y))
print("{:.3}  {:.3}".format(x, y))
print("{:.4}  {:.4}".format(x, y))
print("{:.5}  {:.5}".format(x, y))
print("{:.6}  {:.6}".format(x, y))
print()
print("{:.1f}  {:.1f}".format(x, y))
print("{:.2f}  {:.2f}".format(x, y))
print("{:.3f}  {:.3f}".format(x, y))
print("{:.4f}  {:.4f}".format(x, y))
print("{:.5f}  {:.5f}".format(x, y))
print("{:.6f}  {:.6f}".format(x, y))

.2 表示显示两位精度的数字, .2f 表示显示两位小数。还可以指定字段宽度:

x = 0.1
y = 123.456789

print("'{:10.1}'  '{:10.1}'".format(x, y))
print("'{:10.2}'  '{:10.2}'".format(x, y))
print("'{:10.3}'  '{:10.3}'".format(x, y))
print("'{:10.4}'  '{:10.4}'".format(x, y))
print("'{:10.5}'  '{:10.5}'".format(x, y))
print("'{:10.6}'  '{:10.6}'".format(x, y))
print()
print("'{:10.1f}'  '{:10.1f}'".format(x, y))
print("'{:10.2f}'  '{:10.2f}'".format(x, y))
print("'{:10.3f}'  '{:10.3f}'".format(x, y))
print("'{:10.4f}'  '{:10.4f}'".format(x, y))
print("'{:10.5f}'  '{:10.5f}'".format(x, y))
print("'{:10.6f}'  '{:10.6f}'".format(x, y))

通过递归和格式化的学习,我们可以更好地处理复杂的编程问题和优化输出结果。希望这些内容能帮助你在编程之路上更进一步。

递归与格式化:编程中的重要概念

格式化的高级应用

在实际编程中,格式化的应用场景丰富多样,除了前面提到的基本用法,还有一些高级技巧值得掌握。

动态格式化

有时候,我们需要根据程序运行时的情况动态地确定格式化的方式。例如,根据用户输入的精度要求来格式化浮点数。以下是一个示例代码:

x = 3.1415926
precision = int(input("请输入要保留的小数位数: "))
format_str = "{:." + str(precision) + "f}"
print(format_str.format(x))

在这个例子中,用户输入一个整数作为小数位数,程序根据这个输入动态生成格式化字符串,然后对浮点数 x 进行格式化输出。

格式化多个变量

我们可以在一个字符串中同时格式化多个变量,并且可以指定它们的顺序和格式。例如:

name = "Alice"
age = 25
height = 1.65
print("姓名: {0}, 年龄: {1:d}, 身高: {2:.2f} 米".format(name, age, height))

这里使用了索引 {0} {1} {2} 来指定变量的顺序,同时对年龄使用了整数格式 {1:d} ,对身高使用了保留两位小数的格式 {2:.2f}

递归与格式化的综合应用

递归和格式化可以结合起来解决一些复杂的问题。例如,在递归生成树形结构的数据时,我们可以使用格式化来美化输出。以下是一个简单的递归生成树形结构并格式化输出的示例:

class TreeNode:
    def __init__(self, value, children=None):
        self.value = value
        self.children = children if children is not None else []

def print_tree(node, level=0):
    indent = "  " * level
    print("{}{}".format(indent, node.value))
    for child in node.children:
        print_tree(child, level + 1)

# 构建一个简单的树
root = TreeNode(1, [
    TreeNode(2, [TreeNode(4), TreeNode(5)]),
    TreeNode(3)
])

print_tree(root)

在这个例子中, TreeNode 类表示树的节点, print_tree 函数是递归函数,它根据节点的层级使用空格进行缩进,从而实现树形结构的格式化输出。

递归与格式化的性能考虑

虽然递归和格式化在编程中非常有用,但在使用时也需要考虑性能问题。

递归的性能问题

递归函数在调用时会不断地在栈上创建新的函数调用帧,如果递归深度过大,会导致栈溢出错误。例如,前面提到的无限递归的例子:

def f():
    print("Hello")
    f()

f()

为了避免这种情况,我们需要控制递归深度,就像前面介绍的控制递归深度的例子一样。另外,递归函数可能会有重复计算的问题,例如在计算斐波那契数列时:

def fibonacci(n):
    if n <= 1:
        return n
    else:
        return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(10))

在计算 fibonacci(10) 时,会多次重复计算 fibonacci(3) fibonacci(4) 等,导致性能下降。可以使用迭代或记忆化搜索等方法来优化。

格式化的性能问题

格式化字符串也会有一定的性能开销,尤其是在大量数据需要格式化时。例如,在循环中频繁使用格式化字符串:

for i in range(1000):
    print("{:03d}".format(i))

可以考虑使用更高效的方法,如预先计算格式化字符串或使用 f-string (Python 3.6 及以上版本支持):

for i in range(1000):
    print(f"{i:03d}")

f-string 是一种更简洁和高效的字符串格式化方式,它在运行时直接替换变量的值,避免了 format 方法的一些开销。

总结

递归和格式化是编程中非常重要的概念,它们在不同的场景中都有广泛的应用。递归可以帮助我们解决一些具有自相似性的问题,如分形绘制、树形结构处理等;格式化可以让我们的输出更加美观和易读,提高程序的用户体验。

在使用递归时,要注意控制递归深度,避免栈溢出和重复计算;在使用格式化时,要根据具体情况选择合适的格式化方法,考虑性能因素。通过合理运用递归和格式化,我们可以编写出更加高效、简洁和易读的代码。

以下是一个总结递归和格式化关键要点的表格:
| 概念 | 要点 | 示例代码 |
| ---- | ---- | ---- |
| 递归 | 函数调用自身,需控制递归深度 | python def f(level): if level < 10: f(level + 1) f(1) |
| 格式化 | 美化输出,支持多种格式 | python x = 3.14; print("{:.2f}".format(x)) |
| 递归与格式化结合 | 解决复杂问题,如树形结构输出 | python class TreeNode: ... def print_tree(node, level=0): ... |
| 性能考虑 | 递归注意栈溢出和重复计算,格式化选择高效方法 | 递归优化:记忆化搜索;格式化:使用 f-string |

mermaid 格式流程图展示递归函数的执行流程:

graph TD;
    A[开始] --> B{递归条件是否满足};
    B -- 是 --> C[执行递归操作];
    C --> B;
    B -- 否 --> D[结束];

通过对递归和格式化的深入学习和实践,我们可以提升自己的编程能力,更好地应对各种编程挑战。希望这篇文章能对你有所帮助,让你在编程的道路上不断进步。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值