程序设计与计算思维¶

Computer Programming and Computational Thinking

第 14 讲:离散与连续¶

2025—2026学年度春季学期

清华大学 韩文弢

Python 文件操作¶

文件与路径¶

文件是存储在存储设备上的数据集合,是数据持久化的基本单位。文件的本质是一个字节序列,以及对这个文件本身的描述信息(如文件名、创建日期等)。

路径用于定位文件在文件系统中的位置:

  • 绝对路径:从根目录开始的完整路径
  • 相对路径:相对于当前工作目录的路径
    • . 表示当前目录,如 ./data.txt
    • .. 表示上级目录,如 ../output/result.csv

不同操作系统的路径差异:

系统 路径分隔符 绝对路径示例
Windows \ (反斜杠) C:\Users\name\data.txt
Linux/macOS / (正斜杠) /Users/name/data.txt

Python 使用 os.path.join() 或 pathlib 可自动处理跨平台路径。

In [1]:
import os

print("当前工作目录:", os.getcwd())
print("路径拼接:", os.path.join('folder', 'subfolder', 'file.txt'))
print("路径存在检查:", os.path.exists('.'))
当前工作目录: /Users/hanwentao/repo/teaching/cpct/lectures
路径拼接: folder/subfolder/file.txt
路径存在检查: True

打开文件:open() 函数¶

open() 是 Python 内置函数,用于打开文件并返回文件对象:

open(file, mode='r')

常用模式:

模式 说明
'r' 读取(默认)
'w' 写入(覆盖)
'a' 追加
'x' 创建(文件存在则报错)
'b' 二进制模式
't' 文本模式(默认)

with 语句¶

传统方式需要手动关闭文件,容易遗漏或出错:

In [2]:
f = open('demo.txt', 'w')
f.write('Hello, Python!\n')
f.write('文件操作示例\n')
f.close()

f = open('demo.txt')
print(f.read())
f.close()
Hello, Python!
文件操作示例

问题:如果 write() 过程中发生异常,close() 不会被调用,导致资源泄漏。

with 语句解决方案:

with open('file.txt') as f:
    content = f.read()
# 文件在此自动关闭,即使发生异常

优点:

  • 自动调用 f.close()
  • 代码更简洁
  • 异常安全
In [3]:
with open('demo.txt', 'w') as f:
    f.write('Hello, Python!\n')
    f.write('文件操作示例\n')

print("文件已写入")

with open('demo.txt') as f:
    content = f.read()
    print("文件内容:")
    print(content)
文件已写入
文件内容:
Hello, Python!
文件操作示例

文件对象的常用方法¶

方法 说明
read(size) 读取指定字节数,默认全部
readline() 读取一行
readlines() 读取所有行,返回列表
write(s) 写入字符串
writelines(lines) 写入字符串列表
seek(offset) 移动文件指针
tell() 返回当前指针位置
In [4]:
with open('demo.txt') as f:
    print("逐行读取:")
    for i, line in enumerate(f, 1):
        print(f"  第{i}行: {line.strip()}")
逐行读取:
  第1行: Hello, Python!
  第2行: 文件操作示例

pathlib 模块¶

pathlib 提供面向对象的路径操作方式:

In [5]:
from pathlib import Path

p = Path('.')
print("当前目录文件:", [f.name for f in p.iterdir() if f.is_file()][:5])

file_path = Path('demo.txt')
print(f"\n文件名: {file_path.name}")
print(f"文件后缀: {file_path.suffix}")
print(f"父目录: {file_path.parent}")
print(f"是否存在: {file_path.exists()}")
当前目录文件: ['lec14.ipynb', 'demo.txt', '.DS_Store', 'lec10.ipynb', 'lec09.ipynb']

文件名: demo.txt
文件后缀: .txt
父目录: .
是否存在: True
In [6]:
Path('output').mkdir(exist_ok=True)

output_file = Path('output') / 'result.txt'
output_file.write_text('使用 pathlib 写入数据\n')

print("读取内容:", output_file.read_text().strip())
读取内容: 使用 pathlib 写入数据

文件操作小结¶

  • 使用 with 语句确保文件正确关闭
  • 推荐使用 pathlib 进行路径操作
  • 常用操作:读取 (read)、写入 (write)、追加 (mode='a')
In [7]:
import os
os.remove('demo.txt')
os.remove('output/result.txt')
os.rmdir('output')
print("清理完成")
清理完成

离散与连续¶

In [8]:
# 准备工作
import numpy as np
import matplotlib.pyplot as plt
import networkx as nx  # 图(网络)处理包
from ipywidgets import interact, FloatSlider, IntSlider

