#1 算法学习 - Python基础

环境

➜  ~ python --version
Python 3.13.5
➜  ~ uv --version
uv 0.8.11 (f892276ac 2025-08-14)
➜  ~ which python
/Users/laoer/.local/share/uv/tools/ruff/bin/python
➜  ~ which uv
/Users/laoer/.local/bin/uv

标准输出

在 Python 中,标准输出使用 print 函数在控制台打印内容。print 默认会在末尾换行,如果想取消换行,可以使用参数 end=“”

a = 10

# 输出:10
print(a)

# 串联输出(通过字符串拼接或逗号分隔)
# 输出:Hello, World!
print("Hello" + ", " + "World!")
# 使用 sep 指定分隔符
print("Hello", "World!", sep=", ")

s = "abc"
# 输出:abc 10
print(s, a)

# 格式化输出
# 输出:abc 10
print(f"{s} {a}")

在上述的示例中,sep 参数只在一次 print 调用里输出“多个对象”时才生效。比如:

>>> print("hello"+"world!",sep="+")
helloworld!
>>> print("hello" "world!",sep=" ")
helloworld!

可以看到这两个示例,sep 参数并没有起作用,是因为"hello"+"world!"(运行时执行字符串拼接,生成单个字符串 "helloworld!" )和"hello" "world!"( 属于相邻字面量自动合并(implicit concatenation),在编译阶段就被合并成单个字符串 "helloworld!" ,运行时根本看不见两个独立的 "hello""world!" )只会被看作是一个字符串,正确的做法是把两个字符串作为两个独立参数穿进去。比如:

>>> print("hello","world!",sep="+")
hello+world!
>>> print("hello","world!",sep=" ")
hello world!

控制语句

Python 中的控制语句包括条件判断和循环。

条件判断 ifelifelse

a = 10

if a > 5:
    print("a > 5")
elif a == 5:
    print("a == 5")
else:
    print("a < 5")
# 输出:a > 5

循环 for / while

for 循环通常用于遍历可迭代对象或已知范围的序列,while 循环用于满足条件时反复执行代码块。

# 输出:0 1 2 3 4 
for i in range(5):
    print(i, end=" ")
print()

num = 100
# 输出:100 50 25 12 6 3 1 
while num > 0:
    print(num, end=" ")
    num //= 2
print()

关于 for 循环部分:

  1. for i in range(5):

  • range(5) 会依次产生整数 0、1、2、3、4(共 5 个)。

  • 每产生一个整数,就把它赋给循环变量 i ,然后进入循环体一次。

  1. print(i, end=" ")

  • print 把当前的 i 打印出来。

  • 关键在 end=" " :它告诉 print 打印完后不要换行,而是在末尾留一个空格。

  • 因此 5 次循环会连续打印在同一行:0 1 2 3 4 (末尾多一个空格)。

  1. print()

不带任何参数,只输出一个换行符,把光标移到下一行,防止后续输出继续粘在刚才那行后面。

关于 while 循环部分:

  1. num = 100

  • 把变量 num 初始化为 100。

  1. while num > 0:

  • 只要 num 大于 0 就继续循环。

  1. print(num, end=" ")

  • 打印当前 num 的值,并在末尾留一个空格(不换行)。

  1. num //= 2

  • num 整除 2(向下取整)后再存回 num

  1. print()

  • 循环结束后输出一个换行符。

特别注意,num //=2//=之间不能有空格,否则 Python 会把 // 当成单行注释符,后面的 = 2 被注释掉,于是解释器看到 num 后面突然冒出一个 = ,结果抛出如下错误(语法错误):

/Users/laoer/Documents/learn-python/.venv/bin/python /Users/laoer/Documents/learn-python/python_basic.py 
  File "/Users/laoer/Documents/learn-python/python_basic.py", line 5
    num// = 2
          ^
SyntaxError: invalid syntax

同时,num//= 之间最好是能空一格,因为 PEP 8(官方排版说明书) 推荐运算符前后留空格。

