数据结构课程设计(三)构建决策树

3 决策树

3.1 需求规格说明

【问题描述】

ID3算法是一种贪心算法,用来构造决策树。ID3算法起源于概念学习系统(CLS),以信息熵的下降速度为选取测试属性的标准,即在每个节点选取还尚未被用来划分的具有最高信息增益的属性作为划分标准,然后继续这个过程,直到生成的决策树能完美分类训练样例。

具体方法是:从根结点开始,对结点计算所有可能的特征的信息增益,选择信息增益最大的特征作为结点的特征,由该特征的不同取值建立子结点;再对子结点递归的调用以上方法,构建决策树;知道所以特征的信息增益均很小或没有特征可以选择为止。

参考论文:Quinlan J R. Induction of Decision Trees[J]. Machine Learning, 1986, 1(1): 81-106.

参考资料:决策树—ID3、C4.5、CART_决策树流程图-CSDN博客

【数据说明】

DT_data.csv为样本数据,共14条记录。每一条记录共4维特征,分别为Weather(天气), Temperature(温度),Humidity(湿度),Wind(风力);其中Date(约会)为标签列。

【基本要求】(60%)

(1)根据样本数据,建立决策树。

(2)输入测试数据,得到预测是否约会(yes/no)。

【提高要求】(40%)

(1)对决策树(ID3)中特征节点分类选择指标(信息增益)进行优化,选择信息增益率作为决策树(C4.5)中特征节点分类指标,。

决策树(C4.5)及信息增益率参考资料:数据挖掘--决策树C4.5算法(例题)_c4.5算法例题-CSDN博客

3.2 总体分析与设计

(1)设计思想

①存储结构

在决策树的实现中,我采用了以下存储结构:

样本数据结构(Sample):定义了一个结构体Sample来存储每个样本的特征和标签。特征包括天气(Weather)、温度(Temperature)、湿度(Humidity)和风力(Wind),标签为约会结果(Date),表示为yes或no。

决策树节点结构(TreeNode):定义了结构体TreeNode来表示决策树的节点。每个节点包含一个属性(attribute),用于分裂的属性名;一个分支映射(branches),存储该属性不同取值对应的子节点;以及一个标签(label),表示叶子节点的分类结果。

决策树类(DecisionTree):定义了一个类DecisionTree,包含根节点(root)和算法类型(algorithmType)。该类提供构建决策树(buildTree)、预测(predict)和可视化(visualizeTree)等方法。

②主要算法思想

决策树的构建主要基于ID3和C4.5算法。这两种算法都是利用信息增益或信息增益率作为属性选择的标准,递归地构建决策树,直到所有样本都能被正确分类或者没有更多的属性可以用来进一步分裂。

ID3算法:以信息熵的下降速度为选取测试属性的标准,选择信息增益最大的属性进行分裂。

C4.5算法:在ID3的基础上进行了优化,使用信息增益率来选择属性,以减少属性值中某个类别占比过大时的影响。

(2)设计表示

决策树构建的UML类图如图3.2-1所示。

3.2-1 程序UML类图

这里Question_3类主要是为了读取数据并预生成标签列,而Decision类则主要进行的是决策树的构建以及其可视化的工作。

(3)详细设计表示

决策树构建的程序流程图如图3.2-2所示。

3.2-2 决策时实现流程图

该程序的流程步骤如下所示:

1.初始化:构造函数中初始化根节点和算法类型。

2.属性选择:计算每个属性的信息增益或信息增益率,并选择最佳属性。

3.节点分裂:根据选择的属性值分裂节点,递归构建子树。

4.叶子节点生成:当所有样本属于同一类别或没有更多属性时,生成叶子节点。

5.预测:从根节点开始,根据样本的特征值递归查找对应的分支,直到到达叶子节点,得到预测结果。

6.可视化:递归遍历决策树,使用QT的GraphicsView控件实现绘制节点和分支,展示决策树结构。

由于本题中,采取了两种不同的算法,故这里我还绘制了构建决策树的流程图,如图3.2-3所示。

3.2-3 构建决策树流程图

3.3 编码

问题1】:在生成决策树和剪枝的时候指针容易丢失的问题

【解决方法】:在解决生成决策树中指针容易丢失的问题时,首先需要确保正确管理内存。动态分配内存时,使用new关键字进行分配,而在不再需要时使用delete进行释放。此外,释放内存后,将指针设置为nullptr,以避免野指针问题。另一方面,在实际调试过程中,通过手动绘图,监控指针指向,保证生成决策树和剪枝时要逻辑合理。反复调试保证代码正确。

问题2】:树的结点只记录了属性,无法获取结点的特征属性,可视化效果较差