plt.rcParams['font.sans-serif'] = ['Noto Sans CJK SC']

离散数学¶

  • 离散数学: 与有限或可数的孤立值相关联。
  • 示例: 集合 $\{1,...,n\}$,整数集。

离散数学对象示例:

  • 有限集合: $[1,2,...,100]$
  • 无限离散集合: 整数 $\mathbb{Z} = \{\ldots,-2,-1,0,1,2,\ldots\}$
  • 图结构 (Graphs)
In [9]:
# 生成并绘制 Barabási-Albert 图
G = nx.barabasi_albert_graph(150, 2)
plt.figure(figsize=(8, 8))
nx.draw(G, node_size=20, node_color='steelblue', edge_color='gray', alpha=0.7)
plt.title('Barabási-Albert 图 (n=150, m=2)')
plt.show()
No description has been provided for this image

连续数学¶

  • 涉及整个区间或实数轴。
  • 数学分析:严格处理连续。

连续实数轴:

包含所有实数,无缝隙地延伸。

In [10]:
# 绘制连续实数轴
fig, ax = plt.subplots(figsize=(8, 1))
ax.axhline(y=0, color='black', linewidth=3)
ax.set_xlim(-5, 5)
ax.set_ylim(-0.5, 0.5)
ax.set_yticks([])
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['left'].set_visible(False)
ax.set_xlabel('连续实数轴')
plt.show()
No description has been provided for this image

离散与连续的桥梁¶

  • 离散和连续间的关系是数学的核心主题。
  • 多数连续概念都有对应的离散形式。

案例 1:索引与函数求值¶

类比:$v_i$(v 的第 i 个元素)vs. $f(x)$(在 x 处求值 f):

  • 索引:从序列中提取元素
  • 函数求值:映射或计算

向量实际上是一个离散函数,其参数可以取值 i = 1,2,...,n,而求值结果就是 $v_i$。

事实上,考虑一个 range 对象如 range(2, 21, 2):

  • 可以看作是向量 $[2,4,...,20]$ 的简写
  • 对这个"类似向量"的东西进行索引时,实际上是在显式地求值一个函数,即 $i \to 2i$。