关于 for 与 while 的区别:

  • for :走“可迭代对象”的流水线——有多少元素就转多少圈,圈数事先可算(除非中途 break)。

  • while :走“布尔信号灯”的单行道——只要灯还绿就一直走,圈数完全取决于条件什么时候变 False。

维度

for

while

核心驱动

布尔表达式 迭代器协议 ( iter/__next__ )

布尔表达式

循环次数

由可迭代对象长度决定(除非 break)

由程序员控制的逻辑条件决定

死循环风险

低(除非迭代器无限)

高(条件永远为 True)

可读性

遍历/枚举/序列推导时更直观

条件等待/状态机场景更直观

额外语法糖

else 子句(未 break 时执行)

同样支持 else

性能

C 级别迭代,略快

每次都要重新求值条件

for 适合如下场景:

  • 遍历序列、文件、生成器、range

  • 列表/字典/集合推导式

  • 计数循环(配合 enumerate , zip

while 适合如下场景:

  • 不确定次数的等待: while not queue.empty()

  • 交互式输入: while (cmd := input('> ')) != 'quit'

  • 轮询/重试:网络重连、硬件状态检测

  • 状态机:游戏主循环、协议解析

通过以下简单的示例可以看出 forwhile 在语法和执行流程的差异。

# ===== 1) for 循环:遍历已知范围 =====
total_for = 0
for i in range(1, 11):   # 范围写死,循环 10 次
    total_for += i
print('for 结果:', total_for)

# ===== 2) while 循环:条件驱动 =====
total_while = 0
n = 1
while n <= 10:           # 条件满足就继续
    total_while += n
    n += 1               # 必须手动更新变量,否则死循环
print('while 结果:', total_while)

输出结果如下:

for 结果: 55
while 结果: 55
  • forrange(1, 11) 把次数“摆好”,循环变量 i 自动更新。

  • while:只有自己写的 n <= 10n += 1 在控制“何时停、下一步是谁”。

基本数据结构

Python 提供了丰富的内置数据结构,如 listdeque(/dek/)、dict( /dɪkt/)、set 等。以下是一些常用数据结构的介绍及其使用方法。

列表 list(动态数据)

list 是 Python 的可变序列类型,可以用作动态数组。

  1. “可变序列(mutable sequence)”——抽象层面

  • list 支持原地修改(append、pop、insert、sort…)而不需要重新创建对象。

  • 对比 tuple、str 等不可变序列,它们一旦创建内容就固定;list 可以随时增删改。

  1. “动态数组(dynamic array)”——实现层面

CPython 在 C 里用一块连续的内存缓冲区(C 的 PyObject * 指针数组)来存放元素引用。

  • 当缓冲区满时,内部会重新申请一块更大的连续内存(通常按 0、4、8、16、25、35… 的过度分配策略增长),然后把旧元素整体搬到新地址——这正是传统“动态数组”做的事情。

  • 对外暴露的接口却是“链表式”的随意插入/删除,但底层仍然是O(1)随机索引、O(1)尾部追加均摊的动态数组特性。

初始化方法:

# 初始化一个空列表
nums = []

# 初始化一个包含元素 1, 3, 5 的列表
nums = [1, 3, 5]

# 初始化大小为 n,元素都为0的列表
n = 10
nums = [0] * n

# 二维列表,m 行 n 列,元素都为 1
m, n = 3, 4
matrix = [[1] * n for _ in range(m)]

输出结果

[1, 3, 5]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]

Python 中列表的常用方法:

nums = [0] * 10

# 输出:False
print(len(nums) == 0)
# 输出:10
print(len(nums))

# 在列表尾部插入一个元素 20
nums.append(20)
# 输出:11
print(len(nums))

# 得到列表最后一个元素,输出:20
print(nums[-1])

# 删除列表的最后一个元素
nums.pop()
# 输出:10
print(len(nums))

