专题讨论2:树与查找

专题讨论2:(树与查找)决策树

项目名称 内容
课程名称 数据结构
班级 网安2511
学号 202521336001 、202521336006
实验项目名称 10:决策树
上机实践日期 2026.05

一、关于选题起源

某天,我盯着自己的歌单陷入了沉思:
为什么我收藏了那么多“高级感”十足的后摇,
最后循环最多的,却永远是那几首神曲?
为了直面这种惨淡的人生真相,
我们决定请出数据结构里的“福尔摩斯”——决策树。
这位侦探先生破案的方式非常耿直,
既不看歌词,也不分析旋律,
只负责问你一堆看似废话的问题:
“你是不是那种听到吉他solo就兴奋的人?”
“你是不是非英文歌不听的‘伪文青’?”
每一个“是”或“否”,都会把你引向一条新的岔路,
直到最后,它拍拍你的肩膀,淡淡地说一句:
“别装了,你就是个标准的 Pop 流行乐受害者。”
本次实验的目标很简单:
搭建一棵属于我自己的音乐推荐决策树,
让算法代替镜子,照出我们那真实听歌习惯————


今天,就让我们用最简单的入门案例,带大家搞懂:决策树到底是什么?怎么建立决策树?
首先我们先厘清,决策树究竟是个什么东西?其实它本质上就是一种模拟人类决策过程的树形结构,就像我们平时做选择一样,一步步根据条件判断、分支,最终得到结果。它由根节点、分支节点和叶子节点组成,节点里存放判断特征,分支存放不同的判断结果,叶子节点就是最终的结论。
这篇博客将举一个简单的例子,通过这个简单的例子我们可以掌握决策树的内涵与实现方法,从而为未来大家去构建更复杂的决策树打下基础,也能真正理解树结构的实战价值。

二、实验设计

  • 结构示意

“那么,这棵树是如何一步步‘审问’你的听歌偏好的?别急,看完下面这个简陋却精悍的结构示意,你就全明白了。”

是否喜欢摇滚?
├─ 是 → Rock
└─ 否 → 是否常听欧美?
        ├─ 是 → Pop
        └─ 否 → Classical
  • 示例图
    eb26d162897c776bb475414c8f15d8aa

  • 构建决策树

“光看上面的示意图不过瘾?来,让我们一起剖析这棵树的‘生长过程’,看看在代码世界里,它是怎么被一砖一瓦搭建起来的~”

函数 buildMusicTree()
    // 创建根节点
    根节点 = 新建节点()
    根节点.特征 = "LikeRock"  // 是否喜欢摇滚

    // 分支:喜欢摇滚 → 推荐 Rock
    摇滚节点 = 新建节点()
    摇滚节点.标签 = "Rock"
    根节点.子节点["Yes"] = 摇滚节点

    // 分支:不喜欢摇滚 → 进入下一个判断
    不喜欢摇滚节点 = 新建节点()
    不喜欢摇滚节点.特征 = "ListenWestern"  // 是否听欧美

    // 听欧美 → 推荐 Pop
    流行节点 = 新建节点()
    流行节点.标签 = "Pop"
    不喜欢摇滚节点.子节点["Yes"] = 流行节点

    // 不听欧美 → 推荐 Classical
    古典节点 = 新建节点()
    古典节点.标签 = "Classical"
    不喜欢摇滚节点.子节点["No"] = 古典节点

    根节点.子节点["No"] = 不喜欢摇滚节点

    返回 根节点
  • 预测函数伪代码
函数 predict(节点, 样本):
    如果 节点.标签 不为空:
        返回 节点.标签

    特征值 ← 样本[节点.特征]
    子节点 ← 节点.子节点[特征值]

    返回 predict(子节点, 样本)

三、代码实现

  • 数据结构定义(给树打个骨架!)
    要建房子先打地基,要建树,就得先定义“节点”这个基本单元。
    我们不用晦涩的术语,咱们就把它想象成一个个“带问号的盒子”:盒子里存着问题(比如“喜欢摇滚吗”),还有指向不同答案的指针。
    下面这个 struct Node,就是咱们这棵音乐决策树的“基因蓝图”————
#include <iostream>
#include <string>
#include <map>
using namespace std;

struct Node {
    string feature;        // 划分特征
    map<string, Node*> children;
    string label;          // 叶子节点类别
};
  • 构建决策树(开始种树啦!)
    骨架有了,接下来就是往里面填肉了。​
    这一步,我们要亲手把这棵树“种”出来。
    我们写一个 buildMusicTree()函数,它就像个“树农”,负责把一个个节点挂到该挂的位置
    你看,根节点是“喜欢摇滚吗”,不喜欢就去问“听欧美吗”,听着听着,最后就给你贴上“Pop”或“Classical”的标签。
    整个过程,就像在玩“文字版大富翁”,走到哪格,就触发哪个事件~