In [11]:
v = list(range(2, 21, 2))
print('range 展开为向量:', v)
range 展开为向量: [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
In [12]:
print('从列表中提取第7个元素 (索引6):', v[6])
从列表中提取第7个元素 (索引6): 14
In [13]:
r = range(2, 21, 2)
print('从 range 对象中获取第7个元素:', r[6])
从 range 对象中获取第7个元素: 14
In [14]:
def f(x):
    return 2 * x

print('f(7) =', f(7))
f(7) = 14

案例 2:逼近圆的面积¶

经典思想:使用离散的多边形,逼近连续的圆形边界。

In [15]:
def plot_inscribed_polygon(s):
    """绘制圆内接正多边形"""
    fig, ax = plt.subplots(figsize=(8, 8))
    
    # 绘制圆
    theta = np.linspace(0, 2*np.pi, 1000)
    ax.plot(np.cos(theta), np.sin(theta), 'k-', linewidth=2, label='圆')
    
    # 绘制内接多边形
    angles = np.linspace(0, 2*np.pi, s+1)
    x = np.cos(angles)
    y = np.sin(angles)
    ax.fill(x, y, alpha=0.3, color='blue', label=f'{s}边形')
    ax.plot(x, y, 'b-', linewidth=2)
    
    # 计算面积
    area = (s/2) * np.sin(2*np.pi/s)
    error = np.pi - area
    
    ax.set_xlim(-1.2, 1.2)
    ax.set_ylim(-1.2, 1.2)
    ax.set_aspect('equal')
    ax.grid(True, alpha=0.3)
    ax.legend(loc='upper right')
    ax.set_title(f'边数: {s}\n多边形面积: {area:.6f}\nπ = {np.pi:.6f}\n误差: {error:.6f}', 
                fontsize=12)
    plt.tight_layout()
    plt.show()

plot_inscribed_polygon(6)
No description has been provided for this image
In [16]:
# 创建交互式演示
interact(plot_inscribed_polygon, 
         s=IntSlider(min=3, max=100, step=1, value=6, description='边数:'));
interactive(children=(IntSlider(value=6, description='边数:', min=3), Output()), _dom_classes=('widget-interact'…

案例 3:正方形网格逼近¶

如果不用多边形,基于离散正方形网格叠加也能近似连续的圆面积。

In [17]:
def plot_inscribed_squares(s):
    """使用内接正方形逼近圆的面积"""
    fig, ax = plt.subplots(figsize=(8, 8))
    
    # 绘制圆
    theta = np.linspace(0, 2*np.pi, 1000)
    ax.plot(np.cos(theta), np.sin(theta), 'k-', linewidth=3)
    
    # 绘制网格
    h = 1/s
    for i in range(-s, s+1):
        ax.plot([i/s, i/s], [-1, 1], 'g-', linewidth=0.5, alpha=0.5)
        ax.plot([-1, 1], [i/s, i/s], 'g-', linewidth=0.5, alpha=0.5)
    
    # 计算并绘制内接正方形
    a = 0  # 计数器
    
    # 中心大正方形
    xx = int(np.floor(np.sqrt(2)/(2*h)))
    x = xx * h
    y = x
    square = plt.Rectangle((-x, -y), 2*x, 2*y, color='red', alpha=0.7)
    ax.add_patch(square)
    a += (2*xx)**2
    
    # 其他小正方形
    for i in range(-s, -xx):
        for j in range(-s, 0):
            x = i * h
            y = j * h
            if (x**2 + y**2 <= 1 and (x+h)**2 + (y+h)**2 <= 1 and 
                x**2 + (y+h)**2 <= 1 and (x+h)**2 + y**2 <= 1):
                # 绘制8个对称位置的正方形
                for xi, yi in [(x, y), (-x-h, y), (x, -y-h), (-x-h, -y-h),
                              (y, x), (y, -x-h), (-y-h, x), (-y-h, -x-h)]:
                    square = plt.Rectangle((xi, yi), h, h, color='blue', alpha=0.7)
                    ax.add_patch(square)
                a += 8
    
    area_approx = a * h**2
    
    ax.set_xlim(-1.1, 1.1)
    ax.set_ylim(-1.1, 1.1)
    ax.set_aspect('equal')
    ax.set_title(f's = {s}\n正方形数量: {a}\n面积估计: {area_approx:.6f} = {area_approx/np.pi:.6f}π', 
                fontsize=12)
    plt.tight_layout()
    plt.show()

plot_inscribed_squares(10)
No description has been provided for this image
In [18]:
# 创建交互式演示
interact(plot_inscribed_squares, 
         s=IntSlider(min=2, max=40, step=1, value=10, description='网格数:'));
interactive(children=(IntSlider(value=10, description='网格数:', max=40, min=2), Output()), _dom_classes=('widget…

案例 4:向连续运动过渡¶

  • 离散状态: 随机游走 (Random Walk)
  • 连续极限: 布朗运动 (Brownian Motion)
In [19]:
# 生成随机游走数据
N = 1024
h = 1/N
v = np.random.randn(N)

def plot_random_walk(j):
    """绘制不同采样率的随机游走"""
    J = N // (2**j)
    
    fig, ax = plt.subplots(figsize=(12, 6))
    
    # 累积和
    c = np.concatenate([[0], np.cumsum(v)]) * np.sqrt(h)
    t = np.arange(N+1) / N
    
    # 绘制完整路径
    ax.plot(t, c, 'b-', linewidth=1, alpha=0.5)
    
    # 绘制采样点
    t_sample = t[::J]
    c_sample = c[::J]
    ax.scatter(t_sample, c_sample, color='red', s=50, zorder=5)
    ax.plot(t_sample, c_sample, 'r-', linewidth=2)
    
    ax.set_ylim(-2, 2)
    ax.set_xlabel('时间', fontsize=12)
    ax.set_ylabel('位置', fontsize=12)
    ax.set_title(f'随机游走 (采样间隔: {J})', fontsize=14)
    ax.grid(True, alpha=0.3)
    plt.tight_layout()
    plt.show()

plot_random_walk(6)
No description has been provided for this image
In [20]:
# 创建交互式演示
interact(plot_random_walk, 
         j=IntSlider(min=1, max=9, step=1, value=6, description='采样级别:'));
interactive(children=(IntSlider(value=6, description='采样级别:', max=9, min=1), Output()), _dom_classes=('widget-…

常见离散和连续的对应关系¶

离散形式 (Discrete) 连续形式 (Continuous)
累加求和 $\sum$ 定积分 $\int_a^b$
累积和 不定积分
有限差分 导数与梯度
离散卷积 连续卷积
离散随机游走 连续布朗运动
二项分布 正态分布

本讲小结¶

  • 离散与连续相辅相成,是计算数学和分析的核心。
  • 关键思想: 可以利用易计算的离散近似高精度地估算复杂的连续量。
  • 价值: 这些桥梁构建了整个数值分析与计算数学的基石。