ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

MIT 6.00公开课:用Python夯实计算思维与算法基础

MIT 6.00公开课:用Python夯实计算思维与算法基础 最近在整理操作系统和算法基础时又重新把 MIT 6.00《计算机科学与编程导论》2008 年秋季的公开课翻了出来。这门课用的是 Python 2乍一看版本很老但真正跟下来会发现这门课教的东西恰恰是很多 Python 教程里最缺的“计算思维”训练。如果你正在自学编程或者已经能写一些业务代码但觉得算法、抽象、调试能力跟不上这篇文章非常适合你。我将把整个课程的学习主线串一遍课程讲了什么、为什么值得学、如何搭建兼容 Python 2 的练习环境、核心知识点怎么拆解以及典型作业题怎么实现。文中的代码都给出完整可运行版本并标注好 Python 2 / Python 3 的差异方便你一边看视频一边动手验证。1. 课程背景与内容概览1.1 MIT 6.00 是一门怎样的课MIT 6.00 的全称是 Introduction to Computer Science and Programming直译是“计算机科学与编程导论”。这里要留意课程名里的顺序Computer Science 在前Programming 在后。它并不是一门“Python 语法课”而是一门面向大一新生的计算机科学入门课Python 只是用来实践思维的工具。2008 年秋季版本的授课教授是 John Guttag他后来还基于这门课写了教材《Introduction to Computation and Programming Using Python》。这一版课程的讲课录像被放到了 MIT OpenCourseWare 上配套资源包括课程大纲、讲义、作业题、考试题和项目说明全部免费至今仍然可以访问。1.2 课程主线不是学语法而是学计算整门课的核心主线围绕几个问题展开计算机如何执行程序如何把复杂问题拆解成可计算的小步骤如何评估一个算法快不快、内存占得多不多如何通过抽象和建模让代码能够应对需求变化如何系统地调试程序而不是靠猜为了回答这些问题课程会依次讲 Python 基础语法、函数与递归、算法复杂度、排序查找、面向对象、模拟与优化。你会发现语法部分只占前几讲更多的时间花在了“用代码解决问题”上。1.3 2008 年秋季版本的参考价值2008 年秋季的 6.00 用的是 Python 2.5 左右的环境。Python 2 后来在 2020 年初正式停止官方维护很多第三方库也不再兼容。不过这不是一票否决这门课的理由。恰恰相反当你知道了 Python 2 和 Python 3 的差异后再回看那年的代码反而更能理解 Python 语言这些年发生了哪些变化、哪些设计被保留、哪些设计被修正。另外MIT 在 6.00 之后推出了 6.0001、6.0002 等新版本新版全部使用 Python 3。如果后面学完本篇想追求语法上的“现代感”可以直接转到新版公开课。但从内容和讲授深度来说2008 年秋季这版依然是很好的学习素材。2. 为什么这门老课仍然值得学2.1 底层知识比工具知识更持久Python 的版本会更新第三方库会有 breaking change但“如何把问题抽象成函数”“为什么要分析算法复杂度”“递归的边界条件怎么设计”这些底层知识不会变。MIT 6.00 的价值就体现在这里它强调的不是某个 API 怎么用而是编程中最稳定的计算思维。举个例子课程会反复强调“穷举法”“二分法”“牛顿法”这些通用问题求解策略。你在很多算法题里都能看到它们的影子。掌握这些思想后换任何一种语言、换任何一套框架思路都还在。2.2 能加深对现代 Python 的理解学习 Python 2 的代码并不是让你以后也用 Python 2 写新项目。而是当你看到print hello与print(hello)的区别时你会意识到语法演进并不是随便改的。Python 3 把 print 变成函数、把整数除法改成浮点除法、把raw_input并入input这些修订都是为了减少歧义和提升一致性。用老代码建立对比比直接看新版语法的“现状说明”更容易留下深刻记忆。这也是我把课程资料重新过一遍后最明显的感受。2.3 学习闭环非常完整很多网上教程只讲语法缺少作业和项目环节。MIT 6.00 不一样它每个阶段都有配套练习。作业题不仅要求写出正确结果还会考察复杂度、边界条件和代码结构。跟着作业走一遍才算真正学到东西。3. 环境准备与版本说明3.1 给课程配置一个可运行的 Python 环境2008 年课程使用的解释器是 Python 2.5但那个版本太老了现在很多系统上已经很难直接安装。我建议你安装 Python 2.7 作为补充环境因为 2.7 是 Python 2 系列的最终版本与课程代码的兼容性最好同时比 2.5 更现代一些。如果你用 Windows可以安装 Python 2.7 的安装包安装时勾选 Add to PATH。如果你用 macOS 或 Linux系统自带的 Python 版本通常已经是 Python 3这时不要直接覆盖系统 Python而是用pyenv或conda单独创建 Python 2.7 环境。下面是 conda 方式conda create -n py27 python2.7 conda activate py27 python --version运行后如果输出Python 2.7.x说明环境创建成功。之后在命令行里启动python即可进入交互式解释器。3.2 用 Python 3 学习时的兼容处理如果你不想额外安装 Python 2也可以直接用 Python 3 观看课程但要把课程中的代码做少量转换。最常用到的差异有下面几个# Python 2 写法 print hello # Python 3 写法 print(hello)# Python 2 中获取用户输入 name raw_input(Enter your name: ) # Python 3 中获取用户输入 name input(Enter your name: )# Python 2 中整数除法结果是整数 7 / 2 # 结果是 3 # Python 3 中整数除法结果是浮点数 7 / 2 # 结果是 3.5 # 如果想整除使用 // 7 // 2 # 结果是 3# Python 2 中 range 会生成整个列表xrange 生成迭代器 for i in xrange(10): print i # Python 3 中 range 本身就是迭代器xrange 已移除 for i in range(10): print(i)3.3 推荐的项目文件结构为了方便管理课程练习建议按下面的结构存放文件mit600/ ├── lectures/ # 讲义和笔记 ├── assignments/ # 每周作业 ├── projects/ # 小型项目 └── practice/ # 自己写的练习代码每写完一个练习可以顺便在文件头部写清楚题目来源、输入输出、复杂度和自己的解题思路。这样复习时会很有帮助。4. 核心知识点与代码拆解4.1 函数与抽象课程在讲函数时不只是讲 def 关键字而是强调“函数的本质是抽象”。调用一个函数时调用者不需要关心函数内部每一行是怎么执行的只需要知道输入和输出之间的契约。这种思维在大型项目里尤其重要。下面用两个函数做一个对比# 文件路径practice/function_example.py def square_list(numbers): 输入一个列表返回每个元素的平方组成的列表 result [] for n in numbers: result.append(n * n) return result # 把计算平方的逻辑抽成独立函数更利于复用和测试 def square(n): return n * n def square_list_v2(numbers): return [square(n) for n in numbers] data [1, 2, 3, 4] print square_list(data) print square_list_v2(data)在 Python 2.7 环境下print后面可以不加括号。如果你用的是 Python 3请把print改成print()。这里的关键点是当需求变化例如要求先判断数字正负再平方你只需要修改square内部逻辑而不用动square_list_v2的循环结构。4.2 递归思维递归是计算机科学里非常核心的思维。课程里用阶乘、斐波那契、汉诺塔等例子层层递进地解释递归三要素递归出口base case递归调用recursive case保证每次递归都向出口靠近先看最经典的阶乘# 文件路径practice/recursion_factorial.py def factorial(n): if n 0: return 1 return n * factorial(n - 1) for i in range(6): print i, factorial(i)运行结果0 1 1 1 2 2 3 6 4 24 5 120递归虽然简洁但并不是所有场景都适合。如果递归深度过大Python 会抛出RecursionError。这时候需要用迭代方式改写或者考虑动态规划。课程后面会专门比较这两种思路的优劣。4.3 算法复杂度与二分查找算法复杂度是 6.00 的绝对重点。课程用“渐近复杂度”去评价一个算法的增长速度也就是大 O 记号。常见复杂度从低到高排列复杂度含义典型例子O(1)常数时间访问列表某个下标元素O(log n)对数时间二分查找O(n)线性时间顺序查找O(n log n)线性对数时间归并排序O(n²)平方时间冒泡排序二分查找是理解 O(log n) 的最佳例子。它要求输入序列已经有序每次取中间值比较把搜索范围缩小一半。一个基本实现如下# 文件路径practice/binary_search.py def binary_search(sorted_list, target): low 0 high len(sorted_list) - 1 while low high: mid (low high) // 2 if sorted_list[mid] target: return mid elif sorted_list[mid] target: low mid 1 else: high mid - 1 return -1 nums [1, 3, 5, 7, 9, 11] print binary_search(nums, 7) print binary_search(nums, 8)输出3 -1注意这里使用了//而不是/是为了避免在 Python 3 出现浮点数下标。4.4 面向对象入门6.00 在课程后半段开始讲面向对象。Python 中的类可以把数据和操作数据的方法打包在一起。课程用学生、课程、成绩等场景来演示如何建模。下面是一个简化版的学生类示例# 文件路径practice/student_class.py class Student: def __init__(self, name, student_id): self.name name self.student_id student_id self.grades [] def add_grade(self, grade): self.grades.append(grade) def average_grade(self): if not self.grades: return 0.0 return sum(self.grades) / float(len(self.grades)) def __str__(self): return self.name ( str(self.student_id) ) s Student(Alice, 20250001) s.add_grade(90) s.add_grade(85) s.add_grade(95) print s print s.average_grade()输出Alice (20250001) 90.0这个例子展示了面向对象的三个好处数据聚合、职责内聚、代码复用。课程后面还会讲到继承与多态例如UG与GradStudent都继承自Student但各自重写部分方法。5. 典型练习实战5.1 用牛顿法求平方根牛顿法是一种通过迭代逼近方程根的方法。求平方根可以转化为求f(x) x^2 - a的根。迭代公式是x_{n1} x_n - f(x_n) / f(x_n)对于求平方根来说迭代式可以写成x_{n1} (x_n a / x_n) / 2下面给出完整实现# 文件路径practice/newton_sqrt.py def sqrt_newton(a, epsilon0.0001): if a 0: raise ValueError(不能对负数求平方根) x a / 2.0 if a 0 else 0.0 while True: next_x (x a / x) / 2.0 if abs(next_x - x) epsilon: return next_x x next_x for value in [1, 2, 4, 9, 16]: print value, sqrt_newton(value)运行结果1 1.0 2 1.414213562373095 4 2.000000000000002 9 3.000000001321184 16 4.000000000000011这个练习的关键点在于理解循环退出的条件当相邻两次迭代结果足够接近时认为已经收敛。这里涉及浮点数的精度问题所以用epsilon而不是直接比较相等。5.2 汉诺塔递归实现汉诺塔是递归思维最经典的演示。规则是把 n 个盘子从 A 柱移到 C 柱B 柱辅助每次只能移动一个盘子且大盘子不能压在小盘子上。递归思路是先把上面 n-1 个盘子从 A 移到 B再把最下面的大盘从 A 移到 C最后把 n-1 个盘子从 B 移到 C。# 文件路径practice/hanoi.py def hanoi(n, source, target, auxiliary): if n 1: print Move disk 1 from, source, to, target return hanoi(n - 1, source, auxiliary, target) print Move disk, n, from, source, to, target hanoi(n - 1, auxiliary, target, source) hanoi(3, A, C, B)运行结果Move disk 1 from A to C Move disk 2 from A to B Move disk 1 from C to B Move disk 3 from A to C Move disk 1 from B to A Move disk 2 from B to C Move disk 1 from A to C递归次数是2^n - 1所以当 n 变大时移动次数会指数增长。这也是课程用来引出“指数复杂度”的重要例子。5.3 递归与迭代实现二分查找课程会要求用递归方式重新实现二分查找。递归版本更贴近“分支思想”# 文件路径practice/binary_search_recursive.py def binary_search_recursive(sorted_list, target, low, high): if low high: return -1 mid (low high) // 2 if sorted_list[mid] target: return mid elif sorted_list[mid] target: return binary_search_recursive(sorted_list, target, mid 1, high) else: return binary_search_recursive(sorted_list, target, low, mid - 1) nums [1, 2, 4, 8, 16, 32, 64] print binary_search_recursive(nums, 8, 0, len(nums) - 1) print binary_search_recursive(nums, 3, 0, len(nums) - 1)输出3 -1递归版本与迭代版本的时间复杂度相同都是 O(log n)但递归版本更接近数学上的分治定义便于证明正确性。5.4 完整示例学生成绩管理为了综合运用函数、列表、类和方法可以把前面的知识点拼成一个更完整的小项目。目标是写一个简单管理学生成绩的控制台脚本。# 文件路径practice/grade_management.py class Student: def __init__(self, name, student_id): self.name name self.student_id student_id self.grades [] def add_grade(self, grade): if grade 0 or grade 100: raise ValueError(成绩必须在 0-100 之间) self.grades.append(grade) def average(self): if not self.grades: return 0.0 return sum(self.grades) / float(len(self.grades)) def summary(self): return self.name 平均分: str(self.average()) def create_demo_students(): students [] s1 Student(Alice, 001) s1.add_grade(80) s1.add_grade(90) students.append(s1) s2 Student(Bob, 002) s2.add_grade(70) s2.add_grade(85) students.append(s2) return students if __name__ __main__: students create_demo_students() for student in students: print student.summary() max_student max(students, keylambda s: s.average()) print 最高平均分学生:, max_student.name运行结果Alice 平均分: 85.0 Bob 平均分: 77.5 最高平均分学生: Alice这个例子把类定义、异常处理、列表操作、lambda 表达式综合到了一起。你可以继续扩展比如从文件里读取成绩、按平均分排序、统计不及格人数等。6. 常见问题与调试思路6.1 Python 2 / Python 3 语法混用报错学习老课程时经常遇到SyntaxError: invalid syntax最常见的触发点是没有括号的print。问题现象常见原因解决思路print报语法错误在 Python 3 中运行了 Python 2 代码给print加括号或切换到 Python 2.7 环境raw_input未定义Python 3 已移除raw_input改用input()xrange未定义Python 3 已移除xrange改用range()建议在练习时固定使用同一个环境避免反复切换。如果你决定用 Python 3就提前把课程中的老语法写在一张对照表旁边遇到报错直接查表。6.2 整数除法导致结果异常Python 2 中7 / 2的结果是3不是3.5。很多初学者在计算平均值时发现结果少了小数部分。Python 3 已经修正这一点但如果你在 Python 2.7 环境里跑要主动做浮点转换# Python 2 中正确计算平均值 average sum(grades) / float(len(grades))在代码里使用float()显式转换既能规避整除问题也让阅读者一眼看出你设计的是浮点除法。6.3 浮点数比较不精确程序里判断两个浮点数是否相等时直接使用通常不靠谱。课程在牛顿法等迭代算法里也遇到过类似问题。正确做法是比较两个数的差值是否小于某个很小的阈值# 不推荐 if x 0.1: print equal # 推荐 epsilon 0.0001 if abs(x - 0.1) epsilon: print almost equal6.4 递归深度超限Python 默认递归深度在 1000 左右超过后会抛出RecursionError: maximum recursion depth exceeded遇到这个错误先检查递归出口是否缺失再确认是否每次调用都在减少问题规模。如果递归深度确实需要很大考虑改成循环或尾递归优化。6.5 建议的调试步骤课程里最推崇的调试方式是“二分定位法”在可能出错的代码之间插入打印语句观察中间值从而快速缩小问题范围。第 1 步复现输入确认能否稳定复现问题。第 2 步在函数入口打印参数确认是否进入函数。第 3 步在循环关键位置打印中间结果观察变量变化。第 4 步检查边界条件例如空列表、负数、0 等。第 5 步如果问题仍找不到把代码拆成更小的独立函数逐个测试。7. 学习路径与工程建议7.1 如何高效完成这门公开课建议按三轮法学习。第一轮看视频不要暂停建立整体框架。第二轮边看边记笔记把每讲的关键概念和代码敲一遍。第三轮做作业题做完后再对照课程答案或与同学讨论。不要一上来就追求把所有题目做完。优先吃透课程前 10 讲的递归和算法复杂度这两部分对后续影响最大。7.2 把课程作业改造成现代 Python 工程学完老课程后可以把作业里的代码用 Python 3 重写一遍同时加入现代工程习惯给函数写 docstring描述输入、输出和副作用。使用类型注解让数据结构更清晰。把可复用代码组织成模块而不是写在单个脚本里。用 unittest 或 pytest 编写基础单元测试。尽量遵循 PEP 8 风格规范。例如上面的 Student 类可以改造成带类型注解的版本# 文件路径practice/student_modern.py from typing import List class Student: def __init__(self, name: str, student_id: str) - None: self.name name self.student_id student_id self.grades: List[float] [] def add_grade(self, grade: float) - None: if grade 0 or grade 100: raise ValueError(成绩必须在 0-100 之间) self.grades.append(grade) def average(self) - float: if not self.grades: return 0.0 return sum(self.grades) / len(self.grades) def summary(self) - str: return f{self.name} 平均分: {self.average()}这样改造不仅能复习课程知识还能练习现代 Python 开发规范。7.3 后续进阶路线完成 MIT 6.00 后可以根据目标选择不同方向想继续打算法基础可以学 MIT 6.006 Introduction to Algorithms。想接触数据分析可以学 MIT 6.0002 Introduction to Computational Thinking and Data Science。想深入 Python 语言本身可以读《流畅的 Python》。想补操作系统和计算机系统知识可以学《CSAPP》或 MIT 6.033。最关键的是保持写代码的频率。公开课只提供了“学”的素材真正的“会”需要在动手调试、改 bug、写练习题的过程中建立起来。建议每周至少留出两个完整时间段专门用来敲代码和整理笔记。学习 MIT 6.00 这门老课程最大的收获不是记住几个 Python 语法而是建立起“把一个问题抽象成程序”的完整思路。它让我意识到编程入门阶段最应该重视的并不是框架又多又新而是函数抽象、递归、复杂度、调试和数据结构这些“地基”。如果你也想系统补一补计算机基础或者从业务开发转向上游能力建设这门课依然值得你从头到尾认真过一遍。跟着课程节奏把每一讲的代码亲手敲完整再把自己重写的现代 Python 版本和课程原版对比一下你会看到自己的成长。
返回列表