【解决方法】:使用map存储该结点的特征属性和特征属性取值,每个结点存储上一层分类的值以及下一层分类的最优属性,通过存储上一层分类的值,我们可以了解节点的父节点是什么,从而构建出完整的树形结构。而下一层分类的最优属性则可以帮助我们了解节点的子节点应该具备哪些特征,以便进一步展开子节点。在此基础上,完成树结构的绘制,实现可视化,能清晰明了的告诉用户在哪种情况下最有可能去约会。

3.4 程序及算法分析

①使用说明

打开程序后,即可出现初始化程序样式,如图3.4-1所示。

3.4-1 初始化程序

接下来,可以点击相应的算法,并点击“选择CSV文件”将提供的测试CSV输入并进行训练,此时程序会调用visualizeTree函数会将训练结果同步可视化到界面中显示,如图3.4-2所示。

3.4-2 选择决策树训练集文件

这里我应用了ID3算法为例子,选择不同的选项后,再点击“分析结果”即可得到相应的输出结果,如图3.4-3所示。

3.4-3 输出结果

同样,如果选择“C45”算法则会调用C45算法进行决策树的构建,这里需要说明的是C4.5算法和ID3算法的一个显著差异就是应用了信息熵增益率来取代信息熵增益,这样可以显著消除众数分布的影响,并且C45算法所构建的决策树样式也是和ID3算法有较大差异的,如图3.4-4所示。

3.4-4 决策树构建差别

而应用C4.5算法进行预测时显然会得到更加精确的预测结果,如图3.4-5所示。

3.4-5 C4.5算法预测结果

3.5 小结

决策树是一种常用的监督学习算法,主要用于分类和回归问题。它的每个内部节点代表一个属性或特征,每个分支代表一个决策规则,每个叶节点则代表一个结果或决策。决策树的优点在于它的模型结果易于理解和解释,因为决策过程类似于人类的决策过程。此外,它需要的数据预处理较少,不需要进行归一化或标准化,且可以处理数值和分类数据。

在ID3算法中,我通过计算信息增益来选择最佳属性进行节点分裂。信息增益是衡量属性对于分类结果的信息量的增加程度,它帮助我们确定哪个属性最能减少分类的不确定性。然而,我发现单一地使用信息增益作为分裂标准有时会导致选择偏向于那些值较多的属性。为了改进这一问题,我在C4.5算法中引入了信息增益率。这一改进让我更加全面地考虑了属性的选择。信息增益率不仅考虑了信息增益的大小,还考虑了分裂信息值的大小。这样,算法在选择分裂属性时,能够更准确地衡量分裂后的信息量变化。而在实现ID3和C4.5决策树算法的过程中,我遇到了许多困难。决策树的复杂性不在算法逻辑上,而是涉及到对数据结构的深入理解。指针的正确使用对于决策树的构建至关重要,一旦出错,可能会导致树的结构混乱或内存泄漏。最棘手的是指针丢失问题。在动态构建决策树时,我经常遇到指针指向无效内存地址或未初始化的情况。为了解决这个问题,我仔细检查了内存管理代码,并确保在使用指针之前进行了正确的初始化。同时,我也加强了对new和delete操作符的使用,确保正确地分配和释放内存。

但是本题程序还有待改进,比如绘制时的标记与结点图案重合的情况,我还需要进一步详细的处理。而且未讨论如何处理连续数值型数据以构建更精确的决策树,例如引入CART算法更好地处理连续属性。当然,算法都是有缺陷的,需要针对不同的问题选择最适用的算法才能将问题解决,同时优化算法的工作也需要继续努力完成。

3.6 附录

