Skip to content

抽象思维与问题建模

本节要建立两个非常重要的能力:抽象和建模。

抽象是从复杂现象中抓住关键部分,忽略暂时不重要的细节。建模是把问题转化成可以被程序处理的结构。

数据结构学习得好不好,很大程度上取决于你能不能把现实问题转成合适的数据模型。


抽象并不是把问题变得模糊,而是把问题变得更清晰。

例如,现实中的排队买票有很多细节:每个人的姓名、身高、衣服颜色、说话声音、付款方式、窗口工作人员的状态等。但如果我们只关心“谁先来,谁先服务”,那么最关键的信息就是顺序。

此时,我们可以忽略大量无关细节,把问题抽象成一个队列。

队列只关注两个核心动作:

  • 新来的人排到队尾。
  • 服务时从队头开始。

这就是抽象的力量。它帮助我们从复杂场景中提取真正影响问题解决的部分。


建模是把抽象出来的关系转化成程序中可以处理的形式。

例如,好友关系可以抽象成“人和人之间的连接”。如果只用文字描述,很难让程序处理。但如果把每个人看作一个顶点,把好友关系看作边,就可以建模成图。

类似地:

  • 文件目录可以建模成树。
  • 浏览器后退记录可以建模成栈。
  • 打印任务等待列表可以建模成队列。
  • 学号到学生信息的对应关系可以建模成哈希表。
  • 按优先级处理的任务可以建模成堆。

建模的过程,本质上是在问:这个问题中的数据是什么?数据之间是什么关系?最常发生的操作是什么?


假设你要设计一个待办事项功能。需求很简单:

  • 可以添加新任务。
  • 可以查看所有任务。
  • 可以把任务标记为完成。
  • 可以删除任务。

最直接的建模方式是使用列表。每个任务保存标题和完成状态,所有任务按顺序放入一个集合。

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;
}

这个模型能工作,因为待办事项天然可以按顺序排列。每个任务可以用一个对象表示,所有任务放进一个列表里。

这段代码的重点不是功能完整,而是展示建模思路:先确定数据单元,再确定数据集合,最后确定围绕数据的操作。


如果待办事项变复杂,原来的列表模型可能不再够用。

如果需求变成“按照截止时间自动提醒”,可能需要按照时间排序。如果需求变成“总是优先处理最重要的任务”,可能需要优先队列。如果需求变成“任务之间有依赖关系,完成一个任务之前必须先完成另一个任务”,可能需要图。

这说明数据结构选择不是一次性固定的。需求不同,模型就可能变化。


面对一个新问题,可以按照下面的顺序思考:

  1. 找出问题中真正需要被程序处理的数据。

  2. 判断这些数据之间是线性关系、层级关系、映射关系,还是复杂连接关系。

  3. 列出最常见、最关键的操作,例如插入、删除、查找、遍历、排序或合并。

  4. 根据数据关系和操作需求,初步选择可能合适的数据结构。

  5. 用简单代码验证这个模型是否能自然表达问题。

这个过程不是死板公式,而是训练思考的工具。练习次数多了,你会更快看出问题背后的结构。


抽象帮助我们忽略无关细节,抓住问题中的核心关系。建模帮助我们把这些关系转化成程序可以处理的数据结构。

学习数据结构时,不要只盯着代码实现。更重要的是练习从真实问题中识别关系、提取操作、选择结构。只有这样,数据结构才会从“知识点”变成真正能解决问题的工具。