Node* buildMusicTree() {
    Node* root = new Node();
    root->feature = "LikeRock";

    Node* rock = new Node();
    rock->label = "Rock";
    root->children["Yes"] = rock;

    Node* noRock = new Node();
    noRock->feature = "ListenWestern";

    Node* pop = new Node();
    pop->label = "Pop";
    noRock->children["Yes"] = pop;

    Node* classical = new Node();
    classical->label = "Classical";
    noRock->children["No"] = classical;

    root->children["No"] = noRock;
    return root;
}
  • 预测函数(递归思想)
    树种好了,现在轮到它发挥价值了—————怎么用它来“猜”用户的喜好?
    这就是最精彩的部分:预测函数。
    它的逻辑其实特别简单,就是一个“递归”的过程:
    拿到用户的特征(比如“喜欢摇滚=Yes”),顺着树往下走一步;
    到了下一个节点,再看下一个问题……
    直到走到一个“叶子节点”(也就是没有孩子了),嘶~答案就出来了。
string predict(Node* root, map<string, string>& sample) {
    if (!root->label.empty())
        return root->label;

    string value = sample[root->feature];
    return predict(root->children[value], sample);
}
  • 测试入口(让树开口说话~)
    逻辑都写完了,总得跑起来看看效果吧?​
    最后,咱们写个 main函数,模拟一个真实的用户来“问卦”。
    用户输入自己的听歌习惯(“喜欢摇滚吗?”“听欧美吗?”),程序就会顺着树一路“盘问”,最后输出一个“音乐人格诊断书”
    这一步,就是让这棵静态的树,“活”过来,开始为你服务的时刻。
int main() {
    Node* tree = buildMusicTree();

    map<string, string> user;
    cout << "是否喜欢摇滚?(Yes/No): "; cin >> user["LikeRock"];
    cout << "是否常听欧美音乐?(Yes/No): "; cin >> user["ListenWestern"];

    cout << "预测喜欢的音乐类型:"
        << predict(tree, user) << endl;

    return 0;
}

四、复杂度分析

  1. 构建阶段
  • 本实验采用手动构建方式,需要依次创建每个节点并建立分支关系,操作次数与节点数量成正比。
    👉 时间复杂度:O(n)(n 为树中节点总数,本实验 n=5)
  1. 预测阶段
  • 预测过程相当于在树上进行深度优先遍历
  • 最坏情况下遍历树的高度 h
    👉 时间复杂度:O(h)​
  1. 空间复杂度
  • 空间用于存储所有节点与分支,与节点数量成正比。
    👉 空间复杂度:O(n)

五、运行展示

本机运行截图
image

image

image


六、实验小结

收获:

  1. 理解决策树是一种递归结构
  2. 掌握决策树构建与预测的基本流程
  3. 能够将现实问题(音乐喜好)抽象为特征与类别

为什么选用决策树做音乐喜好判断,而非简单的 if-else?
有人可能会忍不住问:这么简单的判断逻辑,直接用几行 if-else 嵌套不就能实现吗?何必大费周章去搭建一棵树结构?
自然,针对本次只有两层判断的极简案例,if-else 代码写起来确实更简短,可这也仅仅局限于超简单、无扩展的固定场景一旦我们想要新增一个判断特征,比如加入 “是否喜欢慢节奏”“是否偏爱小众音乐”,if-else 就会陷入层层嵌套的困境,代码变得臃肿混乱、可读性极差,后期修改和维护更是难上加难,完全是硬编码的固定逻辑,没有任何灵活性。

对比优势

维度 if-else 决策树
扩展性 新增条件需修改原有逻辑 只需新增节点,不动核心代码
可读性 多层嵌套难以一眼看清流程 树状结构直观对应人类决策过程
维护性 改一处可能牵一发而动全身 局部调整,互不干扰

这就是为什么我们宁愿"大费周章"建树————它把逻辑结构代码实现解耦了。
我们选择用决策树,正是因为它脱离了单纯的代码逻辑,上升到了数据结构的层面。它把每一个判断、每一个分支都封装成独立的节点,新增判断条件、修改推荐结果,只需要增减或调整节点,完全不用改动原有核心代码;同时它的结构和我们的决策思路完全对应,哪怕是零基础的人,也能一眼看懂整个判断流程,更能帮我们真正理解 “树结构” 在实际问题中的应用,而不是停留在表层的条件判断。

这篇文章的初衷,从来不是为了用复杂代码实现简单功能,而是借这个极简的音乐偏好预测案例,让大家轻松入门:看懂决策树的基本结构,掌握它的搭建和使用逻辑,明白它相比硬编码的 if-else,在扩展性、可读性、可维护性上的核心优势。毕竟学会决策树的思想,才能应对后续更复杂、更多维度的决策问题,这才是学习数据结构的真正意义。


七、附件(参考文献和相关资料)

  1. 决策树原理实例
  2. 决策树入门实战例子

AI 声明:本文核心思路为原创,AI 做辅助文字润色与结构梳理。

posted @ 2026-05-06 14:30  Wujee  阅读(98)  评论(1)    收藏  举报