//决策树构建
// 构造函数
DecisionTree::DecisionTree(AlgorithmType algoType) : root(nullptr), algorithmType(algoType) {}
// 析构函数
DecisionTree::~DecisionTree() {freeTree(root);
}
// 释放内存
void DecisionTree::freeTree(TreeNode* node) {if (!node) return;for (auto& branch : node->branches) {freeTree(branch.second);}delete node;
}
// 计算信息熵
double DecisionTree::calculateEntropy(const std::vector<Sample>& samples) const {std::map<std::string, int> labelCount;for (const auto& sample : samples) {labelCount[sample.date]++;}double entropy = 0.0;int total = samples.size();for (const auto& pair : labelCount) {double p = static_cast<double>(pair.second) / total;entropy -= p * log2(p);}return entropy;
}
// 按属性分组
std::map<std::string, std::vector<Sample>> DecisionTree::splitByAttribute(const std::vector<Sample>& samples, const std::string& attribute) const {std::map<std::string, std::vector<Sample>> groups;for (const auto& sample : samples) {std::string value;if (attribute == "Weather") value = sample.weather;if (attribute == "Temperature") value = sample.temperature;if (attribute == "Humidity") value = sample.humidity;if (attribute == "Wind") value = sample.wind;groups[value].push_back(sample);}return groups;
}
// 计算信息增益
double DecisionTree::calculateGain(const std::vector<Sample>& samples, const std::string& attribute) const {auto groups = splitByAttribute(samples, attribute);double totalEntropy = calculateEntropy(samples);double weightedEntropy = 0.0;int totalSamples = samples.size();for (const auto& pair : groups) {double p = static_cast<double>(pair.second.size()) / totalSamples;weightedEntropy += p * calculateEntropy(pair.second);}return totalEntropy - weightedEntropy;
}
//计算分裂信息
double DecisionTree::calculateSplitInfo(const std::vector<Sample>& samples, const std::string& attribute) const {auto groups = splitByAttribute(samples, attribute);double splitInfo = 0.0;int totalSamples = samples.size();for (const auto& pair : groups) {double p = static_cast<double>(pair.second.size()) / totalSamples;if (p > 0) {splitInfo -= p * log2(p);}}return splitInfo;
}
//计算信息增益率
double DecisionTree::calculateGainRatio(const std::vector<Sample>& samples, const std::string& attribute) const {double gain = calculateGain(samples, attribute);double splitInfo = calculateSplitInfo(samples, attribute);// 避免分母为0if (splitInfo == 0) return 0.0;return gain / splitInfo;
}
// 构建决策树
TreeNode* DecisionTree::buildTree(const std::vector<Sample>& samples, const std::set<std::string>& attributes) {// 如果样本全属于一个类别std::string firstLabel = samples.front().date;bool allSame = std::all_of(samples.begin(), samples.end(), [&firstLabel](const Sample& s) {return s.date == firstLabel;});if (allSame) {TreeNode* leaf = new TreeNode();leaf->label = firstLabel;return leaf;}// 如果没有剩余属性if (attributes.empty()) {TreeNode* leaf = new TreeNode();std::map<std::string, int> labelCount;for (const auto& sample : samples) {labelCount[sample.date]++;}leaf->label = std::max_element(labelCount.begin(), labelCount.end(),[](const auto& a, const auto& b) {return a.second < b.second;})->first;return leaf;}// 选择最佳属性double maxMetric = -1;std::string bestAttribute;for (const auto& attr : attributes) {double metric;if (algorithmType == ID3) {metric = calculateGain(samples, attr); // ID3 使用信息增益}else {metric = calculateGainRatio(samples, attr); // C4.5 使用信息增益率}if (metric > maxMetric) {maxMetric = metric;bestAttribute = attr;}}// 创建当前节点TreeNode* node = new TreeNode();node->attribute = bestAttribute;// 按最佳属性划分auto groups = splitByAttribute(samples, bestAttribute);std::set<std::string> remainingAttributes = attributes;remainingAttributes.erase(bestAttribute);for (const auto& pair : groups) {node->branches[pair.first] = buildTree(pair.second, remainingAttributes);}return node;
}
// 预测
std::string DecisionTree::predict(const Sample& sample, TreeNode* node) const {if (node->label != "") return node->label;std::string value;if (node->attribute == "Weather") value = sample.weather;if (node->attribute == "Temperature") value = sample.temperature;if (node->attribute == "Humidity") value = sample.humidity;if (node->attribute == "Wind") value = sample.wind;auto it = node->branches.find(value);if (it != node->branches.end()) {return predict(sample, it->second);}return "Unknown";
}
// 可视化决策树
void DecisionTree::visualizeTree(QGraphicsScene* scene, TreeNode* node, int x, int y, int dx, int dy) const {if (!node) return;// 节点样式int nodeRadius = 20; // 节点半径QColor nodeColor = node->label.empty() ? Qt::yellow : Qt::green; // 叶子节点用绿色,非叶子用黄色QGraphicsEllipseItem* ellipse = scene->addEllipse(x - nodeRadius, y - nodeRadius, nodeRadius * 2, nodeRadius * 2, QPen(Qt::black), QBrush(nodeColor));// 节点文字QGraphicsTextItem* text = scene->addText(QString::fromStdString(node->label.empty() ? node->attribute : node->label));text->setDefaultTextColor(Qt::black);text->setFont(QFont("Arial", 10, QFont::Bold));text->setPos(x - text->boundingRect().width() / 2, y - text->boundingRect().height() / 2);// 如果是叶子节点,终止递归if (!node->label.empty()) return;// 动态调整分支间距int childCount = static_cast<int>(node->branches.size());if (childCount == 0) return;int totalWidth = (childCount - 1) * dx; // 子节点总宽度int startX = x - totalWidth / 2;       // 子节点起始位置// 遍历子节点,绘制分支线和递归调用int index = 0;for (const auto& branch : node->branches) {int childX = startX + index * dx; // 子节点X坐标int childY = y + dy;              // 子节点Y坐标// 绘制分支线QGraphicsLineItem* line = scene->addLine(x, y + nodeRadius, childX, childY - nodeRadius, QPen(Qt::black, 2));// 绘制分支文字QGraphicsTextItem* branchText = scene->addText(QString::fromStdString(branch.first));branchText->setDefaultTextColor(Qt::darkGray);branchText->setFont(QFont("Arial", 10));branchText->setPos((x + childX) / 2 - branchText->boundingRect().width() / 2,(y + childY) / 2 - branchText->boundingRect().height() + 30);// 递归绘制子节点visualizeTree(scene, branch.second, childX, childY, dx / 2, dy);++index;}
}

