第4章 复合数据类型
4.1 列表(list)
列表是Python中最常用的可变序列,用方括号 [] 表示,元素之间用逗号分隔。
创建列表
python
# 空列表 []
empty_list = []
# 创建列表 [...]
numbers = [1, 2, 3, 4, 5]
# 创建列表 [...]
mixed = [1, "hello", 3.14, True]
# 创建列表 [...]
matrix = [[1, 2], [3, 4], [5, 6]]
列表的索引和切片
python
# 创建列表 [...]
fruits = ["苹果", "香蕉", "橘子", "西瓜", "葡萄"]
print(fruits[0]) # 苹果
print(fruits[2]) # 橘子
print(fruits[-1]) # 葡萄
print(fruits[-2]) # 西瓜
print(fruits[1:4]) # ["香蕉", "橘子", "西瓜"](包含start,不包含stop)
print(fruits[:3]) # ["苹果", "香蕉", "橘子"]
print(fruits[2:]) # ["橘子", "西瓜", "葡萄"]
print(fruits[::2]) # ["苹果", "橘子", "葡萄"](步长为2)
print(fruits[::-1]) # ["葡萄", "西瓜", "橘子", "香蕉", "苹果"](反转)
常用列表方法
| 方法 | 功能 | 示例 |
|---|---|---|
append(x) |
在末尾添加元素 | fruits.append("芒果") |
insert(i, x) |
在指定位置插入元素 | fruits.insert(1, "草莓") |
remove(x) |
删除第一个值为x的元素 | fruits.remove("香蕉") |
pop(i) |
删除并返回指定位置的元素(默认最后一个) | fruits.pop() |
index(x) |
返回第一个值为x的元素的索引 | fruits.index("橘子") |
count(x) |
统计x出现的次数 | fruits.count("苹果") |
sort() |
原地排序(升序) | numbers.sort() |
reverse() |
反转列表 | fruits.reverse() |
len() |
返回列表长度(内置函数) | len(fruits) |
python
# 创建列表 [...]
numbers = [3, 1, 4, 1, 5, 9, 2]
numbers.append(6) # [3, 1, 4, 1, 5, 9, 2, 6]
numbers.sort() # [1, 1, 2, 3, 4, 5, 6, 9]
numbers.pop() # 移除并返回9,列表变为[1, 1, 2, 3, 4, 5, 6]
print(len(numbers)) # 7
4.1.1 列表拷贝:赋值 vs copy() vs deepcopy()
列表拷贝是一个容易踩坑的知识点,务必分清以下三种操作的区别:
1. 直接赋值 = ------ 引用传递(共享同一对象)
python
a = [1, 2, 3]
b = a # b 和 a 指向同一个列表对象
b[0] = 999
print(a) # [999, 2, 3] ← a 也变了!
print(b) # [999, 2, 3]
print(a is b) # True(a 和 b 是同一个对象)
2. copy() ------ 浅拷贝(只复制第一层)
python
a = [1, 2, 3]
b = a.copy() # b 是 a 的浅拷贝,是一个新列表
b[0] = 999
print(a) # [1, 2, 3] ← a 不变
print(b) # [999, 2, 3]
print(a is b) # False(a 和 b 是不同的对象)
但浅拷贝有陷阱! 如果列表中嵌套了子列表(多维列表),浅拷贝只复制外层,内层的子列表仍然是共享的:
python
a = [[1, 2], [3, 4]]
b = a.copy() # 浅拷贝
b[0][0] = 999 # 修改子列表中的元素
print(a) # [[999, 2], [3, 4]] ← a 也被影响了!
为什么? 浅拷贝只复制了"外壳",内部的子列表仍然是同一份引用:
浅拷贝前:
a ──> [ [1,2], [3,4] ]
浅拷贝后:
a ──> [ [1,2], [3,4] ] ← 同一个子列表对象
b ──> [ ↑ , ↑ ] ← b 指向同一个子列表对象
3. deepcopy() ------ 深拷贝(完全独立的副本)
深拷贝会递归地复制所有层级,生成完全独立的副本:
python
import copy
a = [[1, 2], [3, 4]]
b = copy.deepcopy(a) # 深拷贝
b[0][0] = 999
print(a) # [[1, 2], [3, 4]] ← a 不受影响
print(b) # [[999, 2], [3, 4]]
对比总结:
| 方式 | 代码 | 修改 b 影响 a? | 本质 |
|---|---|---|---|
赋值 = |
b = a |
会 | 同一对象,换个名字 |
浅拷贝 copy() |
b = a.copy() |
看情况:一层不改,多层会改 | 新外壳,共用内脏 |
深拷贝 deepcopy() |
b = copy.deepcopy(a) |
不会 | 完全独立,互不影响 |
4.2 元组(tuple)
元组是不可变的序列,用圆括号 () 表示。
python
point = (3, 5)
rgb = (255, 128, 0)
single = (1,) # 单元素元组,必须有逗号
# 空元组 ()
empty_tuple = ()
print(point[0]) # 3
print(point[-1]) # 5
x, y = point
print(x, y) # 3 5
重要:单元素元组必须加逗号!
这是 Python 初学者极易犯的错误。括号
()在 Python 中有双重含义:既可以表示元组,也可以表示数学运算中的优先级。
pythont1 = (5) # 这不是元组!Python 理解为数学括号,t1 是整数 5 t2 = (5,) # 这才是单元素元组,逗号是关键 t3 = 5, # 也可以这样写(元组由逗号定义,括号可省略) print(type(t1)) # <class 'int'> ← 不是元组! print(type(t2)) # <class 'tuple'> ← 是元组 print(type(t3)) # <class 'tuple'> ← 也是元组 print(t2[0]) # 5记忆口诀 :"元组看逗号,不看括号"。创建元组时,逗号才是关键,括号只是辅助。单元素时必须加逗号,否则 Python 会把它当作普通表达式。
元组 vs 列表:
- 列表可变(可增删改),元组不可变(创建后不能修改)
- 列表用
[],元组用()- 元组比列表更安全、更快,适合存储不需要修改的数据
4.3 字典(dict)
字典是键值对(key-value)的集合,用花括号 {} 表示。
python
# 创建字典 {键:值, ...}
student = {"name": "张三", "age": 20, "score": 88}
print(student["name"]) # 张三
print(student.get("age")) # 20(推荐,键不存在返回None,不会报错)
student["score"] = 92 # 修改已有键的值
student["city"] = "北京" # 添加新键值对
del student["age"] # 删除键值对
for key in student: # 遍历键
# 打印输出到屏幕
print(key)
for value in student.values(): # 遍历值
# 打印输出到屏幕
print(value)
for key, value in student.items(): # 遍历键值对
# 打印输出到屏幕
print(f"{key}: {value}")
print(student.keys()) # 获取所有键
print(student.values()) # 获取所有值
print(student.items()) # 获取所有键值对
4.4 集合(set)
集合是无序不重复元素的集合,用花括号 {} 表示。
python
# 创建集合 {...}
numbers = {1, 2, 3, 4, 5}
empty_set = set() # 注意:{} 是空字典,不是空集合
unique = set([1, 2, 2, 3, 3, 3]) # {1, 2, 3}
# 创建集合 {...}
a = {1, 2, 3, 4}
# 创建集合 {...}
b = {3, 4, 5, 6}
print(a | b) # 并集:{1, 2, 3, 4, 5, 6}
print(a & b) # 交集:{3, 4}
print(a - b) # 差集:{1, 2}
print(a ^ b) # 对称差集:{1, 2, 5, 6}
4.5 字符串(复习与进阶)
常用字符串方法
| 方法 | 功能 | 示例 |
|---|---|---|
len(s) |
返回字符串长度 | len("Hello") → 5 |
s.upper() |
转换为大写 | "hello".upper() → "HELLO" |
s.lower() |
转换为小写 | "Hello".lower() → "hello" |
s.strip() |
去除两端空白 | " hello ".strip() → "hello" |
s.split(sep) |
按分隔符分割为列表 | "a,b,c".split(",") → ["a","b","c"] |
s.join(list) |
将列表用指定分隔符连接为字符串 | "-".join(["a","b","c"]) → "a-b-c" |
s.find(sub) |
查找子串,返回索引(找不到返回-1) | "Hello".find("ll") → 2 |
s.replace(old, new) |
替换子串 | "Hello".replace("l", "x") → "Hexxo" |
s.count(sub) |
统计子串出现次数 | "ababa".count("ab") → 2 |
s.startswith(prefix) |
判断是否以指定字符串开头 | "Hello".startswith("He") → True |
s.endswith(suffix) |
判断是否以指定字符串结尾 | "Hello".endswith("lo") → True |
python
# 字符串变量 sentence,存储文本
sentence = "Python is fun"
words = sentence.split() # ["Python", "is", "fun"]
new_sentence = " ".join(words) # "Python is fun"
# 字符串变量 text,存储文本
text = "I love Python"
print(text.find("love")) # 2
print(text.replace("Python", "Java")) # "I love Java"
4.6 序列通用操作
列表、元组、字符串都是序列类型,支持以下通用操作:
| 操作 | 描述 | 示例 |
|---|---|---|
x in s |
判断x是否在序列s中 | 3 in [1,2,3] → True |
x not in s |
判断x是否不在序列s中 | 3 not in [1,2] → True |
s + t |
序列连接 | [1,2] + [3] → [1,2,3] |
s * n |
序列重复 | "Hi" * 3 → "HiHiHi" |
s[i] |
索引访问 | "abc"[1] → "b" |
s[i:j] |
切片 | "abcde"[1:4] → "bcd" |
len(s) |
序列长度 | len([1,2,3]) → 3 |
min(s) |
最小值 | min([3,1,4]) → 1 |
max(s) |
最大值 | max([3,1,4]) → 4 |
sum(s) |
求和(元素必须是数值) | sum([1,2,3]) → 6 |
4.7 列表生成式(推导式)
列表生成式是一种简洁创建列表的语法。
python
# 创建列表 [...]
squares = [x ** 2 for x in range(1, 11)]
print(squares) # [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
# 创建列表 [...]
evens = [x for x in range(1, 21) if x % 2 == 0]
print(evens) # [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
# 创建列表 [...]
pairs = [(x, y) for x in range(1, 4) for y in range(1, 3)]
print(pairs) # [(1,1), (1,2), (2,1), (2,2), (3,1), (3,2)]
# 创建列表 [...]
words = ["hello", "world", "python"]
# 创建列表 [...]
upper_words = [w.upper() for w in words]
print(upper_words) # ["HELLO", "WORLD", "PYTHON"]
列表生成式嵌套写法
列表生成式支持嵌套 for 循环,可以一次性生成二维结构。
经典例子:使用列表生成式嵌套生成九九乘法表
python
# 外层循环 i = 1 到 9,内层循环 j = 1 到 i
multiplication_table = [f"{j}×{i}={i*j}" for i in range(1, 10) for j in range(1, i+1)]
# 打印结果
for item in multiplication_table:
print(item, end="\t")
if item.split("×")[1] == item.split("=")[0]: # 每行最后一个元素换行
print()
输出结果:
1×1=1
1×2=2 2×2=4
1×3=3 2×3=6 3×3=9
1×4=4 2×4=8 3×4=12 4×4=16
1×5=5 2×5=10 3×5=15 4×5=20 5×5=25
1×6=6 2×6=12 3×6=18 4×6=24 5×6=30 6×6=36
1×7=7 2×7=14 3×7=21 4×7=28 5×7=35 6×7=42 7×7=49
1×8=8 2×8=16 3×8=24 4×8=32 5×8=40 6×8=48 7×8=56 8×8=64
1×9=9 2×9=18 3×9=27 4×9=36 5×9=45 6×9=54 7×9=63 8×9=72 9×9=81
嵌套规则:列表生成式中,嵌套 for 的顺序和普通双层 for 循环顺序一致。
python# 等价写法: result = [] for i in range(1, 10): for j in range(1, i+1): result.append(f"{j}×{i}={i*j}")
4.8 类型转换
python
# 字符串变量 s,存储文本
s = "hello"
print(list(s)) # ['h', 'e', 'l', 'l', 'o']
# 创建列表 [...]
lst = [1, 2, 3]
print(tuple(lst)) # (1, 2, 3)
# 创建列表 [...]
lst2 = [1, 2, 2, 3, 3, 3]
print(set(lst2)) # {1, 2, 3}
# 创建列表 [...]
pairs = [("name", "张三"), ("age", 20)]
print(dict(pairs)) # {"name": "张三", "age": 20}
例题
例题4-1(读程序写结果)
题目:阅读以下程序,写出运行结果。
python
# 创建列表 [...]
nums = [10, 20, 30, 40, 50]
# .末尾添加
nums.append(60)
# .插入
nums.insert(0, 5)
# .移除
nums.pop(2)
# .删除
nums.remove(40)
# 打印输出到屏幕
print(nums)
答案/参考代码:
[5, 10, 30, 50, 60]
解析:
- 初始:
[10, 20, 30, 40, 50] append(60)→[10, 20, 30, 40, 50, 60]insert(0, 5)→[5, 10, 20, 30, 40, 50, 60]pop(2)移除索引2处的元素20 →[5, 10, 30, 40, 50, 60]remove(40)移除第一个值为40的元素 →[5, 10, 30, 50, 60]
例题4-2(补全程序题)
题目 :以下程序输入一个英文句子,将其中的每个单词首字母大写后输出(使用字符串的 split() 和 join() 方法)。请补全空缺代码。
python
# 输入 → 存入变量 sentence
sentence = input("请输入一个英文句子:")
words = _______ # 将句子按空格分割成单词列表
# 空列表 []
capitalized_words = []
# 遍历 words
for word in words:
capitalized_words.append(_______ ) # 将每个单词首字母大写
result = _______ # 将列表用空格连接回字符串
# 打印输出到屏幕
print("转换结果:", result)
测试:输入 "hello world python",输出应为 "Hello World Python"。
答案/参考代码:
python
words = sentence.split() # 将句子按空格分割成单词列表
# 空列表 []
capitalized_words = []
# 遍历 words
for word in words:
capitalized_words.append(word.capitalize()) # 将每个单词首字母大写
result = " ".join(capitalized_words) # 将列表用空格连接回字符串
例题4-3(读程序写结果)
题目:阅读以下程序,写出运行结果。
python
t1 = (1, 2, 3)
t2 = (4, 5)
# 计算 → 赋值给 t3
t3 = t1 + t2
# 打印输出到屏幕
print(len(t3))
# 打印输出到屏幕
print(t3[2])
# 打印输出到屏幕
print(t3[-1])
# 打印输出到屏幕
print(t3[1:4])
答案/参考代码:
5
3
5
(2, 3, 4)
解析:
t1 + t2连接为(1, 2, 3, 4, 5),长度为5- 索引2的元素是
3 - 最后一个元素(索引-1)是
5 t3[1:4]切片取索引1到3的元素 →(2, 3, 4)
例题4-4(补全程序题)
题目:以下程序统计一个字符串中每个字符出现的次数,存入字典并输出。请补全空缺代码。
python
# 输入 → 存入变量 text
text = input("请输入一个字符串:")
char_count = {} # 空字典
# 遍历 text
for ch in text:
if _______ : # 如果字符已经在字典中
_______ # 计数加1
# 否则
else:
_______ # 否则将字符加入字典,初始为1
# 打印输出到屏幕
print("字符统计结果:")
# .所有键值对
for ch, count in char_count.items():
# 打印输出到屏幕
print(f"'{ch}': {count}")
测试:输入 "hello",输出应为 {'h':1, 'e':1, 'l':2, 'o':1}。
答案/参考代码:
python
# 遍历 text
for ch in text:
if ch in char_count: # 如果字符已经在字典中
char_count[ch] += 1 # 计数加1
# 否则
else:
char_count[ch] = 1 # 否则将字符加入字典,初始为1
例题4-5(读程序写结果)
题目:阅读以下程序,写出运行结果。
python
# 创建集合 {...}
set_a = {1, 2, 3, 4, 5}
# 创建集合 {...}
set_b = {4, 5, 6, 7, 8}
# 打印输出到屏幕
print(set_a & set_b)
# 打印输出到屏幕
print(set_a | set_b)
# 打印输出到屏幕
print(set_a - set_b)
# 打印输出到屏幕
print(set_b - set_a)
答案/参考代码:
{4, 5}
{1, 2, 3, 4, 5, 6, 7, 8}
{1, 2, 3}
{6, 7, 8}
解析:
- 交集
&:两个集合共有的元素 →{4, 5} - 并集
|:两个集合所有不重复的元素 →{1,2,3,4,5,6,7,8} - 差集
-:在set_a中但不在set_b中的元素 →{1,2,3} - 差集
-:在set_b中但不在set_a中的元素 →{6,7,8}
例题4-6(读程序写结果)
题目:阅读以下程序,写出运行结果。
python
# 创建列表 [...]
numbers = [x * 2 for x in range(1, 6) if x % 2 == 0]
# 打印输出到屏幕
print(numbers)
# 创建列表 [...]
words = ["python", "java", "c++", "go"]
# 创建列表 [...]
result = [w.upper() for w in words if len(w) > 3]
# 打印输出到屏幕
print(result)
答案/参考代码:
[4, 8]
['PYTHON', 'JAVA']
解析:
- 第一个列表生成式:
range(1,6)中偶数为2和4,乘以2后得到[4, 8] - 第二个列表生成式:长度大于3的单词有 "python"(6) 和 "java"(4),转换为大写
例题4-7(补全程序题)
题目:以下程序使用字典存储学生的各科成绩,遍历字典计算总分和平均分。请补全空缺代码。
python
# 创建字典 {键:值, ...}
scores = {"语文": 85, "数学": 92, "英语": 78, "Python": 95}
# 整型变量 total = 0
total = 0
for _______ : # 遍历字典的值
# total += 运算
total += score
# 计算 → 赋值给 average
average = total / _______
# 打印输出到屏幕
print(f"总分:{total}")
# 打印输出到屏幕
print(f"平均分:{average:.1f}")
答案/参考代码:
python
for score in scores.values(): # 遍历字典的值
# total += 运算
total += score
# 计算 → 赋值给 average
average = total / len(scores)
例题4-8(编程题)
题目描述 :
编写程序,输入一段英文文本,统计其中每个单词出现的次数(不区分大小写),并按照单词的字母顺序输出统计结果。要求:
- 使用字典存储单词及其出现次数
- 使用字符串的
split()、lower()、strip()方法处理文本 - 输出格式:每行一个单词及其出现次数
参考代码:
python
# 输入 → 存入变量 text
text = input("请输入一段英文文本:")
# 导入 string 库
import string
# 遍历 text
for ch in text:
# 如果 ch in string.punctuation
if ch in string.punctuation:
# .替换
text = text.replace(ch, " ")
# .转小写
words = text.lower().split()
# 空字典 {}
word_count = {}
# 遍历 words
for word in words:
# 如果 word in word_count
if word in word_count:
word_count[word] += 1
# 否则
else:
word_count[word] = 1
# 打印输出到屏幕
print("\n单词统计结果:")
# .所有键
for word in sorted(word_count.keys()):
# 打印输出到屏幕
print(f"{word}: {word_count[word]}")
4.9 考试提示
针对"读程序写结果"题型
- 列表操作 的链式调用:
append、insert、pop、remove等方法的顺序执行结果 - 集合运算 :交集
&、并集|、差集-的结果 - 列表生成式的等价展开要能理解
- 元组的不可变性特点常结合索引和切片出题
针对"补全程序题"题型
- 字典统计 模式:
if key in dict: dict[key] += 1 else: dict[key] = 1 - 字符串方法 :
split()、join()、upper()、lower()、capitalize() - 字典遍历 :
items()、keys()、values()的区分使用
针对"编程题"题型
- 词频统计是典型考题,需要综合使用字符串方法和字典
- 列表去重 :使用
set()或自定义逻辑 - 数据分组:使用字典将数据按照某种规则分组存储
课后练习
课后练习4-1(读程序写结果)
题目:阅读以下程序,写出运行结果。
python
# 创建列表 [...]
data = [3, 1, 4, 1, 5, 9, 2, 6]
# .排序
data.sort()
# .移除
data.pop()
# .插入
data.insert(0, 0)
# .追加多个
data.extend([7, 8])
# 打印输出到屏幕
print(data)
答案:
[0, 1, 1, 2, 3, 4, 5, 6, 7, 8]
解析:
- 初始:
[3, 1, 4, 1, 5, 9, 2, 6] sort()排序:[1, 1, 2, 3, 4, 5, 6, 9]pop()移除最后一个元素9:[1, 1, 2, 3, 4, 5, 6]insert(0, 0)在开头插入0:[0, 1, 1, 2, 3, 4, 5, 6]extend([7, 8])追加7和8:[0, 1, 1, 2, 3, 4, 5, 6, 7, 8]
课后练习4-2(补全程序题)
题目:以下程序输入一个列表(元素为整数),去除列表中的重复元素(保持原有顺序),并输出结果。请补全空缺代码。
python
nums = eval(input("请输入一个整数列表,如[1,2,3]:"))
result = [] # 存放去重后的结果
# 遍历 nums
for num in nums:
if _______ : # 如果num不在result中
_______ # 将num加入result
# 打印输出到屏幕
print("去重后的结果:", result)
测试:输入 [1, 2, 2, 3, 1, 4, 3],输出应为 [1, 2, 3, 4]。
答案:
python
# 遍历 nums
for num in nums:
if num not in result: # 如果num不在result中
result.append(num) # 将num加入result
课后练习4-3(编程题)
题目描述 :
编写程序,模拟一个电话簿管理系统。要求:
- 使用字典存储联系人信息,格式为
{名称: 电话号码} - 程序首先添加3个联系人(名称和号码由用户输入)
- 输入一个联系人名称,查询其电话号码
- 如果联系人存在则输出号码,不存在则输出"未找到该联系人"
- 最后输出所有联系人的列表(按名称字母顺序排序)
参考代码:
python
# 空字典 {}
phonebook = {}
# 打印输出到屏幕
print("请添加3个联系人:")
# 遍历 range(3)
for i in range(3):
# 输入 → 存入变量 name
name = input(f"请输入第{i+1}个联系人的名称:")
# 输入 → 存入变量 phone
phone = input(f"请输入{name}的电话号码:")
phonebook[name] = phone
# 输入 → 存入变量 search_name
search_name = input("\n请输入要查询的联系人名称:")
# 如果 search_name in phonebook
if search_name in phonebook:
# 打印输出到屏幕
print(f"{search_name}的电话号码是:{phonebook[search_name]}")
# 否则
else:
# 打印输出到屏幕
print("未找到该联系人")
# 打印输出到屏幕
print("\n所有联系人列表(按名称排序):")
# .所有键
for name in sorted(phonebook.keys()):
# 打印输出到屏幕
print(f"{name}: {phonebook[name]}")
本章模拟练习
模拟练习4-1(读程序写结果)
题目:阅读以下程序,写出运行结果。
python
students = [
{"name": "张三", "score": 85},
{"name": "李四", "score": 92},
{"name": "王五", "score": 78}
]
# 整型变量 max_score = 0
max_score = 0
# 字符串变量 max_name,存储文本
max_name = ""
# 遍历 students
for s in students:
# 如果 s["score"] > max_score
if s["score"] > max_score:
max_score = s["score"]
max_name = s["name"]
# 打印输出到屏幕
print(f"最高分:{max_name},{max_score}分")
答案:
最高分:李四,92分
解析:遍历字典列表,记录最高分和对应的姓名。张三85分、李四92分、王五78分,最高分是李四的92分。
模拟练习4-2(补全程序题)
题目:以下程序输入一个字符串,判断它是否为回文字符串(正读反读一样,如"abcba")。请补全空缺代码。
python
# 输入 → 存入变量 text
text = input("请输入一个字符串:")
reversed_text = _______ # 反转字符串
# 如果 text == reversed_text
if text == reversed_text:
# 打印输出到屏幕
print(f"'{text}'是回文字符串")
# 否则
else:
# 打印输出到屏幕
print(f"'{text}'不是回文字符串")
char_list = list(text)
# .反转
char_list.reverse()
reversed_text2 = _______ # 将列表连接回字符串
# 打印输出到屏幕
print(f"方法二验证:'{reversed_text2}'")
答案:
python
reversed_text = text[::-1] # 反转字符串
reversed_text2 = "".join(char_list) # 将列表连接回字符串
模拟练习4-3(编程题)
题目描述 :
编写程序,对学生成绩进行统计分析。要求:
- 输入若干个学生的成绩(用空格分隔,如
85 92 78 90 88) - 将输入的字符串转换为成绩列表
- 计算并输出:
- 最高分、最低分、平均分(保留1位小数)
- 优秀(≥90分)的学生人数
- 不及格(<60分)的学生人数
- 输出所有成绩中位数(排序后取中间的那个数,如果为偶数个则取中间两个的平均值)
参考代码:
python
# 输入 → 存入变量 input_str
input_str = input("请输入学生成绩(用空格分隔):")
# .分割
score_strs = input_str.split()
scores = [float(s) for s in score_strs] # 列表生成式转换为浮点数
max_score = max(scores)
min_score = min(scores)
# 计算 → 赋值给 avg_score
avg_score = sum(scores) / len(scores)
excellent = len([s for s in scores if s >= 90])
fail = len([s for s in scores if s < 60])
sorted_scores = sorted(scores)
n = len(sorted_scores)
if n % 2 == 1: # 奇数个
# 计算 → 赋值给 median
median = sorted_scores[n // 2]
else: # 偶数个
# 计算 → 赋值给 median
median = (sorted_scores[n // 2 - 1] + sorted_scores[n // 2]) / 2
# 打印输出到屏幕
print(f"成绩列表:{scores}")
# 打印输出到屏幕
print(f"最高分:{max_score}")
# 打印输出到屏幕
print(f"最低分:{min_score}")
# 打印输出到屏幕
print(f"平均分:{avg_score:.1f}")
# 打印输出到屏幕
print(f"优秀人数(≥90分):{excellent}")
# 打印输出到屏幕
print(f"不及格人数(<60分):{fail}")
# 打印输出到屏幕
print(f"中位数:{median}")
下一章预告:第5章 函数与模块 ------ 将学习函数的定义与调用、参数传递、变量作用域、模块导入等知识。
Python程序设计 期末备考教程(第5-8章)
4.8 综合应用:二分查找法
二分查找法在有序列表中快速查找元素,每次将查找范围缩小一半。
算法步骤
- 设定
low=0指向第一个元素,high=len(lst)-1指向最后一个 - 计算中间位置
mid = (low + high) // 2 - 比较目标值与
lst[mid]:- 相等 → 找到,返回索引
- 目标值 <
lst[mid]→high = mid - 1(搜索左半部分) - 目标值 >
lst[mid]→low = mid + 1(搜索右半部分)
- 重复2~3步,直到
low > high为止
代码实现
python
def binary_search(lst, target):
"""在有序列表lst中查找target,返回索引,未找到返回-1"""
low, high = 0, len(lst) - 1
while low <= high:
mid = (low + high) // 2
if lst[mid] == target:
return mid
elif target < lst[mid]:
high = mid - 1
else:
low = mid + 1
return -1
# 测试
nums = [1, 3, 5, 7, 9, 11, 13, 15]
print(binary_search(nums, 7)) # 输出:3
print(binary_search(nums, 4)) # 输出:-1
复杂度分析
| 维度 | 值 | 对比 |
|---|---|---|
| 时间复杂度 | O(log n) | 顺序查找O(n) |
| 空间复杂度 | O(1) | 只需几个指针变量 |
二分查找要求列表有序 且支持随机访问(如Python列表)。
示例 :在升序列表 [2, 5, 8, 12, 16, 23, 38, 45, 50] 中查找23:
low=0, high=8, mid=4→lst[4]=16 < 23→low=5low=5, high=8, mid=6→lst[6]=38 > 23→high=5low=5, high=5, mid=5→lst[5]=23→ 找到!
第5章 编程思维与方法
一、知识讲解
1. 计数统计
计数统计是最基本的编程思维,通过循环遍历数据,使用计数器变量统计满足条件的元素个数。
python
# 基本框架
# 声明整型变量 count,赋值为 0
count = 0
# for 循环:遍历 数据集合 中的每个元素
for 元素 in 数据集合:
# if 判断 条件 是否成立
if 条件:
# 将 count 进行 += 运算并赋值回自身
count += 1
2. 穷举法
穷举法(暴力枚举)是将所有可能的情况逐一列举,检查每种情况是否满足条件。
python
# 基本框架
# for 循环:遍历 range(起始, 结束+1) 中的每个元素
for i in range(起始, 结束+1):
# if 判断 条件 是否成立
if 条件:
print(i) # 输出满足条件的解
典型应用:水仙花数、质数判断、百钱百鸡等。
3. 递推/迭代
递推是从已知条件出发,逐步推导出结果。迭代是不断用旧值计算新值的过程。
python
# 斐波那契数列递推
a, b = 1, 1
# for 循环:遍历 range(2, n) 中的每个元素
for i in range(2, n):
# 计算表达式,结果赋值给 a, b
a, b = b, a + b
4. 顺序查找
从列表的第一个元素开始,依次比较,直到找到目标或遍历完所有元素。
python
def sequential_search(lst, target):
# for 循环:遍历 range(len(lst)) 中的每个元素
for i in range(len(lst)):
# if 判断 lst[i] == target 是否成立
if lst[i] == target:
return i # 返回下标
return -1 # 未找到
5. 二分查找
二分查找要求数据必须是有序的。每次取中间元素与目标比较,缩小一半查找范围。
python
def binary_search(lst, target):
# 计算表达式,结果赋值给 low, high
low, high = 0, len(lst) - 1
# while 循环:当 low <= high 成立时重复执行
while low <= high:
# 计算表达式,结果赋值给 mid
mid = (low + high) // 2
# if 判断 lst[mid] == target 是否成立
if lst[mid] == target:
return mid
# elif 再判断 lst[mid] < target 是否成立
elif lst[mid] < target:
# 计算表达式,结果赋值给 low
low = mid + 1
# else 以上条件都不成立时执行
else:
# 计算表达式,结果赋值给 high
high = mid - 1
return -1
6. 冒泡排序
相邻元素两两比较,大的往后"冒泡"。每轮确定一个最大值放在末尾。
python
def bubble_sort(lst):
n = len(lst) # n = 列表的长度(即元素的个数)
for i in range(n - 1): # 外层循环:总共需要 n-1 轮比较
for j in range(n - 1 - i): # 内层循环:每轮比较的次数递减(已冒泡的不再比较)
if lst[j] > lst[j + 1]: # 如果前面的元素比后面的大(顺序不对)
lst[j], lst[j + 1] = lst[j + 1], lst[j] # 交换两个元素,把大的往后"冒泡"
7. 选择排序
每轮选出未排序部分的最小值,放到已排序部分的末尾。
python
def selection_sort(lst):
n = len(lst) # n = 列表的长度
for i in range(n - 1): # 外层循环:每一轮选出一个最小值放到位置 i
min_idx = i # 先假设位置 i 的元素就是本轮的最小值
for j in range(i + 1, n): # 内层循环:遍历位置 i 之后的所有未排序元素
if lst[j] < lst[min_idx]: # 如果发现更小的元素
min_idx = j # 更新最小值下标为 j
lst[i], lst[min_idx] = lst[min_idx], lst[i] # 将最小值与位置 i 的元素交换
8. 插入排序
将未排序元素逐个插入到已排序部分的正确位置。
python
def insertion_sort(lst):
n = len(lst) # n = 列表的长度
for i in range(1, n): # 从第2个元素开始,逐个插入已排序的部分
key = lst[i] # key = 当前要插入的元素(好比手里的"待插入牌")
j = i - 1 # j 指向已排序部分的最后一个元素
while j >= 0 and lst[j] > key: # 从右向左找插入位置,只要前面的比 key 大就继续
lst[j + 1] = lst[j] # 把比 key 大的元素往后移一位
j -= 1 # j 继续向左移动,检查更前面的元素
lst[j + 1] = key # 找到正确位置,把 key 插入进去
9. 希尔排序(Shell Sort)
希尔排序是插入排序的改进版,通过引入"增量"(gap)的概念,先对间隔较大的元素进行排序,使数组"基本有序",再逐步缩小增量进行插入排序,最终 gap=1 时就是一次标准的插入排序。
python
def shell_sort(lst):
n = len(lst)
gap = n // 2 # 初始增量 gap = 列表长度的一半
while gap > 0: # 当 gap 大于 0 时继续循环
for i in range(gap, n): # 从 gap 位置开始,对每个元素进行"分组插入排序"
temp = lst[i] # 取出当前要插入的元素
j = i
while j >= gap and lst[j - gap] > temp: # 在 gap 间隔分组中向前比较
lst[j] = lst[j - gap] # 将较大的元素向后移动一个 gap 位
j -= gap # j 向前移动 gap 位
lst[j] = temp # 找到正确位置,插入
gap //= 2 # 增量减半,缩小分组间距
希尔排序执行示例:
原始列表:[8, 9, 1, 7, 2, 3, 5, 4, 6, 0]
gap=5: [3, 5, 1, 6, 0, 8, 9, 4, 7, 2] (每隔5个元素的子序列分别排序)
gap=2: [0, 2, 1, 4, 3, 5, 7, 6, 8, 9] (间隔2的分组排序)
gap=1: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] (标准插入排序,此时列表已基本有序)
10. 排序算法对比
| 排序算法 | 最好情况 | 平均情况 | 最坏情况 | 空间复杂度 | 稳定性 |
|---|---|---|---|---|---|
| 冒泡排序 | O(n) | O(n²) | O(n²) | O(1) | 稳定 |
| 选择排序 | O(n²) | O(n²) | O(n²) | O(1) | 不稳定 |
| 插入排序 | O(n) | O(n²) | O(n²) | O(1) | 稳定 |
| 希尔排序 | O(nlogn) | O(n^1.3) | O(n²) | O(1) | 不稳定 |
| 快速排序 | O(nlogn) | O(nlogn) | O(n²) | O(logn) | 不稳定 |
- 稳定排序:值相等的元素在排序后相对位置不变。冒泡排序和插入排序是稳定的。
- 不稳定排序:值相等的元素在排序后可能会交换位置。选择排序和希尔排序是不稳定的。
- 快速排序 是实际应用中最常用的排序算法,平均效率最高,Python 内置的
sorted()和list.sort()底层使用的 Timsort 就是归并排序和插入排序的混合优化版本。
11. 时间复杂度
时间复杂度用来衡量算法执行时间随数据规模增长的变化趋势。使用大O表示法(Big O Notation)来描述。
常见复杂度从小到大排序:
O(1) < O(logn) < O(n) < O(nlogn) < O(n²) < O(n³) < O(2ⁿ)
| 复杂度 | 名称 | 通俗解释 | 典型例子 |
|---|---|---|---|
| O(1) | 常数阶 | 无论数据多大,执行时间不变 | 列表索引访问 lst[i] |
| O(logn) | 对数阶 | 每次操作后数据规模减半 | 二分查找 |
| O(n) | 线性阶 | 执行时间与数据规模成正比 | 顺序查找、单层循环 |
| O(nlogn) | 线性对数阶 | 比线性稍慢,但优于平方阶 | 快速排序、归并排序 |
| O(n²) | 平方阶 | 嵌套循环,数据翻倍时间翻四倍 | 冒泡排序、选择排序、插入排序 |
| O(n³) | 立方阶 | 三重嵌套循环 | 简单矩阵乘法 |
| O(2ⁿ) | 指数阶 | 数据每增加1,时间翻倍 | 递归求斐波那契(无优化) |
如何判断时间复杂度:
python
# O(1) - 常数阶:只执行一次
x = lst[0]
# O(n) - 线性阶:一个循环
for i in range(n):
print(i)
# O(n²) - 平方阶:嵌套循环
for i in range(n): # 外层循环 n 次
for j in range(n): # 内层循环 n 次
print(i, j) # 总共执行 n × n 次
# O(logn) - 对数阶:每次减半
while n > 1:
n = n // 2 # 每次 n 缩小一半,大约执行 log₂n 次
考试重点:记住冒泡排序、选择排序、插入排序的时间复杂度都是 O(n²),二分查找是 O(logn)。看到嵌套循环就是 O(n²),单层循环就是 O(n)。
12. sorted() 与 sort() 的区别
这是一个非常高频的考点,务必区分清楚:
| 对比维度 | list.sort() |
sorted() |
|---|---|---|
| 类型 | 列表的方法(只能用于列表) | 内置函数(可用于任何可迭代对象) |
| 是否改变原数据 | 原地排序,改变原列表 | 生成新列表,不改变原数据 |
| 返回值 | None(无返回值) |
返回一个新的排序后的列表 |
| 用法 | lst.sort() |
new_lst = sorted(lst) |
代码对比:
python
# ---- sort():原地排序,改变原列表 ----
numbers = [3, 1, 4, 1, 5]
result = numbers.sort()
print(result) # None(sort() 没有返回值!)
print(numbers) # [1, 1, 3, 4, 5](原列表被改变了)
# ---- sorted():生成新列表,不改变原数据 ----
numbers = [3, 1, 4, 1, 5]
result = sorted(numbers)
print(result) # [1, 1, 3, 4, 5](返回新列表)
print(numbers) # [3, 1, 4, 1, 5](原列表不变!)
# ---- sorted() 可用于任何可迭代对象 ----
# 对元组排序
t = (3, 1, 4, 1, 5)
print(sorted(t)) # [1, 1, 3, 4, 5](返回列表)
# 对字符串排序
print(sorted("python")) # ['h', 'n', 'o', 'p', 't', 'y'](按字母顺序)
# 降序排序
print(sorted(numbers, reverse=True)) # [5, 4, 3, 1, 1]
numbers.sort(reverse=True)
print(numbers) # [5, 4, 3, 1, 1]
记忆口诀 :"sort 改自己,sorted 生新列" 。
sort()是列表的方法,调用后原列表被修改且返回None;sorted()是内置函数,不改变原数据,返回一个新的排序列表。考试中经常在"读程序写结果"题型中考察这一点------如果误以为sort()有返回值,就会写错结果。
二、例题精讲
【读程序写结果】例题1:穷举法
题目:阅读以下程序,写出运行结果。
python
count = 0 # count 用来统计符合条件的数字个数,初始为 0
for i in range(100, 200): # 遍历 100 到 199 之间的每一个整数
if i % 3 == 0 and i % 5 == 0: # 如果 i 能同时被 3 和 5 整除(即能被 15 整除)
count += 1 # 计数器加 1
print(count) # 输出满足条件的数字总个数
答案:7
解析:程序统计了100到199之间能同时被3和5整除(即能被15整除)的数的个数。100÷15≈6.67,所以第一个数是105,最后一个是195,共有(195-105)/15+1=7个。
【读程序写结果】例题2:递推法
题目:阅读以下程序,写出运行结果。
python
a, b = 1, 2 # 初始化 a = 1, b = 2(a 在前,b 在后)
for i in range(4): # 循环执行 4 次(i = 0, 1, 2, 3)
a, b = b, a + b # 同时更新:新 a = 旧 b,新 b = 旧 a + 旧 b
print(a) # 循环结束后输出 a 的值
答案:13
解析:这是一个递推过程。
- 初始:a=1, b=2
- i=0:a=2, b=1+2=3
- i=1:a=3, b=2+3=5
- i=2:a=5, b=3+5=8
- i=3:a=8, b=5+8=13
循环结束打印a,值为13。
【补全程序题】例题3:穷举法补全
题目:百钱百鸡问题:公鸡5元一只,母鸡3元一只,小鸡1元三只。用100元买100只鸡,请补全以下程序输出所有购买方案。
python
for x in range(0, 21): # x 表示公鸡数量(最多20只)
for y in range(0, 34): # y 表示母鸡数量(最多33只)
z = ________ # 小鸡数量 = 100 - x - y
if 5 * x + 3 * y + ________ == 100: # 总价等于100
# print() 输出内容到屏幕上
print(f"公鸡{x}只,母鸡{y}只,小鸡{z}只")
答案:
- 第1空:
100 - x - y - 第2空:
z / 3或z // 3
参考代码:
python
for x in range(0, 21): # x 遍历公鸡的可能数量:0 ~ 20 只
for y in range(0, 34): # y 遍历母鸡的可能数量:0 ~ 33 只
z = 100 - x - y # z = 小鸡数量 = 总数 100 - 公鸡 - 母鸡
if z >= 0 and 5 * x + 3 * y + z / 3 == 100: # 钱数总和必须等于 100 元,且小鸡数 >= 0
print(f"公鸡{x}只,母鸡{y}只,小鸡{z}只") # 输出符合条件的购买方案
【补全程序题】例题4:二分查找补全
题目 :以下程序用二分查找在有序列表 [1, 3, 5, 7, 9, 11, 13, 15, 17, 19] 中查找数字 13,请补全空缺。
python
# 创建列表,用方括号 [] 包裹元素
lst = [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
# 声明整型变量 target,赋值为 13
target = 13
low, high = 0, ________
# while 循环:当 low <= high 成立时重复执行
while low <= high:
mid = ________
# if 判断 lst[mid] == target 是否成立
if lst[mid] == target:
# print() 输出内容到屏幕上
print(f"找到了,下标为{mid}")
________
# elif 再判断 lst[mid] < target 是否成立
elif lst[mid] < target:
# 计算表达式,结果赋值给 low
low = mid + 1
# else 以上条件都不成立时执行
else:
# 计算表达式,结果赋值给 high
high = mid - 1
答案:
- 第1空:
len(lst) - 1(右边界初始值) - 第2空:
(low + high) // 2(计算中间下标) - 第3空:
break(找到后退出循环)
【补全程序题】例题5:选择排序补全
题目:以下程序使用选择排序将列表升序排列,请补全空缺。
python
# 创建列表,用方括号 [] 包裹元素
lst = [64, 25, 12, 22, 11]
n = len(lst)
# for 循环:遍历 range(n - 1) 中的每个元素
for i in range(n - 1):
min_idx = ________
# for 循环:遍历 range(i + 1, n) 中的每个元素
for j in range(i + 1, n):
# if 判断 lst[j] ________ lst[min_idx] 是否成立
if lst[j] ________ lst[min_idx]:
min_idx = ________
lst[i], lst[min_idx] = lst[min_idx], lst[i]
# print() 输出内容到屏幕上
print(lst)
答案:
- 第1空:
i(将当前下标设为最小值下标) - 第2空:
<(发现更小的值) - 第3空:
j(更新最小值下标为j)
【编程题】例题6:冒泡排序编程
题目 :编写程序,使用冒泡排序将列表 [5, 2, 8, 1, 9, 4] 按升序排列,并输出排序后的结果。
参考代码:
python
lst = [5, 2, 8, 1, 9, 4] # 原始未排序的列表
n = len(lst) # n = 列表的长度
for i in range(n - 1): # 外层循环:总共需要 n-1 轮排序
for j in range(n - 1 - i): # 内层循环:每轮比较 n-1-i 对相邻元素
if lst[j] > lst[j + 1]: # 如果左边的数比右边的大
lst[j], lst[j + 1] = lst[j + 1], lst[j] # 交换位置,把较大的数往后移
print("排序结果:", lst) # 输出排序完成后的列表
输出 :排序结果: [1, 2, 4, 5, 8, 9]
【编程题】例题7:顺序查找编程
题目 :编写程序,统计列表 [78, 56, 92, 45, 88, 92, 67, 92] 中指定分数 92 出现的次数,并输出所有出现位置的下标。
参考代码:
python
lst = [78, 56, 92, 45, 88, 92, 67, 92] # 待查找的成绩列表
target = 92 # 目标值:要查找的分数
count = 0 # 计数器:记录目标值出现的次数
positions = [] # 空列表:用来存放所有出现位置的下标
for i in range(len(lst)): # 遍历列表的每一个下标位置
if lst[i] == target: # 如果当前位置的元素等于目标值
count += 1 # 次数加 1
positions.append(i) # 把这个位置的下标存到列表中
print(f"目标值 {target} 出现了 {count} 次") # 输出总共出现的次数
print(f"出现的下标位置为: {positions}") # 输出所有出现位置的下标
输出:
目标值 92 出现了 3 次
出现的下标位置为: [2, 5, 7]
【编程题】例题8:迭代法编程
题目:编写程序,使用迭代法计算斐波那契数列的第20项。斐波那契数列定义:第1项为1,第2项为1,从第3项开始每项等于前两项之和。
参考代码:
python
n = 20 # 要求第 20 项
a, b = 1, 1 # 斐波那契数列的前两项都是 1
for i in range(2, n): # 从第 3 项开始循环到第 20 项(循环 18 次)
a, b = b, a + b # 同时更新:新 a = 旧 b(前一项),新 b = 旧 a + 旧 b(当前项)
print(f"斐波那契数列第 {n} 项为: {b}") # 输出第 20 项的值
输出 :斐波那契数列第 20 项为: 6765
三、考试提示
针对"读程序写结果"题型
- 穷举法题:重点关注循环范围和判断条件,手动模拟几轮循环即可找到规律。
- 递推法题:建议用表格记录每次循环后的变量值变化,找出最终结果。
- 排序法题:可以手动画出每次交换后的列表状态,避免出错。
针对"补全程序题"题型
- 二分查找常考的三个空:high初值(
len(lst)-1)、mid计算((low+high)//2)、找到后break。 - 选择排序常考:min_idx初值
i、比较条件<、更新min_idx为j。 - 注意补全时不要写多余的符号,空格也要规范。
针对"编程题"题型
- 冒泡排序:记住双重循环结构,外层
n-1次,内层n-1-i次。 - 顺序查找:遍历每个元素比较即可,注意返回下标或统计次数。
- 迭代法:记住递推公式,用
a, b = b, a+b同时更新两个变量。 - 做题步骤:先写注释理清思路 → 写框架代码 → 填充细节 → 检查边界条件。
四、课后练习
练习1(读程序写结果)
题目:阅读以下程序,写出运行结果。
python
count = 0 # 计数器初始化为 0
for i in range(1, 51): # 遍历 1 到 50 之间的每个整数
if i % 7 == 0 or i % 10 == 7: # 如果 i 是 7 的倍数,或者个位数字是 7
count += 1 # 计数器加 1
print(count) # 输出符合条件的数字总个数
答案:17
解析:统计1到50之间7的倍数或个位是7的数字个数。
- 7的倍数:7,14,21,28,35,42,49 → 7个
- 个位是7但不是7的倍数:17,27,37,47 → 4个
但注意7既是7的倍数又个位是7,已被计数。实际上程序是or,所以每个数只计一次。 - 7的倍数:7,14,21,28,35,42,49(7个)
- 个位是7:7,17,27,37,47(5个,但7已算过,新增4个)
- 总共:7+4 = 11... 等一下,让我重新算。
7的倍数(1-50):7,14,21,28,35,42,49 → 7个
个位是7:7,17,27,37,47 → 5个
其中7是交集(重复)
所以 count = 7 + 5 - 1 = 11... 不对!程序用的是 or,每个i判断一次。让我数:
i=7: 7%70 ✓ → count=1
i=14: 14%70 ✓ → count=2
i=17: 17%10==7 ✓ → count=3
i=21: ✓ → count=4
i=27: ✓ → count=5
i=28: ✓ → count=6
i=35: ✓ → count=7
i=37: ✓ → count=8
i=42: ✓ → count=9
i=47: ✓ → count=10
i=49: ✓ → count=11
所以答案是11。
等等,再检查一下:
7: 7%70 ✓
14: 14%70 ✓
17: 17%107 ✓
21: 21%70 ✓
27: 27%107 ✓
28: 28%70 ✓
35: 35%70 ✓
37: 37%107 ✓
42: 42%70 ✓
47: 47%107 ✓
49: 49%7==0 ✓
一共11个。
练习2(补全程序题)
题目:以下程序使用顺序查找在列表中查找目标值,请补全空缺。
python
# 创建列表,用方括号 [] 包裹元素
scores = [88, 72, 93, 65, 85, 90]
# 声明整型变量 target,赋值为 85
target = 85
found = ________
# for 循环:遍历 range(________) 中的每个元素
for i in range(________):
# if 判断 scores[i] == target 是否成立
if scores[i] == target:
# print() 输出内容到屏幕上
print(f"找到 {target},下标为 {i}")
# 声明布尔型变量 found,值为 True(真)
found = True
________
# if 判断 not found 是否成立
if not found:
# print() 输出内容到屏幕上
print("未找到")
答案:
- 第1空:
False - 第2空:
len(scores) - 第3空:
break
练习3(编程题)
题目:编写程序,使用穷举法找出100到999之间所有的"水仙花数"。水仙花数是指一个三位数,其各位数字的立方和等于该数本身。例如:153 = 1^3 + 5^3 + 3^3。
参考代码:
python
for num in range(100, 1000): # 遍历所有三位数(100 ~ 999)
a = num // 100 # 百位:整除 100 取商,如 345//100 = 3
b = (num // 10) % 10 # 十位:先整除 10 去掉个位,再取个位,如 (345//10)%10 = 4
c = num % 10 # 个位:除以 10 取余数,如 345%10 = 5
if a**3 + b**3 + c**3 == num: # 如果百位³ + 十位³ + 个位³ 等于原数本身
print(num, end=" ") # 则输出该水仙花数,末尾用空格隔开不换行
输出 :153 370 371 407
五、本章模拟练习
模拟1(读程序写结果)
题目:阅读以下程序,写出运行结果。
python
lst = [3, 7, 1, 9, 4, 6, 2] # 原始未排序的列表
n = len(lst) # n = 列表长度
for i in range(n - 1): # 外层循环:进行 n-1 轮冒泡排序
for j in range(n - 1 - i): # 内层循环:每轮比较次数递减
if lst[j] > lst[j + 1]: # 如果前面的数比后面大
lst[j], lst[j + 1] = lst[j + 1], lst[j] # 交换两者,让大的往后走
print(lst) # 输出排序后的结果
答案 :[1, 2, 3, 4, 6, 7, 9]
模拟2(补全程序题)
题目:以下程序使用二分查找在有序列表中查找目标值,请补全程序。
python
# 创建列表,用方括号 [] 包裹元素
lst = [2, 5, 8, 12, 16, 23, 38, 45, 56, 72]
# 声明整型变量 target,赋值为 23
target = 23
# 声明整型变量 low,赋值为 0
low = 0
high = ________
# while 循环:当 low <= high 成立时重复执行
while low <= high:
mid = ________
# if 判断 lst[mid] == target 是否成立
if lst[mid] == target:
# print() 输出内容到屏幕上
print(f"找到 {target},下标为 {mid}")
________
# elif 再判断 lst[mid] < target 是否成立
elif lst[mid] < target:
________
# else 以上条件都不成立时执行
else:
# 计算表达式,结果赋值给 high
high = mid - 1
答案:
- 第1空:
len(lst) - 1 - 第2空:
(low + high) // 2 - 第3空:
break - 第4空:
low = mid + 1
模拟3(编程题)
题目 :编写程序,使用选择排序将列表 [29, 10, 14, 37, 13, 33] 按升序排列,每完成一轮排序后输出当前列表状态。
参考代码:
python
lst = [29, 10, 14, 37, 13, 33] # 原始未排序列表
n = len(lst) # n = 列表长度
for i in range(n - 1): # 外层循环:进行 n-1 轮选择
min_idx = i # 假设位置 i 的元素是当前最小值
for j in range(i + 1, n): # 内层循环:从 i+1 开始向后查找更小的元素
if lst[j] < lst[min_idx]: # 如果找到了更小的
min_idx = j # 更新最小值下标
lst[i], lst[min_idx] = lst[min_idx], lst[i] # 将最小值交换到位置 i
print(f"第{i+1}轮: {lst}") # 每轮结束后输出当前列表状态
print("最终结果:", lst) # 输出最终排序结果
输出:
第1轮: [10, 29, 14, 37, 13, 33]
第2轮: [10, 13, 14, 37, 29, 33]
第3轮: [10, 13, 14, 37, 29, 33]
第4轮: [10, 13, 14, 29, 37, 33]
第5轮: [10, 13, 14, 29, 33, 37]
最终结果: [10, 13, 14, 29, 33, 37]
算法分类与复杂度分析
这是计算机算法的基础分析框架,用来评估和比较不同算法的优劣。
算法分类汇总
| 算法类别 | 核心思路 | 典型例题 |
|---|---|---|
| 递推法 | 从已知条件出发,利用递推式逐步推出后面的结果 | 斐波那契数列、猴子吃桃 |
| 迭代法 | 从初始近似值出发,反复用公式改进逼近精确解 | 牛顿迭代法求平方根 |
| 穷举法 | 逐一列举所有可能的情况,检查是否满足条件 | 百钱百鸡、水仙花数、素数 |
| 查找算法 | 在数据集合中查找特定元素的位置 | 顺序查找、二分查找 |
| 排序算法 | 将一组数据按指定顺序重新排列 | 选择排序、插入排序、冒泡排序 |
时间复杂度
算法的时间复杂度记作 T(n) = O(f(n)) ,表示算法执行时间随问题规模n的增长规律。
- n:问题规模(如数组长度、循环次数等)
- 大O表示法:只保留最高阶项,忽略常数和低阶项
- 语句频度f(n) = 原操作执行次数
复杂度排序(由低到高):
O(1) < O(log n) < O(n) < O(n log n) < O(n²) < O(n³) < O(2ⁿ) < O(n!)
| 时间复杂度 | 含义 | 举例 |
|---|---|---|
| O(1) | 常数时间,与数据规模无关 | 访问数组第一个元素 |
| O(log n) | 对数时间,极其高效 | 二分查找 |
| O(n) | 线性时间,遍历一次 | 顺序查找 |
| O(n log n) | 线性对数时间 | 高效排序(归并、快速) |
| O(n²) | 平方时间 | 冒泡排序 |
| O(2ⁿ) | 指数时间,增长极快 | 暴力法解NP问题 |
空间复杂度
空间复杂度记作 S(n) = O(f(n)),表示算法运行所需存储空间随问题规模增长的规律。
一般情况下,我们只需关注辅助存储空间(额外分配的变量、数组等),输入数据本身所占空间不计入。
排序算法综合对比
| 算法 | 平均时间复杂度 | 最坏时间复杂度 | 空间复杂度 | 稳定性 |
|---|---|---|---|---|
| 冒泡排序 | O(n²) | O(n²) | O(1) | 稳定 |
| 选择排序 | O(n²) | O(n²) | O(1) | 不稳定 |
| 插入排序 | O(n²) | O(n²) | O(1) | 稳定 |
| 希尔排序 | O(n¹·³) | O(n²) | O(1) | 不稳定 |
| 快速排序 | O(n log n) | O(n²) | O(log n) | 不稳定 |
课堂练习
用某种排序方法调整序列 7 4 2 8 1 0 6 3,最终得到 0 1 2 3 4 6 7 8,你判断使用了哪种排序算法?(提示:观察第一趟排序结果可判断)
希尔排序(设增量gap依次为5、3、1):
- gap=5:
7 4 2 8 1 0 6 3 - gap=3:......
- gap=1:
0 1 2 3 4 6 7 8
快速排序(设Ls=52为枢轴):
- 将Lhigh和枢轴比较,要求Lhigh ≥ 枢轴
- 将Llow和枢轴比较,要求Llow ≤ 枢轴
第6章 自定义函数
一、知识讲解
1. 函数定义与调用
函数是一段可重复使用的代码块。使用 def 关键字定义。
python
# 定义函数
def 函数名(参数列表): # def 关键字 + 函数名 + 参数列表 + 冒号
"""函数说明文档(可选)""" # 三引号包围的说明文字,描述函数功能(不是必需的但建议写)
函数体 # 函数要执行的代码,注意前面要有缩进(通常是 4 个空格)
return 返回值 # return 语句把结果返回给调用者(如果不需要返回可以省略)
# 调用函数
函数名(实参列表) # 通过"函数名(实际参数)"来执行函数
2. 参数传递
Python函数的参数传递方式:
- 位置参数:按顺序传递
- 默认参数:定义时给参数默认值,调用时可省略
- 关键字参数 :调用时用
参数名=值的方式传递
python
def greet(name, greeting="你好"): # 定义函数:name 是位置参数,greeting 有默认值 "你好"
print(f"{greeting},{name}") # 输出问候语,例如 "你好,张三"
greet("张三") # 只传 name,greeting 用默认值 "你好" → 输出"你好,张三"
greet("张三", "Hello") # 两个参数都按位置传递 → 输出"Hello,张三"
greet(greeting="嗨", name="张三") # 使用关键字参数,可以不按顺序传参 → 输出"嗨,张三"
3. return返回值
函数用 return 返回结果。函数遇到 return 立即结束,不再执行后续语句。
python
def add(a, b):
return a + b # return 将 a+b 的结果返回给调用者
print("这行不会执行") # return 之后的代码永远不会被执行到
result = add(3, 5) # 调用 add(3,5),返回值 8 被赋给 result 变量
如果没有 return,函数默认返回 None。
4. 递归函数
递归是函数自己调用自己的编程技巧。递归必须包含两部分:
- 递归终止条件:不再调用自身的情况
- 递归调用:调用自己解决更小的问题
python
def factorial(n):
if n == 0: # 【终止条件】n = 0 时不再递归,直接返回 1
return 1 # 0! = 1,这是递归的"出口"
return n * factorial(n - 1) # 递归调用:n! = n × (n-1)!,函数调用自身计算更小规模的问题
递归执行过程:先递进(向下调用),再回归(逐层返回结果)。
5. lambda函数
lambda是一种简洁的匿名函数,只能写一行表达式。
python
# 语法:lambda 参数: 表达式
f = lambda x, y: x + y # 定义一个匿名函数,等价于 def f(x,y): return x+y
print(f(3, 5)) # 调用 f(3,5),计算 3+5,输出 8
# 常用于排序
students = [("张三", 85), ("李四", 92), ("王五", 78)] # 每个元素是(姓名, 成绩)元组
students.sort(key=lambda s: s[1]) # 按成绩升序排序,lambda 取出每个元组的第 2 个元素作为排序依据
6. 模块导入
将函数放在模块(.py文件)中,用 import 导入使用。
python
# 方式1:导入整个模块
import math # 导入 math 模块,使用其中的函数需要加"math."前缀
print(math.sqrt(16)) # 调用 math 模块的 sqrt 函数计算平方根,√16 = 4.0
# 方式2:导入特定函数
from math import sqrt, pi # 只导入 sqrt 函数和 pi 常量,使用时不需要加"math."
print(sqrt(16)) # 直接调用 sqrt,输出 4.0
print(pi) # 直接使用 pi 常量,输出 3.141592653589793
# 方式3:导入所有(不推荐)
from math import * # 导入 math 模块所有内容,容易引起命名冲突,不推荐使用
7. 默认参数的顺序规则(重要!)
Python语法规定:默认参数必须放在非默认参数的右侧,否则会报语法错误。
python
# 正确写法:非默认参数在左,默认参数在右
def func(a, b=10): # a没有默认值,b有默认值 → 正确
return a + b
# 错误写法:默认参数放在非默认参数左边 → 语法错误!
def func(a=10, b): # SyntaxError! a有默认值却放在b(无默认值)的左边
return a + b
为什么有这种规定?因为Python需要根据位置来匹配参数。如果默认参数在左边,调用 func(5) 时,Python无法判断这个 5 是传给 a(覆盖默认值)还是传给 b。
8. 可变参数 *args 和 **kwargs
有时候你事先不知道函数会接收多少个参数,可以使用可变参数。
python
# *param:收集剩余的所有"位置参数",打包成一个元组
def func(a, *args):
print(f"a = {a}") # 第一个参数正常接收
print(f"args = {args}") # 剩下的都收集到 args 元组中
func(1, 2, 3, 4)
# 输出:
# a = 1
# args = (2, 3, 4) ← 多余的2,3,4被装进元组
# **param:收集剩余的所有"关键字参数",打包成一个字典
def func2(a, **kwargs):
print(f"a = {a}")
print(f"kwargs = {kwargs}") # 关键字参数被收集为字典
func2(1, name="张三", age=18)
# 输出:
# a = 1
# kwargs = {'name': '张三', 'age': 18} ← 关键字参数变成了字典
记忆技巧 :* 一个星号 → 元组(tuple),** 两个星号 → 字典(dict)。
9. 可变对象 vs 不可变对象的参数传递
这是考试中的常见考点。函数调用时传递参数,实际传递的是对象的引用。但不同类型的对象表现不同:
- 不可变对象 (int、str、tuple):函数内部修改不会影响外部实参
- 可变对象 (list、dict、set):函数内部修改会影响外部实参
python
# 情况1:不可变对象(int)
def modify_num(n):
n = 100 # 修改局部变量,不会影响外部的实参
print(f"函数内部: n = {n}")
x = 10
modify_num(x) # 调用后,x仍然是10
print(f"函数外部: x = {x}")
# 输出:
# 函数内部: n = 100
# 函数外部: x = 10 ← x没变!
# 情况2:可变对象(list)
def modify_list(lst):
lst.append(4) # 修改列表内容,会影响外部的实参!
print(f"函数内部: lst = {lst}")
my_list = [1, 2, 3]
modify_list(my_list)
print(f"函数外部: my_list = {my_list}")
# 输出:
# 函数内部: lst = [1, 2, 3, 4]
# 函数外部: my_list = [1, 2, 3, 4] ← 外部实参也被改变了!
为什么会有这种区别? 因为可变对象传的是"同一份数据"的引用,函数内外的变量指向的是同一个列表对象;而不变对象的"修改"实际上创建了新对象,不会影响原来的。
10. lambda在sorted排序中的高级应用
lambda常与 sorted() 配合使用,对复杂结构进行排序。
python
# 示例1:字典按键排序
d = {'b': 2, 'a': 1, 'c': 3}
# d.items() 返回 [('b',2), ('a',1), ('c',3)],x[0]取键,x[1]取值
result = sorted(d.items(), key=lambda x: x[0]) # 按键(字母)排序
print(result) # [('a', 1), ('b', 2), ('c', 3)]
# 示例2:字典按值排序
result = sorted(d.items(), key=lambda x: x[1]) # 按值(数字)排序
print(result) # [('a', 1), ('b', 2), ('c', 3)]
# 示例3:列表中嵌套字典,按某个键排序
students = [
{'name': '张三', 'age': 20},
{'name': '李四', 'age': 18},
{'name': '王五', 'age': 22}
]
students.sort(key=lambda x: x['age']) # 按年龄升序排序
print(students)
# [{'name': '李四', 'age': 18}, {'name': '张三', 'age': 20}, {'name': '王五', 'age': 22}]
lambda x: x1 的含义 :x 代表列表中的每个元素(在这里是元组),x[1] 取出该元素下标为1的值作为排序依据。
11. global关键字
在函数内部,默认只能读取 全局变量的值,如果要对全局变量赋值修改 ,必须先用 global 声明。
python
count = 0 # 全局变量,定义在函数外部
def increment():
global count # 声明:我要修改全局变量 count
count = count + 1 # 修改全局变量,如果不声明global会报错
increment()
print(count) # 输出:1
# 对比:如果不加 global
def reset():
count = 0 # 这里创建了一个同名的局部变量,不是修改全局的count!
# 全局的count不受影响
注意 :如果只是读取全局变量(如 print(count)),不需要声明 global。只有赋值操作才需要。
12. zip() 函数
zip() 像"拉链"一样,将多个可迭代对象(如列表)的对应位置元素打包成元组。
python
# 两个列表对应位置打包
s1 = ['A', 'B', 'C']
s2 = [85, 92, 78]
result = list(zip(s1, s2))
print(result) # [('A', 85), ('B', 92), ('C', 78)]
# 常见用法:同时遍历多个列表
names = ['张三', '李四', '王五']
scores = [90, 85, 88]
for name, score in zip(names, scores):
print(f"{name}: {score}分")
# 输出:
# 张三: 90分
# 李四: 85分
# 王五: 88分
注意 :如果列表长度不一致,zip() 以最短的列表为准,多余的元素会被忽略。
二、例题精讲
【读程序写结果】例题1:函数调用
题目:阅读以下程序,写出运行结果。
python
def calc(a, b):
return a * 2 + b # 计算 a 的两倍加上 b 并返回结果
x = 3 # x = 3
y = 5 # y = 5
result = calc(y, x) # 调用 calc(y, x) 即 calc(5, 3),a=5, b=3,结果 5*2+3=13
print(result) # 输出结果 13
答案:13
解析 :调用 calc(y, x) 即 calc(5, 3),函数中 a=5, b=3,计算 5*2+3=13。注意参数传递时按照位置对应,而不是按变量名。
【读程序写结果】例题2:递归函数
题目:阅读以下程序,写出运行结果。
python
def func(n):
if n <= 1: # 【终止条件】如果 n <= 1,不再递归
return 1 # 直接返回 1
return n + func(n - 1) # 递归调用:计算 n + 前 n-1 个数的和
print(func(5)) # 调用 func(5),计算 5+4+3+2+1 = 15
答案:15
解析:这是一个递归求和的过程:
- func(5) = 5 + func(4)
- func(4) = 4 + func(3)
- func(3) = 3 + func(2)
- func(2) = 2 + func(1)
- func(1) = 1(终止条件)
- 逐层返回:func(2)=3, func(3)=6, func(4)=10, func(5)=15
所以结果是 5+4+3+2+1 = 15。
【补全程序题】例题3:lambda补全
题目:以下程序使用lambda表达式对列表按成绩降序排序,请补全空缺。
python
# 创建列表,用方括号 [] 包裹元素
students = [("张三", 85), ("李四", 92), ("王五", 78), ("赵六", 88)]
# 按成绩降序排序
# .sort() 原地排序(从小到大)
students.sort(key=lambda s: ________, reverse=________)
# print() 输出内容到屏幕上
print(students)
答案:
- 第1空:
s[1](取每个元组的第二个元素即成绩) - 第2空:
True(降序排序)
输出 :[('李四', 92), ('赵六', 88), ('张三', 85), ('王五', 78)]
【补全程序题】例题4:默认参数补全
题目:以下程序定义了一个计算商品总价的函数,请补全程序。
python
def total_price(price, quantity=________, discount=________):
"""计算总价,quantity默认为1,discount默认为0"""
# 计算表达式,结果赋值给 total
total = price * quantity
# 计算表达式,结果赋值给 total
total = total * (________ - discount)
return total
# 测试
print(total_price(100)) # 1件,无折扣 → 100
print(total_price(100, 3)) # 3件,无折扣 → 300
print(total_price(100, 2, 0.1)) # 2件,9折 → 180
答案:
- 第1空:
1 - 第2空:
0或0.0 - 第3空:
1或1.0 - 解析:当discount=0.1时,
(1 - 0.1) = 0.9,相当于打9折。
【编程题】例题5:模块导入编程
题目:编写程序,导入math模块,计算半径为5的圆的面积和周长(圆周率使用math.pi),结果保留2位小数。
参考代码:
python
import math # 导入 Python 的数学模块,里面包含 pi 和 sqrt 等函数和常量
r = 5 # 圆的半径
area = math.pi * r ** 2 # 圆的面积 = π × r²,math.pi 就是圆周率 π
circumference = 2 * math.pi * r # 圆的周长 = 2 × π × r
print(f"半径{r}的圆:") # 打印标题,f-string 将 r 的值嵌入到字符串中
print(f"面积 = {area:.2f}") # 输出面积,:.2f 表示保留 2 位小数
print(f"周长 = {circumference:.2f}") # 输出周长,同样保留 2 位小数
输出:
半径5的圆:
面积 = 78.54
周长 = 31.42
【编程题】例题6:递归阶乘编程
题目 :编写程序,使用递归函数计算 n!(n的阶乘),要求输入一个正整数n,输出n!的结果。阶乘定义:n! = n × (n-1) × (n-2) × ... × 1,且0! = 1。
参考代码:
python
def factorial(n):
if n == 0: # 【终止条件】0! = 1,这是递归的出口
return 1 # 返回 1,不再继续递归
return n * factorial(n - 1) # 递归调用:n! = n × (n-1)!
n = int(input("请输入一个正整数: ")) # 让用户输入一个正整数,int() 将字符串转为整数
result = factorial(n) # 调用递归函数计算 n 的阶乘
print(f"{n}! = {result}") # 输出结果,例如 "6! = 720"
示例运行:
请输入一个正整数: 6
6! = 720
三、考试提示
针对"读程序写结果"题型
- 函数调用题:注意看清楚实参和形参的对应关系,特别是位置参数和关键字参数。
- 递归题:画"递归调用树",从最外层向内层展开,找到终止条件后再逐层返回。建议用表格记录每层调用的参数和返回值。
针对"补全程序题"题型
- lambda表达式常与
sort()、filter()、map()结合使用。sort(key=lambda x: ...)中的key指定排序依据。 - 默认参数在定义时赋值,调用时可以省略。默认参数必须放在位置参数后面。
- 注意观察上下文,推断空白处应该填写什么功能。
针对"编程题"题型
- 递归函数必须包含终止条件,这是最重要的得分点。
- 模块导入要记住常见模块(math、random、datetime等)的导入方式。
- 函数定义时
def后面要加冒号,函数体要缩进。 - 建议先写函数定义,再写调用测试代码。
四、课后练习
练习1(读程序写结果)
题目:阅读以下程序,写出运行结果。
python
def power(base, exp=2): # 定义函数:base 为底数,exp 为指数(默认值为 2,即默认求平方)
result = 1 # 初始化结果为 1
for i in range(exp): # 循环 exp 次(指数是多少就乘多少次)
result *= base # 每次循环将 result 乘以 base(相当于累乘)
return result # 返回最终的计算结果
print(power(3)) # 调用 power(3),exp 用默认值 2,计算 3² = 9
print(power(3, 3)) # 调用 power(3, 3),计算 3³ = 27
答案:
9
27
解析 :第一次调用 power(3) 使用默认参数exp=2,计算3²=9;第二次调用 power(3, 3) 传入exp=3,计算3³=27。
练习2(补全程序题)
题目:以下程序用递归函数实现字符串反转,请补全空缺。
python
def reverse_str(s):
# if 判断 len(s) <= ________ 是否成立
if len(s) <= ________:
return s
return reverse_str(________) + ________
# print() 输出内容到屏幕上
print(reverse_str("Python"))
答案:
- 第1空:
1 - 第2空:
s[1:] - 第3空:
s[0]
解析 :当字符串长度<=1时直接返回;否则递归处理去掉首字符后的子串,再将首字符拼接到末尾。reverse_str("Python") 的递归过程:
- reverse("Python") → reverse("ython") + "P"
- reverse("ython") → reverse("thon") + "y"
- ... 最终得到 "nohtyP"
练习3(编程题)
题目 :编写程序,定义一个函数 is_prime(n) 判断正整数n是否为质数,并在主程序中输出1到100之间所有的质数。
参考代码:
python
def is_prime(n):
if n < 2: # 小于 2 的数(0 和 1)不是质数
return False # 直接返回 False
for i in range(2, int(n ** 0.5) + 1): # 只需检查 2 到 √n 之间的整数
if n % i == 0: # 如果 n 能被 i 整除(余数为 0)
return False # 说明 n 不是质数,返回 False
return True # 都没有被整除,说明 n 是质数,返回 True
print("1到100之间的质数有:") # 打印提示文字
for num in range(1, 101): # 遍历 1 到 100 的每一个数
if is_prime(num): # 调用 is_prime 判断这个数是否为质数
print(num, end=" ") # 如果是质数,就输出它,末尾用空格代替换行
输出:
1到100之间的质数有:
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97
五、本章模拟练习
模拟1(读程序写结果)
题目:阅读以下程序,写出运行结果。
python
def func(a, b=2, c=3): # 定义函数:a 是位置参数,b 默认 2,c 默认 3
return a + b * c # 返回 a + b × c 的计算结果
print(func(1)) # 只传 a=1,b 和 c 用默认值 → 1 + 2×3 = 7
print(func(1, 3)) # 传 a=1, b=3,c 用默认值 3 → 1 + 3×3 = 10
print(func(1, 3, 4)) # 传 a=1, b=3, c=4 → 1 + 3×4 = 13
答案:
7
10
13
解析:
- func(1):a=1, b=2, c=3 → 1+2×3=7
- func(1,3):a=1, b=3, c=3(默认) → 1+3×3=10
- func(1,3,4):a=1, b=3, c=4 → 1+3×4=13
模拟2(补全程序题)
题目:以下程序使用递归函数计算两个数的最大公约数(辗转相除法),请补全空缺。
python
def gcd(a, b):
# if 判断 b == ________ 是否成立
if b == ________:
return ________
return gcd(b, ________)
# print() 输出内容到屏幕上
print(gcd(48, 18))
答案:
- 第1空:
0 - 第2空:
a - 第3空:
a % b
解析 :辗转相除法定义:gcd(a,b) = a(当b=0),否则gcd(a,b) = gcd(b, a%b)。
计算过程:gcd(48,18) → gcd(18,12) → gcd(12,6) → gcd(6,0) → 返回6。
模拟3(编程题)
题目 :编写程序,定义一个函数 avg_score(scores),接收一个成绩列表,计算并返回平均分(去掉一个最高分和一个最低分后的平均值)。主程序测试 [85, 92, 78, 90, 88, 95]。
参考代码:
python
def avg_score(scores):
if len(scores) <= 2: # 如果成绩个数不超过 2 个
return sum(scores) / len(scores) # 直接返回平均值(无法再去掉最高最低)
sorted_scores = sorted(scores) # 将成绩从小到大排序
trimmed = sorted_scores[1:-1] # 切片去掉第一个(最低分)和最后一个(最高分)
return sum(trimmed) / len(trimmed) # 返回剩余成绩的平均值
scores = [85, 92, 78, 90, 88, 95] # 测试用的成绩列表
result = avg_score(scores) # 调用函数计算去掉最高最低后的平均分
print(f"去掉最高最低分后的平均分: {result:.2f}") # 输出结果,保留 2 位小数
输出 :去掉最高最低分后的平均分: 88.75
计算过程:排序后 78, 85, 88, 90, 92, 95,去掉78和95,剩余 85, 88, 90, 92,平均 (85+88+90+92)/4 = 88.75。
第7章 文件操作
一、知识讲解
1. 打开文件:open()函数
python
# open() 是Python内置的打开文件的函数
# 第一个参数是文件名,第二个参数是打开模式,encoding指定文件编码
file = open(文件名, 模式, encoding='编码')
# 常用模式说明:
# 'r' - 只读模式(默认值),如果文件不存在会报错(FileNotFoundError)
# 'w' - 写入模式,文件不存在则自动创建新文件,文件存在则覆盖原有内容
# 'a' - 追加模式(append),文件不存在则自动创建,存在则在文件末尾追加内容
# 'rb' - 二进制只读模式,用于读取图片、视频等非文本文件
# 'wb' - 二进制写入模式,用于写入图片、视频等非文本文件
# 使用 open() 打开文件后,一定要记得调用 close() 关闭文件
# 否则文件资源可能被长时间占用,其他程序可能无法访问该文件
file.close()
2. with语句(推荐)
使用 with 打开文件可以自动关闭文件,不需要手动调用 close()。
python
# with 语句是Python推荐的打开文件方式
# 用 with 打开文件的最大好处是:不需要手动调用 close()
# 程序执行完 with 内部的代码块后,文件会被自动关闭
with open('file.txt', 'r', encoding='utf-8') as f:
# f.read() 一次性读取文件的全部内容,返回一个字符串
content = f.read()
# 执行到这里时,文件已经自动关闭了,无需写 f.close()
3. 读取文件
python
# 方法1:read() - 一次性读取文件的全部内容(返回一个字符串)
# 计算表达式,结果赋值给 with open('file.txt', 'r', encoding
with open('file.txt', 'r', encoding='utf-8') as f:
# read() 会把整个文件内容当作一个字符串返回,包括其中的换行符
content = f.read()
# 方法2:readline() - 逐行读取,每次只读取一行
# 计算表达式,结果赋值给 with open('file.txt', 'r', encoding
with open('file.txt', 'r', encoding='utf-8') as f:
line = f.readline() # 先读取第一行(返回字符串,包含行尾的换行符)
while line: # 如果 line 不是空字符串,说明还没读完,继续循环
print(line, end='') # 打印当前行,end='' 表示不额外换行(因为line本身已带换行符)
line = f.readline() # 继续读取下一行
# 方法3:readlines() - 一次性读取所有行,返回一个列表
# 计算表达式,结果赋值给 with open('file.txt', 'r', encoding
with open('file.txt', 'r', encoding='utf-8') as f:
# 文件的每一行作为列表中的一个元素,每行末尾包含换行符
lines = f.readlines() # 例如:['第一行\n', '第二行\n', '第三行\n']
4. 写入文件
python
# 'w' 模式是覆盖写入:如果文件已存在,会清空原内容重新写入
# 计算表达式,结果赋值给 with open('file.txt', 'w', encoding
with open('file.txt', 'w', encoding='utf-8') as f:
f.write('Hello, World!\n') # write() 写入字符串,注意:不会自动换行,需要手动写 \n
f.write('第二行内容\n') # 再次写入,会在第一行后面接着写
# 'a' 模式是追加写入:在文件末尾添加新内容,不会影响原内容
# 计算表达式,结果赋值给 with open('file.txt', 'a', encoding
with open('file.txt', 'a', encoding='utf-8') as f:
f.write('追加的内容\n') # 新内容会被添加到文件的末尾
5. CSV文件读写
CSV(逗号分隔值)是一种常见的数据格式。
python
# 写入CSV
# 导入 csv 库
import csv
with open('data.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow(['姓名', '年龄', '成绩']) # 写入表头
writer.writerow(['张三', 18, 92])
writer.writerow(['李四', 19, 85])
# 读取CSV
# 导入 csv 库
import csv
# 计算表达式,结果赋值给 with open('data.csv', 'r', encoding
with open('data.csv', 'r', encoding='utf-8') as f:
reader = csv.reader(f)
# for 循环:遍历 reader 中的每个元素
for row in reader:
print(row) # row是一个列表
6. 文件模式比较
| 模式 | 文件存在 | 文件不存在 | 写入位置 | 是否清空原内容 |
|---|---|---|---|---|
| 'r' | 正常读取 | 报错 | - | - |
| 'w' | 覆盖写入 | 新建 | 文件开头 | 是 |
| 'a' | 追加写入 | 新建 | 文件末尾 | 否 |
7. open()打开模式的完整表格
| 模式 | 说明 | 文件类型 | 操作 |
|---|---|---|---|
'r' |
只读(默认) | 文本 | 文件必须存在,否则报错 |
'w' |
覆盖写入 | 文本 | 文件不存在则新建,存在则清空后写入 |
'a' |
追加写入 | 文本 | 文件不存在则新建,存在则在末尾追加 |
'r+' |
读写 | 文本 | 文件必须存在,可读可写(不截断) |
'w+' |
读写 | 文本 | 文件不存在则新建,存在则清空后读写 |
'a+' |
读写追加 | 文本 | 文件不存在则新建,写入从末尾开始 |
'rb' |
二进制只读 | 二进制 | 用于读取图片、视频、音频等非文本文件 |
'wb' |
二进制写入 | 二进制 | 用于写入图片、视频、音频等非文本文件 |
8. 绝对路径 vs 相对路径
文件路径是告诉计算机"文件在哪里"的地址,分为两种:
- 绝对路径 :从盘符开始的完整路径。例如
C:\Users\zhang\Desktop\data.txt。无论当前程序在哪个目录运行,绝对路径都指向同一个文件。 - 相对路径 :从当前工作目录开始的路径。例如
data.txt(当前目录下的文件)、.\data\file.txt(当前目录data子文件夹下的文件)、..\file.txt(上级目录下的文件)。
python
# 绝对路径:以盘符开头
with open('C:\\Users\\zhang\\Desktop\\data.txt', 'r', encoding='utf-8') as f:
content = f.read()
# 相对路径:从当前目录开始
with open('data.txt', 'r', encoding='utf-8') as f: # 当前目录下的data.txt
content = f.read()
with open('.\\data\\file.txt', 'r', encoding='utf-8') as f: # 当前目录下data文件夹中的file.txt
content = f.read()
考试中一般使用相对路径,因为考试环境中的文件路径是固定的。如果题目没有指定路径,默认文件就在当前目录下。
9. write() vs writelines() 的区别
很多同学容易混淆这两个方法,它们的区别如下:
python
# write():写入一个字符串
with open('test.txt', 'w', encoding='utf-8') as f:
f.write('Hello') # 写一个字符串
f.write('World') # 再写一个字符串,不会自动换行
# 文件内容:HelloWorld
# writelines():写入一个字符串列表(每个元素作为一行)
lines = ['第一行\n', '第二行\n', '第三行\n']
with open('test.txt', 'w', encoding='utf-8') as f:
f.writelines(lines) # 把列表中的每个字符串依次写入
# 文件内容:
# 第一行
# 第二行
# 第三行
关键区别:
write(字符串)------ 参数是一个字符串writelines(列表)------ 参数是一个字符串列表(或任何可迭代对象)- 两者都不会自动添加换行符 ,需要手动在字符串末尾加
\n
10. CSV写入中的 newline='' 参数
在Windows系统上写入CSV文件时,如果不在 open() 中指定 newline='',写入的每一行之间会多出空行。
python
import csv
# 错误写法:不指定newline,在Windows上会产生多余空行
with open('data.csv', 'w', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow(['姓名', '年龄'])
writer.writerow(['张三', 18])
# 结果:每行之间会多一个空行(Windows上换行符被重复处理)
# 正确写法:指定 newline='',避免多余空行
with open('data.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow(['姓名', '年龄'])
writer.writerow(['张三', 18])
# 结果:正常,没有多余空行
原因 :Windows系统默认的换行符是 \r\n,而CSV的 writer 也会写入 \r\n,如果不指定 newline='',两者叠加就会产生 \r\r\n,导致多出一个空行。newline='' 告诉Python不要自动转换换行符,让CSV模块自己处理。
11. 常见异常类型
写程序时难免会出错,Python用"异常"(Exception)来告诉你出了什么错。考试中常见的异常类型:
| 异常类型 | 含义 | 触发场景举例 |
|---|---|---|
ZeroDivisionError |
除以零错误 | 10 / 0 ------ 数学上不允许除以零 |
IOError |
输入/输出错误 | 尝试打开一个不存在的文件 |
IndexError |
索引越界错误 | lst = [1,2,3]; lst[5] ------ 访问了不存在的下标 |
KeyError |
键不存在错误 | d = {'a':1}; d['b'] ------ 字典中没有这个键 |
NameError |
变量名不存在错误 | print(x) ------ 变量x还没有定义 |
12. try-except-else 异常处理结构
程序遇到异常会直接崩溃,但我们可以用 try-except 来"捕获"异常,让程序优雅地处理错误。
python
# 基本结构
try:
# 尝试执行的代码(可能出错的代码)
num = int(input("请输入一个数字: "))
result = 10 / num
except ZeroDivisionError:
# 如果 try 中发生了 ZeroDivisionError,就执行这里的代码
print("错误:不能除以 0!")
except ValueError:
# 如果 try 中发生了 ValueError(比如输入的不是数字),执行这里
print("错误:请输入有效的数字!")
else:
# 如果 try 中没有任何异常发生,执行这里的代码
print(f"计算结果: {result}")
三个部分的执行逻辑:
try块:先执行,如果不出错就继续except块:只有当try中发生了对应类型的异常时才执行else块:只有当try中完全没有异常时才执行(相当于"一切顺利"的后续操作)
注意 :else 是可选的,不是必须写的。try-except 也可以只写 try-except 而不写 else。
二、例题精讲
【读程序写结果】例题1:read() 读程序
题目 :假设当前目录下有一个名为 data.txt 的文件,内容如下:
Python
Java
C++
阅读以下程序,写出运行结果。
python
with open('data.txt', 'r', encoding='utf-8') as f: # 用只读模式打开文件 data.txt,编码为utf-8
content = f.read() # read() 一次性读取文件的全部内容,包括所有换行符
print(repr(content)) # repr() 显示字符串的"官方表示形式",可以看到 \n 等转义字符
答案 :'Python\nJava\nC++\n' 或
Python
Java
C++
解析 :read() 一次性读取文件的全部内容,包括换行符。repr() 函数可以显示字符串中的转义字符。最后一行末尾也有换行符,所以结果是 'Python\nJava\nC++\n'。
【读程序写结果】例题2:模式比较
题目:阅读以下程序,写出运行结果。
python
# 假设当前b.txt内容为:"Hello"
with open('b.txt', 'w', encoding='utf-8') as f: # 以'w'(覆盖写入)模式打开b.txt
f.write('World') # 写入"World",由于是'w'模式,原内容"Hello"被完全覆盖
with open('b.txt', 'r', encoding='utf-8') as f: # 以'r'(只读)模式重新打开b.txt
print(f.read()) # 读取文件内容并打印 → 输出:World(原Hello已被覆盖)
答案 :World
解析 :用 'w' 模式打开文件时,会覆盖原有内容。原文件内容是"Hello",以'w'模式写入"World"后,原内容被完全覆盖。所以读取时只读到"World"。
【补全程序题】例题3:with语句补全
题目:以下程序将列表中的内容逐行写入文件,请补全空缺。
python
fruits = ["苹果", "香蕉", "橙子", "葡萄"] # 定义一个水果列表,包含4种水果名称
________ open('fruits.txt', 'w', encoding='utf-8') as f: # 【填空】用with语句打开文件
for fruit in ________: # 【填空】遍历fruits列表中的每个水果
________(fruit + '\n') # 【填空】将水果名称加上换行符后写入文件
# 验证写入结果
with open('fruits.txt', 'r', encoding='utf-8') as f: # 重新以只读模式打开,验证写入内容
print(f.read()) # 读取并打印文件全部内容
答案:
- 第1空:
with - 第2空:
fruits - 第3空:
f.write或f.write
【补全程序题】例题4:CSV读写补全
题目:以下程序将学生成绩写入CSV文件,再读取并计算平均分,请补全。
python
import csv # 导入csv模块,用于处理CSV格式文件
# 写入数据
students = [ # 定义一个嵌套列表,每个子列表包含姓名和成绩
['张三', 85],
['李四', 92],
['王五', 78]
]
with open('scores.csv', 'w', newline='', encoding='utf-8') as f: # 以覆盖写入模式打开CSV文件
writer = csv.________(f) # 【填空】创建CSV写入器对象
writer.writerow(['姓名', '成绩']) # 先写入表头行:第一列"姓名",第二列"成绩"
for s in students: # 遍历每个学生的数据
writer.________(s) # 【填空】将当前学生的数据作为一行写入CSV
# 读取并计算平均分
total = 0 # 用于累加所有学生的成绩总分
count = 0 # 用于统计学生人数
with open('scores.csv', 'r', encoding='utf-8') as f: # 以只读模式打开CSV文件
reader = csv.________(f) # 【填空】创建CSV读取器对象
next(reader) # next() 跳过第一行(表头),从第二行开始才是真正的数据
for row in reader: # 遍历数据行,每行是一个列表,如 ['张三', '85']
total += int(row[________]) # 【填空】将成绩(第2列,下标为1)转为整数并累加
count += 1 # 学生人数加1
avg = total / count # 总分 ÷ 人数 = 平均分
print(f"平均分: {avg:.1f}") # 打印平均分,保留1位小数
答案:
- 第1空:
writer - 第2空:
writerow - 第3空:
reader - 第4空:
1(成绩在第2列,下标为1)
【编程题】例题5:文件统计编程
题目 :假设文件 article.txt 内容如下:
Hello Python
Python is great
I love Python
编写程序,读取该文件内容,统计:
- 文件的总行数
- 文件中单词 "Python" 出现的次数
- 将统计结果写入文件
result.txt
参考代码:
python
# 统计
line_count = 0 # 记录文件的总行数,初始为0
word_count = 0 # 记录单词"Python"出现的次数,初始为0
with open('article.txt', 'r', encoding='utf-8') as f: # 以只读模式打开文章文件
for line in f: # 逐行遍历文件(这是Python推荐的逐行读取方式,内存友好)
line_count += 1 # 每读取一行,行数计数器加1
word_count += line.count('Python') # 统计当前行中"Python"出现的次数,累加到总次数
# 写入结果
with open('result.txt', 'w', encoding='utf-8') as f: # 以覆盖写入模式打开结果文件
f.write(f"总行数: {line_count}\n") # 写入统计结果:总行数
f.write(f"Python出现次数: {word_count}\n") # 写入统计结果:Python出现次数
print("统计完成,结果已写入result.txt") # 在屏幕上显示提示信息
输出(result.txt内容):
总行数: 3
Python出现次数: 3
三、考试提示
针对"读程序写结果"题型
read()返回整个字符串(含换行符);readline()只读一行;readlines()返回字符串列表。- 注意
'w'模式会覆盖 文件,'a'模式会追加 。未指定模式时默认为'r'。 - 注意编码问题:windows系统默认编码可能不是utf-8,考试中通常会指定。
针对"补全程序题"题型
with open(...) as 变量名:是固定写法,常考关键字with和as。- CSV操作三步:
csv.writer(f)或csv.reader(f)→ 调用writerow()或遍历reader。 - 文件写入时要手动加
\n换行,write()不会自动换行。 next(reader)用于跳过CSV表头行。
针对"编程题"题型
- 文件统计类题目:常用
for line in f:逐行遍历。 - 写入文件时注意模式选择('w'覆盖 vs 'a'追加)。
- 使用
with语句是加分项,体现了代码规范性。 - 文件路径中不要忘了编码参数
encoding='utf-8'。
四、课后练习
练习1(读程序写结果)
题目 :假设当前目录下 score.txt 内容为:
88
92
75
阅读以下程序,写出运行结果。
python
total = 0 # 用于累加所有分数的总和,初始为0
count = 0 # 用于统计有多少个分数,初始为0
with open('score.txt', 'r', encoding='utf-8') as f: # 以只读模式打开分数文件
for line in f: # 逐行读取文件内容
total += int(line.strip()) # strip()去掉行尾换行符,int()转为整数,然后累加到total
count += 1 # 每读取一行,计数器加1
print(total / count) # 总分 ÷ 人数 = 平均分,打印结果
答案 :85.0
解析 :程序逐行读取文件内容,将每行字符串转为整数后累加。88+92+75=255,255÷3=85.0。strip() 去除每行末尾的换行符。
练习2(补全程序题)
题目:以下程序实现文件复制功能(将source.txt的内容复制到target.txt),请补全。
python
with ________('source.txt', 'r', encoding='utf-8') as f1: # 【填空】用open打开源文件(只读模式)
content = ________ # 【填空】用read()读取源文件的全部内容
with open('target.txt', 'w', encoding='utf-8') as f2: # 以覆盖写入模式打开目标文件
________ # 【填空】将读取到的内容写入目标文件
print("文件复制完成") # 提示用户操作完成
答案:
- 第1空:
open - 第2空:
f1.read() - 第3空:
f2.write(content)
练习3(编程题)
题目 :编写程序,接收用户输入的一行文本,将其写入文件 note.txt(追加模式),然后读取该文件的所有内容并显示在屏幕上。
参考代码:
python
text = input("请输入一行文本: ") # 让用户输入一行文本,存入变量text
# 追加写入
with open('note.txt', 'a', encoding='utf-8') as f: # 'a'模式:在文件末尾追加内容,不影响原内容
f.write(text + '\n') # 将用户输入的文本写入文件,后面加换行符
# 读取全部内容
with open('note.txt', 'r', encoding='utf-8') as f: # 以只读模式打开文件,查看完整内容
content = f.read() # 一次性读取所有内容,包括之前写入的所有行
print("文件当前内容:") # 打印提示信息
print(content) # 打印文件的全部内容
五、本章模拟练习
模拟1(读程序写结果)
题目 :假设当前目录下 names.txt 内容为:
Alice
Bob
Charlie
David
阅读以下程序,写出运行结果。
python
with open('names.txt', 'r', encoding='utf-8') as f: # 以只读模式打开names.txt文件
lines = f.readlines() # readlines() 读取所有行,返回一个列表,每行是一个元素
count = len(lines) # len() 获取列表长度,即文件的总行数
print(f"总共有 {count} 行") # 使用f-string格式化输出总行数
print(f"第2行是: {lines[1].strip()}") # lines[1]是第2行(列表下标从0开始),strip()去掉换行符
答案:
总共有 4 行
第2行是: Bob
模拟2(补全程序题)
题目 :以下程序从 data.txt 中读取数字,计算其中正数的和并输出,请补全。
python
# data.txt 内容:-5 12 -3 8 0 -1 15 7
total = 0 # 用于累加正数的和,初始为0
with open('data.txt', 'r', encoding='utf-8') as f: # 以只读模式打开数据文件
content = f.read() # read() 一次性读取全部内容,得到一个字符串
numbers = ________.split() # 【填空】用split()将字符串按空格拆分成字符串列表
for num_str in numbers: # 遍历列表中的每个数字字符串
num = int(________) # 【填空】将当前数字字符串转为整数
if num ________ 0: # 【填空】判断是否为正数(大于0)
total += num # 如果是正数,累加到total
print(f"正数之和为: {total}") # 打印所有正数的和
答案:
- 第1空:
content - 第2空:
num_str - 第3空:
>
模拟3(编程题)
题目 :编写程序,生成一个包含10个1~100之间随机整数的文件 random.txt,每行一个数。然后再读取该文件,找出其中的最大值和最小值。
参考代码:
python
import random # 导入random模块,用于生成随机数
# 生成并写入随机数
with open('random.txt', 'w', encoding='utf-8') as f: # 以覆盖写入模式打开文件
for i in range(10): # 循环10次,生成10个随机数
num = random.randint(1, 100) # randint(1,100) 生成1到100之间的随机整数
f.write(str(num) + '\n') # 将数字转为字符串后写入文件,每行一个数
# 读取并找最大最小值
with open('random.txt', 'r', encoding='utf-8') as f: # 以只读模式打开文件
nums = [] # 创建一个空列表,用于存储所有随机数
for line in f: # 逐行读取文件
nums.append(int(line.strip())) # 去掉换行符,转为整数,添加到列表末尾
print(f"生成的随机数: {nums}") # 打印所有随机数
print(f"最大值: {max(nums)}") # max() 找出列表中的最大值
print(f"最小值: {min(nums)}") # min() 找出列表中的最小值