# 索引访问与修改
nums[0] = 11
# 输出:11
print(nums[0])

# 在索引 3 处插入一个元素 99
nums.insert(3, 99)

# 删除索引 2 处的元素
nums.pop(2)

# 交换 nums[0] 和 nums[1]
nums[0], nums[1] = nums[1], nums[0]

# 遍历列表
# 输出示例:
# 0 11 99 0 0 0 0 0 0 0
for num in nums:
    print(num, end=" ")
print()

在上述示例中,需要着重说明的是matrix = [[1] * n for _ in range(m)] ,这是列表推导式的写法,是 Python 用来“在一行里生成新列表”的语法糖,核心是把传统循环 + 条件过滤 + 结果收集浓缩成一条表达式。它兼具可读性、执行效率和函数式风格。下面从“能做什么 > 语法长什么样 > 怎么运行 > 进阶功能”四个维度展开说明。

  1. 能做什么

  • 把任意可迭代对象(列表、元组、集合、字典、字符串、生成器、文件对象……)映射成新列表。

  • 在映射过程中可以过滤元素、嵌套多重循环、原地拆包、调用函数、计算表达式。

  • 结果一定是新的 list,不会修改原对象

例子:把 0~9 中偶数的平方收集到新列表。

传统写法:

squares = []
for x in range(10):
    if x % 2 == 0:
        squares.append(x * x)

列表推导式:

squares = [x*x for x in range(10) if x % 2 == 0]
  1. 语法结构

[expression         for item in iterable              if condition]        # 单循环
[expression         for item1 in iter1 for item2 in iter2 ... if cond]    # 多重循环
[expression1 if cond else expression2 for item in iterable]               # 三元表达式

阅读顺序:先写“我要生成的元素长什么样(expression)”,再写“从哪拿元素(for … in …)”,最后写“留下哪些(if …)”。

  1. 执行流程

step1. 取外层可迭代对象的下一个元素 → 绑定给循环变量。

step2.如果有 if,先判断条件,False 就跳过。

step3.计算 expression,把结果收集到临时列表。

step4.回到 1,直到外层迭代完毕。

step5.返回收集好的完整列表。

  1. 进阶功能与示例

多种循环

[(x, y) for x in range(3) for y in 'ab']
# [(0,'a'),(0,'b'),(1,'a'),(1,'b'),(2,'a'),(2,'b')]

嵌套列表展开(flatten /ˈflætən/ )

matrix = [[1,2,3],[4,5,6]]
flat = [item for row in matrix for item in row]
# [1,2,3,4,5,6]

另外补充说明下 [[1]*n]*m [[1]*n for _ in range(m)]的区别。表面上看,它们的结果一样。如下:

a = [[0]*3]*4
b = [[0]*3 for _ in range(4)]

print(a)
print(b)

输出结果:

[[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]

但实际上,它们有这本质上的区别:

  • [[1]*n]*m 只创建了一行 [1,1,…] ,然后把这个同一个行对象的引用复制了 m 次;

  • [[1]*n for _ in range(m)] 创建了 m 个彼此独立的新行对象

内存结构上的区别:

>>> a = [[0]*3]*4
>>> id(a[0]) == id(a[1]) == id(a[2]) == id(a[3])
True          # 四行指向同一块内存
>>> b = [[0]*3 for _ in range(4)]
>>> id(b[0]) == id(b[1])
False         # 每行是独立列表

修改上的区别:

>>> a[0][0] = 99
>>> a
[[99, 0, 0], [99, 0, 0], [99, 0, 0], [99, 0, 0]]
>>> b[0][0] = 99
>>> b
[[99, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]

因此, 想得到一个真正的 m×n 矩阵,请用列表推导式 [[1]*n for _ in range(m)] [[1]*n]*m 只是“看似”正确,实则所有行共享同一内存,极易踩坑。

双端队列 deque

dequecollections 模块提供的双端队列,可以高效地在两端插入和删除元素。

评论