项目源代码:Data-structure-coursework/3/Question_3 at main · CUGLin/Data-structure-courseworkhttps://github.com/CUGLin/Data-structure-coursework/tree/main/3/Question_3

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/8909.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024收尾工作

目录 开场白 栈与队列 LeetCode232. 用栈实现队列 LeetCode225. 用队列实现栈 LeetCode102. 二叉树的层序遍历 LeetCode103. 二叉树的锯齿形层序遍历 堆&#xff08;优先级队列&#xff09; 堆排序 LeetCode215. 数组中的第 k 个最大元素 总结 开场白 今天是除夕&…

纯css实现div宽度可调整

<!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>纯css实现div尺寸可调整</title><style…

浅谈Linux的发展

目录 1.Linux背景 1.1 发展史 UNIX发展的历史 1.2开源 1.3官网 1.4.企业应用现状 1.5.发行版本 1.6 os概念&#xff0c;定位 1.Linux背景 1.1 发展史 学习Linux系统编程&#xff0c;你可能要问Linux从哪里来&#xff1f;它是怎么发展的&#xff1f;在这里简要介绍Linux的发展史…

四层网络模型

互联网由终端主机、链路和路由器组成&#xff0c;数据通过逐跳的方式&#xff0c;依次经过每条链路进行传输。 网络层的工作是将数据包从源端到目的端&#xff0c;跨越整个互联网。 网络层的数据包称为数据报。网络将数据报交给链路层&#xff0c;指示它通过第一条链路发送数据…

世上本没有路,只有“场”et“Bravo”

楔子&#xff1a;电气本科“工程电磁场”电气研究生课程“高等电磁场分析”和“电磁兼容”自学”天线“、“通信原理”、“射频电路”、“微波理论”等课程 文章目录 前言零、学习历程一、Maxwells equations1.James Clerk Maxwell2.自由空间中传播的电磁波3.边界条件和有限时域…

python学opencv|读取图像(四十六)使用cv2.bitwise_or()函数实现图像按位或运算

【0】基础定义 按位与运算&#xff1a;全1取1&#xff0c;其余取0。按位或运算&#xff1a;全0取0&#xff0c;其余取1。 【1】引言 前序学习进程中&#xff0c;已经对图像按位与计算进行了详细探究&#xff0c;相关文章链接如下&#xff1a; python学opencv|读取图像&…

如何把obsidian的md文档导出成图片,并加上文档属性

上篇关于这个插件PKMer_Obsidian 插件&#xff1a;Export Image plugin 一键将笔记转换为图片分享的文章 如何把obsidian的md文档导出成图片&#xff0c;并加上水印-CSDN博客 如何导出图片的时候让文档属性也显示出来&#xff0c;啊啊&#xff0c;这个功能找了一晚上&#xf…

MATLAB算法实战应用案例精讲-【数模应用】方向梯度直方图(HOG)(附python代码实现)

目录 前言 算法原理 特征描述 什么是方向梯度直方图? 算法思想: 实现方法: 性能提高: HOG特征提取 直方图阈值化 直方图均衡化 算法步骤: 算法流程 1. 图像预处理 2. 计算图像梯度 3. 计算梯度直方图 4. 图像HOG特征向量 直方图反向投影 其它类型图像直…

CycleGAN模型解读(附源码+论文)

CycleGAN 论文链接&#xff1a;Unpaired Image-to-Image Translation using Cycle-Consistent Adversarial Networks 官方链接&#xff1a;pytorch-CycleGAN-and-pix2pix 老规矩&#xff0c;先看看效果 总体流程 先简单过一遍流程&#xff0c;细节在代码里说。CycleGAN有…

