抽象思维与问题建模
本节要建立两个非常重要的能力:抽象和建模。
抽象是从复杂现象中抓住关键部分,忽略暂时不重要的细节。建模是把问题转化成可以被程序处理的结构。
数据结构学习得好不好,很大程度上取决于你能不能把现实问题转成合适的数据模型。
抽象并不是把问题变得模糊,而是把问题变得更清晰。
例如,现实中的排队买票有很多细节:每个人的姓名、身高、衣服颜色、说话声音、付款方式、窗口工作人员的状态等。但如果我们只关心“谁先来,谁先服务”,那么最关键的信息就是顺序。
此时,我们可以忽略大量无关细节,把问题抽象成一个队列。
队列只关注两个核心动作:
- 新来的人排到队尾。
- 服务时从队头开始。
这就是抽象的力量。它帮助我们从复杂场景中提取真正影响问题解决的部分。
建模是把抽象出来的关系转化成程序中可以处理的形式。
例如,好友关系可以抽象成“人和人之间的连接”。如果只用文字描述,很难让程序处理。但如果把每个人看作一个顶点,把好友关系看作边,就可以建模成图。
类似地:
- 文件目录可以建模成树。
- 浏览器后退记录可以建模成栈。
- 打印任务等待列表可以建模成队列。
- 学号到学生信息的对应关系可以建模成哈希表。
- 按优先级处理的任务可以建模成堆。
建模的过程,本质上是在问:这个问题中的数据是什么?数据之间是什么关系?最常发生的操作是什么?
一个简单例子:任务待办列表
Section titled “一个简单例子:任务待办列表”假设你要设计一个待办事项功能。需求很简单:
- 可以添加新任务。
- 可以查看所有任务。
- 可以把任务标记为完成。
- 可以删除任务。
最直接的建模方式是使用列表。每个任务保存标题和完成状态,所有任务按顺序放入一个集合。
struct Todo { string title; bool done;};
vector<Todo> todos;
void addTodo(const string& title) { todos.push_back({title, false});}
void finishTodo(int index) { todos[index].done = true;}class Todo { String title; boolean done;
Todo(String title) { this.title = title; this.done = false; }}
List<Todo> todos = new ArrayList<>();
void addTodo(String title) { todos.add(new Todo(title));}
void finishTodo(int index) { todos.get(index).done = true;}todos = []
def add_todo(title): todos.append({"title": title, "done": False})
def finish_todo(index): todos[index]["done"] = Trueclass Todo{ public string Title { get; set; } public bool Done { get; set; }
public Todo(string title) { Title = title; Done = false; }}
List<Todo> todos = new List<Todo>();
void AddTodo(string title){ todos.Add(new Todo(title));}
void FinishTodo(int index){ todos[index].Done = true;}这个模型能工作,因为待办事项天然可以按顺序排列。每个任务可以用一个对象表示,所有任务放进一个列表里。
这段代码的重点不是功能完整,而是展示建模思路:先确定数据单元,再确定数据集合,最后确定围绕数据的操作。
同一个问题可能有不同模型
Section titled “同一个问题可能有不同模型”如果待办事项变复杂,原来的列表模型可能不再够用。
如果需求变成“按照截止时间自动提醒”,可能需要按照时间排序。如果需求变成“总是优先处理最重要的任务”,可能需要优先队列。如果需求变成“任务之间有依赖关系,完成一个任务之前必须先完成另一个任务”,可能需要图。
这说明数据结构选择不是一次性固定的。需求不同,模型就可能变化。
抽象与建模的基本过程
Section titled “抽象与建模的基本过程”面对一个新问题,可以按照下面的顺序思考:
-
找出问题中真正需要被程序处理的数据。
-
判断这些数据之间是线性关系、层级关系、映射关系,还是复杂连接关系。
-
列出最常见、最关键的操作,例如插入、删除、查找、遍历、排序或合并。
-
根据数据关系和操作需求,初步选择可能合适的数据结构。
-
用简单代码验证这个模型是否能自然表达问题。
这个过程不是死板公式,而是训练思考的工具。练习次数多了,你会更快看出问题背后的结构。
抽象帮助我们忽略无关细节,抓住问题中的核心关系。建模帮助我们把这些关系转化成程序可以处理的数据结构。
学习数据结构时,不要只盯着代码实现。更重要的是练习从真实问题中识别关系、提取操作、选择结构。只有这样,数据结构才会从“知识点”变成真正能解决问题的工具。