Python编程:文件处理与递归算法实践
1. 文件处理相关练习
1.1 一致行长度格式化
在终端窗口中显示文本时,不同的窗口宽度可能会导致文本显示效果不佳。当文本行过长时会自动换行,影响阅读;过短时又不能充分利用空间。为了解决这个问题,我们需要编写一个程序,该程序可以打开文件并将其内容进行格式化,使得每行尽可能地填满。
例如,对于包含如下内容的文件:
Alice was
beginning to get very tired of sitting by her
sister
on the bank, and of having nothing to do: once
or twice she had peeped into the book her sister
was reading, but it had
no
pictures or conversations in it,"and what is
the use of a book," thought Alice, "without
pictures or conversations?"
当设置行长度为 50 个字符时,输出应该如下:
Alice was beginning to get very tired of sitting
by her sister on the bank, and of having nothing
to do: once or twice she had peeped into the book
her sister was reading, but it had no pictures or
conversations in it, "and what is the use of a
book," thought Alice, "without pictures or
conversations?"
操作步骤如下:
1. 打开文件,逐行读取内容。
2. 对于每行内容,将其拆分为单词。
3. 尝试将单词添加到当前行,直到达到最大行长度。
4. 如果当前行已满,开始新的一行,并继续添加剩余的单词。
5. 注意处理文件中可能存在的多个段落,通过检测空行来区分段落。
1.2 查找包含特定元音顺序的单词
英语中存在至少一个单词,它恰好按顺序包含元音 A、E、I、O、U 和 Y 各一次。我们需要编写一个程序,该程序可以搜索包含单词列表的文件,并显示所有满足此条件的单词。
操作步骤如下:
1. 获取用户输入的文件名。
2. 尝试打开文件,如果文件不存在或无法打开,显示相应的错误信息并退出程序。
3. 逐行读取文件中的单词。
4. 检查每个单词是否包含元音 A、E、I、O、U 和 Y 各一次且按顺序排列。
5. 如果满足条件,则显示该单词。
2. 递归算法基础
递归是一种强大的编程技术,它允许函数调用自身来解决问题。一个递归定义必须以一个不同(通常是更小或更简单)的版本来描述被定义的事物,并且要朝着已知解决方案的问题版本前进。
递归函数通常包含至少一个基本情况(base case)和一个或多个递归情况(recursive case)。基本情况是函数可以直接返回结果而无需再次调用自身的情况;递归情况则是函数调用自身来解决更小版本问题的情况。
2.1 整数求和
计算从 0 到某个正整数 n 的所有整数之和可以使用循环、公式或递归方法。下面是使用递归方法实现的代码:
# Compute the sum of the integers from 0 up to and including n using recursion
# @param n the maximum value to include in the sum
# @return the sum of the integers from 0 up to and including n
def sum_to(n):
if n <= 0:
return 0 # Base case
else:
return n + sum_to(n - 1) # Recursive case
# Compute the sum of the integers from 0 up to and including a value entered by the user
num = int(input("Enter a non-negative integer: "))
total = sum_to(num)
print("The total of the integers from 0 up to and including", num, "is", total)
当用户输入 2 时,程序的执行过程如下:
1.
sum_to(2)
被调用,由于
n
大于 0,执行递归情况,调用
sum_to(1)
。
2.
sum_to(1)
被调用,同样由于
n
大于 0,再次执行递归情况,调用
sum_to(0)
。
3.
sum_to(0)
被调用,此时满足基本情况,直接返回 0。
4.
sum_to(1)
接收到
sum_to(0)
返回的 0,将 1 加上 0 后返回 1。
5.
sum_to(2)
接收到
sum_to(1)
返回的 1,将 2 加上 1 后返回 3。
6. 最终输出结果为 3。
2.2 斐波那契数列
斐波那契数列是一个整数序列,它以 0 和 1 开始,后续的每个数都是前两个数的和。前 10 个斐波那契数是 0、1、1、2、3、5、8、13、21 和 34。
下面是使用递归方法计算斐波那契数列的代码:
# Compute the nth Fibonacci number using recursion
# @param n the index of the Fibonacci number to compute
# @return the nth Fibonacci number
def fib(n):
# Base cases
if n == 0:
return 0
if n == 1:
return 1
# Recursive case
return fib(n - 1) + fib(n - 2)
# Compute the Fibonacci number requested by the user
n = int(input("Enter a non-negative integer: "))
print("fib(%d) is %d." % (n, fib(n)))
这个递归算法虽然简洁,但效率较低。例如,计算
fib(35)
在现代计算机上可以很快返回结果,但计算
fib(70)
可能需要数年时间。因此,对于较大的斐波那契数,通常使用循环或公式来计算。
2.3 字符计数
递归不仅可以用于处理整数问题,还可以用于处理字符串问题。例如,计算一个字符串中特定字符的出现次数。
下面是使用递归方法实现字符计数的代码:
# Count the number of times a particular character is present in a string
# @param s the string in which the characters are counted
# @param ch the character to count
# @return the number of occurrences of ch in s
def count(s, ch):
if s == "":
return 0 # Base case
# Compute the tail of s
tail = s[1: len(s)]
# Recursive cases
if ch == s[0]:
return 1 + count(tail, ch)
else:
return count(tail, ch)
# Count the number of times a character entered by the user occurs in a string entered
# by the user
s = input("Enter a string: ")
ch = input("Enter the character to count: ")
print("'%s' occurs %d times in '%s'" % (ch, count(s, ch), s))
操作步骤如下:
1. 定义基本情况:如果字符串为空,返回 0。
2. 计算字符串的尾部(除第一个字符外的所有字符)。
3. 如果第一个字符等于要计数的字符,则返回 1 加上尾部字符串中该字符的出现次数。
4. 否则,返回尾部字符串中该字符的出现次数。
2.4 递归算法复杂度对比
| 函数 | 输入增加 1 时函数调用次数增长情况 | 复杂度类型 |
|---|---|---|
sum_to
| 函数调用次数增加 1 | 线性增长 |
fib
| 函数调用次数(几乎)翻倍 | 指数增长 |
从复杂度对比可以看出,不同的递归算法在效率上可能存在很大差异。虽然递归在某些情况下可能效率较低,但在其他情况下,如欧几里得算法计算最大公约数,递归算法可以非常高效。
下面是
sum_to
和
fib
函数调用次数增长情况的 mermaid 流程图:
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;
A([开始]):::startend --> B(输入 n):::process
B --> C{选择函数}:::decision
C -->|sum_to| D(计算 sum_to(n)):::process
C -->|fib| E(计算 fib(n)):::process
D --> F(函数调用次数线性增长):::process
E --> G(函数调用次数指数增长):::process
F --> H([结束]):::startend
G --> H
3. 递归算法相关练习
3.1 数值求和
编写一个程序,从用户那里读取数值,直到用户输入一个空行为止,然后显示所有输入数值的总和(如果第一个输入就是空行,则总和为 0.0)。要求使用递归方法完成此任务,程序中不能使用任何循环。
操作步骤如下:
1. 定义一个递归函数,函数无需参数,但需要返回一个数值结果。
2. 在函数内部读取用户输入的一个值。
3. 判断输入是否为空行:
- 如果是,则返回 0.0。
- 如果不是,则将该值转换为数值类型,并加上递归调用该函数的结果。
以下是实现代码:
def total_values():
value = input("Enter a value (blank line to quit): ")
if value == "":
return 0.0
else:
return float(value) + total_values()
result = total_values()
print("The total of all values entered is", result)
3.2 最大公约数计算
欧几里得算法是一种高效的递归算法,用于计算两个正整数 a 和 b 的最大公约数。其算法步骤如下:
1. 如果 b 为 0,则返回 a。
2. 否则,计算 a 除以 b 的余数 c。
3. 返回 b 和 c 的最大公约数。
以下是实现代码:
def gcd(a, b):
if b == 0:
return a
else:
c = a % b
return gcd(b, c)
num1 = int(input("Enter the first positive integer: "))
num2 = int(input("Enter the second positive integer: "))
print("The greatest common divisor of", num1, "and", num2, "is", gcd(num1, num2))
3.3 十进制转二进制
将非负十进制数转换为二进制数可以使用递归方法。操作步骤如下:
1. 定义基本情况:当 n 为 0 或 1 时,直接返回对应的字符串 “0” 或 “1”。
2. 对于其他正整数 n,计算 n 除以 2 的余数作为下一位二进制数。
3. 递归调用函数计算 n // 2 的二进制表示。
4. 将递归调用的结果和当前余数拼接起来并返回。
以下是实现代码:
def decimal_to_binary(n):
if n == 0:
return "0"
elif n == 1:
return "1"
else:
remainder = str(n % 2)
return decimal_to_binary(n // 2) + remainder
num = int(input("Enter a non - negative integer: "))
if num < 0:
print("Error: Please enter a non - negative integer.")
else:
print("The binary representation of", num, "is", decimal_to_binary(num))
3.4 北约音标字母表转换
北约音标字母表是一种广泛使用的拼写字母表,每个字母对应一个特定的单词。编写一个程序,读取用户输入的单词,并显示其北约音标拼写。要求使用递归方法,程序中不能使用任何循环。
操作步骤如下:
1. 定义一个字典,存储字母和对应的北约音标单词。
2. 定义递归函数,接收一个字符串作为参数。
3. 如果字符串为空,返回空字符串。
4. 否则,获取字符串的第一个字符,查找其对应的北约音标单词。
5. 递归调用函数处理字符串的剩余部分,并将结果和当前单词拼接起来返回。
以下是实现代码:
nato_alphabet = {
'A': 'Alpha', 'B': 'Bravo', 'C': 'Charlie', 'D': 'Delta', 'E': 'Echo',
'F': 'Foxtrot', 'G': 'Golf', 'H': 'Hotel', 'I': 'India', 'J': 'Juliet',
'K': 'Kilo', 'L': 'Lima', 'M': 'Mike', 'N': 'November', 'O': 'Oscar',
'P': 'Papa', 'Q': 'Quebec', 'R': 'Romeo', 'S': 'Sierra', 'T': 'Tango',
'U': 'Uniform', 'V': 'Victor', 'W': 'Whiskey', 'X': 'Xray', 'Y': 'Yankee',
'Z': 'Zulu'
}
def nato_spelling(word):
word = word.upper()
if word == "":
return ""
first_char = word[0]
if first_char in nato_alphabet:
return nato_alphabet[first_char] + " " + nato_spelling(word[1:])
else:
return nato_spelling(word[1:])
user_word = input("Enter a word: ")
print(nato_spelling(user_word).strip())
4. 总结
递归是一种强大的编程技术,在解决某些问题时非常有效。通过本文介绍的多个示例,我们可以看到递归在不同场景下的应用,包括整数求和、斐波那契数列计算、字符计数等。同时,我们也了解到不同递归算法在效率上可能存在巨大差异,如
sum_to
函数是线性增长,而
fib
函数是指数增长。
在实际编程中,我们需要根据具体问题选择合适的算法。对于一些问题,递归可以提供简洁的解决方案;而对于另一些问题,可能需要使用循环或其他方法来提高效率。
以下是一个简单的总结表格,对比不同递归练习的特点:
| 练习名称 | 功能 | 复杂度 | 注意事项 |
| ---- | ---- | ---- | ---- |
| 数值求和 | 计算用户输入数值的总和 | 线性 | 不能使用循环,需处理空行输入 |
| 最大公约数计算 | 计算两个正整数的最大公约数 | 高效 | 利用欧几里得算法的递归特性 |
| 十进制转二进制 | 将非负十进制数转换为二进制数 | 线性 | 处理 0 和 1 的特殊情况 |
| 北约音标字母表转换 | 将单词转换为北约音标拼写 | 线性 | 忽略非字母字符,使用递归处理字符串 |
下面是一个 mermaid 流程图,展示递归算法选择的基本思路:
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;
A([开始]):::startend --> B(明确问题):::process
B --> C{问题是否可分解为子问题}:::decision
C -->|是| D{子问题是否与原问题相似}:::decision
C -->|否| E(考虑其他方法):::process
D -->|是| F{是否有基本情况}:::decision
D -->|否| E
F -->|是| G(使用递归解决):::process
F -->|否| E
G --> H([结束]):::startend
E --> H
超级会员免费看
1334

被折叠的 条评论
为什么被折叠?