ue5 GAS制作一个技能,技能冷却,给剑添加碰撞预设,打击敌人

总结&#xff1a; 新建文件夹 ability 取名BP_BaseAbility 新建一个技能GAB_Melee 上面技能GAB_Melee和技能基类BP_BaseAbility 进入技能GAB_Melee&#xff0c;添加打印火云掌 给这个技能添加标签 点这个号 这样命名&#xff0c;小心这个点&#xff08;.&#xff09…

工作总结:git篇

文章目录 前言基础Gerrit1.克隆2.新建本地分支和checkout3.添加到暂存区新增文件到暂存区修改已经添加到暂存区的文件取消添加到暂存区的文件 4.提交到本地仓库在不重复提交的情况下&#xff0c;修改本次提交 5.提交到远程仓库6.评审其他辅助命令 前言 目前也算是工作一段时间…

ESP32 I2S音频总线学习笔记(二):I2S读取INMP441音频数据

简介 在这个系列的上一篇文章中&#xff0c;我们介绍了ESP32 I2S音频总线的相关知识&#xff0c;简要了解了什么是I2S总线、它的通信格式&#xff0c;以及相关的底层API函数。没有看过上篇文章的可以点击文章进行回顾&#xff1a; ESP32 I2S音频总线学习笔记&#xff08;一&a…

(学习总结21)C++11 异常与智能指针

C11 异常与智能指针 异常异常的概念异常的抛出和捕获栈展开查找匹配的处理代码异常重新抛出异常安全问题异常规范标准库的异常 智能指针RAII 和智能指针的设计思路智能指针的使用场景分析C标准库智能指针的使用weak_ptr 和 shared_ptr循环引用weak_ptrshared_ptr 循环引用问题 …

智能调度体系与自动驾驶技术优化运输配送效率的研究——兼论开源AI智能名片2+1链动模式S2B2C商城小程序的应用潜力

摘要&#xff1a;随着全球化和数字化进程的加速&#xff0c;消费者需求日益呈现出碎片化和个性化的趋势&#xff0c;这对物流运输行业提出了前所未有的挑战。传统的物流调度体系与调度方式已难以满足当前复杂多变的物流需求&#xff0c;因此&#xff0c;物流企业必须积极引入大…

AndroidCompose Navigation导航精通1-基本页面导航与ViewPager

文章目录 前言基本页面导航库依赖导航核心部件简单NavHost实现ViewPagerPager切换逻辑图阐述Pager导航实战前言 在当今的移动应用开发中,导航是用户与应用交互的核心环节。随着 Android Compose 的兴起,它为开发者提供了一种全新的、声明式的方式来构建用户界面,同时也带来…

noteboolm 使用笔记

今天心血来潮&#xff0c;想要体验下AInotebook&#xff0c;看看最新的软件能够做到什么程度。 于是来到了notebooklm&#xff0c;这是一个google推出的AI笔记本的网站&#xff0c;我想知道我们能在上面做一些怎么样有趣的事情&#xff01; 网址&#xff1a;https://notebookl…

JAVA 接口、抽象类的关系和用处 详细解析

接口 - Java教程 - 廖雪峰的官方网站 一个 抽象类 如果实现了一个接口&#xff0c;可以只选择实现接口中的 部分方法&#xff08;所有的方法都要有&#xff0c;可以一部分已经写具体&#xff0c;另一部分继续保留抽象&#xff09;&#xff0c;原因在于&#xff1a; 抽象类本身…

ReactNative react-devtools 夜神模拟器连调

目录 一、安装react-devtools 二、在package.json中配置启动项 三、联动 一、安装react-devtools yarn add react-devtools5.3.1 -D 这里选择5.3.1版本&#xff0c;因为高版本可能与夜神模拟器无法联动&#xff0c;导致部分功能无法正常使用。 二、在package.json中配置启…

关于使用Mybatis-plus的TableNameHandler动态表名处理器实现分表业务的详细介绍

引言 随着互联网应用的快速发展&#xff0c;数据量呈爆炸式增长。传统的单表设计在面对海量数据时显得力不从心&#xff0c;容易出现性能瓶颈、查询效率低下等问题。为了提高数据库的扩展性和响应速度&#xff0c;分表&#xff08;Sharding&#xff09;成为了一种常见的解决方案…

【开源免费】基于Vue和SpringBoot的在线文档管理系统(附论文)

本文项目编号 T 038 &#xff0c;文末自助获取源码 \color{red}{T038&#xff0c;文末自助获取源码} T038&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 查…