ID3算法 决策树 C++实现

简介: 人工智能课的实验。 数据结构:多叉树 这个实验我写了好久,开始的时候从数据的读入和表示入手,写到递归建树的部分时遇到了瓶颈,更新样例集和属性集的办法过于繁琐; 于是参考网上的代码后重新写,建立决策树类,把属性集、样例集作为数据成员加入类中,并设立访问数组,这样每次更新属性集、样例集时只是标记访问数组的对应元素即可,不必实际拷贝。

人工智能课的实验。

数据结构:多叉树

这个实验我写了好久,开始的时候从数据的读入和表示入手,写到递归建树的部分时遇到了瓶颈,更新样例集和属性集的办法过于繁琐;

于是参考网上的代码后重新写,建立决策树类,把属性集、样例集作为数据成员加入类中,并设立访问数组,这样每次更新属性集、样例集时只是标记访问数组的对应元素即可,不必实际拷贝。

主函数:

 1 #include "Decision_tree.h"
 2 using namespace std;
 3 int main()
 4 {
 5     int num_attr,num_example;
 6     char filename[30];
 7     cout << "请输入训练集文件名:" << endl;
 8     cin >> filename;
 9     freopen(filename, "r", stdin);//从样例文件读入训练内容
10     cin >> num_attr >> num_example;//读入属性个数、例子个数
11     Decision_tree my_tree=Decision_tree(num_attr,num_example);
12     fclose(stdin);
13     freopen("CON", "r", stdin);//重定向标准输入到控制台
14     my_tree.display_attr();
15     cout << "决策树已建成,按深度优先遍历结果如下:" << endl;
16     my_tree.traverse();
17     do{
18         cout << "请输入测试数据,格式:属性1值 属性2值..." << endl;
19         Example test;
20         for (int i = 0; i < num_attr; i++)
21             cin >> test.values[i];
22         int result = my_tree.judge(test);
23         if (result == 1) cout << "分类结果为P" << endl;
24         else if (result == -1) cout << "分类结果为N" << endl;
25         else if (result == -2) cout << "无法根据已有样例集判断" << endl;
26         cout << "继续吗?(y/n)";
27         fflush(stdin);
28     } while (getchar() == 'y');
29 }

属性结构体

struct Attribute//属性
{
    string name;
    int count;//属性值个数
    int number;//属性的秩
    string values[MAX_VAL];
};

样例结构体

struct Example//样例
{
    string values[MAX];
    int pn;
    Example(){ pn = 0; }//默认为未分类的
};

决策树的结点

typedef struct Node//树的结点
{
    Attribute attr;
    Node* children[MAX_VAL];
    int classification[MAX_VAL];
    Node(){}
}Node;

决策树类的实现

  1 class Decision_tree//决策树
  2 {
  3     Node *root;
  4     Example e[MAX];//样例全集
  5     Attribute a[MAX_ATTR];//属性全集
  6     int num_attr, num_example;
  7     int visited_exams[MAX];//样例集的访问情况
  8     int visited_attrs[MAX_ATTR];//属性集的访问情况
  9     Node* recursive_build_tree(int left_e[], int left_a[])//递归建树
 10     {
 11         double max = 0;
 12         int max_attr=-1;
 13         for (int i = 0;i<num_attr;i++)
 14         {//求信息增益最大的属性
 15             if (left_a[i]) continue;
 16             double temp = Gain(left_e, i);
 17             if (max<temp)
 18             {
 19                 max = temp;
 20                 max_attr = i;
 21             }
 22         }
 23         if (max_attr == -1) return NULL;//已没有可判的属性,返回空指针
 24         //cout << a[max_attr].name << endl;
 25         //以这个属性为结点,以各属性值为分支递归建树
 26         int p = 0, n = 0;
 27         Node *new_node=new Node();
 28         new_node->attr = a[max_attr];
 29         for (int i = 0; i<a[max_attr].count;i++)
 30         {//遍历这个属性的所有属性值
 31             for (int j = 0; j < num_example;j++)
 32             {//得到第i个属性值的正反例总数
 33                 if (left_e[j]) continue;
 34                 if (!e[j].values[max_attr].compare(a[max_attr].values[i]))
 35                 {//例子和属性都是循秩访问的,所以向量元素的顺序不能变
 36                     if (e[j].pn) p++;
 37                     else n++;
 38                 }
 39             }
 40             //cout << a[max_attr].values[i] << " ";
 41             //cout << p << " " << n << endl;
 42             if (p && !n)//全是正例,不再分
 43             {
 44                 //cout << "P" << endl;
 45                 new_node->classification[i] = 1;
 46                 new_node->children[i] = NULL;
 47             }
 48             else if (n && !p)//全是反例,不再分
 49             {
 50                 //cout << "N" << endl;
 51                 new_node->classification[i] = -1;
 52                 new_node->children[i] = NULL;
 53             }
 54             else if (!p && !n)//例子集已空
 55             {
 56                 //cout << "none" << endl;
 57                 new_node->classification[i] = -2;//表示未训练到这种分类,无法判断
 58                 new_node->children[i] = NULL;
 59             }
 60             else//例子集不空,且尚未能区分正反,更新访问情况,递归
 61             {
 62                 new_node->classification[i] = 0;
 63                 left_a[max_attr] = 1;//更新属性访问情况
 64                 int left_e_next[MAX];//下一轮的例子集(为便于回溯,不修改原例子集)
 65                 for (int k = 0; k < num_example; k++)
 66                     left_e_next[k] = left_e[k];
 67                 for (int j = 0; j < num_example; j++)
 68                 {
 69                     if (left_e[j]) continue;
 70                     if (!e[j].values[max_attr].compare(a[max_attr].values[i]))
 71                         left_e_next[j] = 0;//属性值匹配的例子,入选下一轮例子集
 72                     else left_e_next[j] = 1;//属性值不匹配,筛除
 73                 }
 74                 new_node->children[i] = recursive_build_tree(left_e_next, left_a);//递归
 75                 left_a[max_attr] = 0;//恢复属性访问情况
 76             }
 77             p = 0;
 78             n = 0;
 79         }
 80         return new_node;
 81     }
 82     double I(int p, int n)
 83     {
 84         double a = p / (p + (double)n);
 85         double b = n / (p + (double)n);
 86         if (a == 0 || b == 0) return 0;
 87         return -a*log(a) / log(2) - b*log(b) / log(2);
 88     }
 89     double Gain(int left_e[], int cur_attr)//计算信息增益
 90     {
 91         int sum_p=0, sum_n=0;
 92         int p[10] = { 0 }, n[10] = { 0 };
 93         for (int i = 0; i < num_example; i++)
 94         {//求样例集的p,n
 95             if (left_e[i]) continue;
 96             if (e[i].pn) sum_p++;
 97             else sum_n++;
 98         }
 99         if (!sum_p && !sum_n)
100         {
101             //cout << "no more examples!" << endl;
102             return -1;//样例集是空集
103         }
104             
105         double sum_Ipn = I(sum_p, sum_n);
106         for (int i = 0; i < a[cur_attr].count; i++)
107         {//求第i个属性值的p,n
108             for (int j = 0; j < num_example; j++)
109             {
110                 if (left_e[j]) continue;
111                 if (!e[j].values[cur_attr].compare(a[cur_attr].values[i]))
112                     if (e[j].pn) p[i]++;
113                     else n[i]++;
114             }
115         }
116         double E = 0;
117         for (int i = 0; i < a[cur_attr].count; i++)//计算属性的期望
118             E += (p[i] + n[i])*I(p[i], n[i]);
119         E /= (sum_p + sum_n);
120         //cout << a[cur_attr].name <<sum_Ipn - E << endl;
121         return sum_Ipn - E;
122     }
123     void recursive_traverse(Node *current)//DFS递归遍历
124     {
125         if (current == NULL) return;
126         cout << current->attr.name << endl;
127         for (int i = 0; i < current->attr.count; i++)
128         {
129             cout << current->attr.values[i] << " " << current->classification[i] << endl;
130             recursive_traverse(current->children[i]);
131         }
132     }
133     int recursive_judge(Example exa, Node *current)
134     {
135         for (int i = 0; i < current->attr.count; i++)
136         {
137             if (!exa.values[current->attr.number].compare(current->attr.values[i]))
138             {
139                 if (current->children[i]==NULL) return current->classification[i];
140                 else return recursive_judge(exa, current->children[i]);        
141             }        
142         }
143         return 0;
144     }
145 public:
146     Decision_tree(int num1,int num2)
147     {
148         
149         //通过读文件初始化
150         num_attr = num1;
151         num_example = num2;
152 
153         for (int i = 0; i<num_attr; i++)
154         {
155             a[i].number = i;//属性的秩
156             cin>>a[i].name;//读入属性名
157             cin>>a[i].count;//读入此属性的属性值个数
158             for (int j = 0; j<a[i].count; j++)
159             {
160                 cin>>a[i].values[j];//读入各属性值
161             }
162         }
163         
164         for (int i = 0; i<num_example; i++)
165         {
166             string temp;
167             for (int j = 0; j < num_attr; j++)
168             {
169                 cin>>e[i].values[j];
170             }
171             cin >> temp;
172             if (!temp.compare("P")) e[i].pn = 1;
173             else e[i].pn = 0;
174         }
175         //检查
176         /*for (int i = 0; i<num_attr; i++)
177         {
178             cout << a[i].name << endl;//读入属性名
179             for (int j = 0; j<a[i].count; j++)
180             {
181                 cout<<a[i].values[j]<<" ";//读入各属性值
182             }
183             cout << endl;
184         }
185         for (int i = 0; i<num_example; i++)
186         {
187             for (int j = 0; j < num_attr; j++)
188                 cout<<e[i].values[j]<<" ";
189             cout<<e[i].pn<<endl;
190             
191         }
192         */
193         memset(visited_exams, 0, sizeof(visited_exams));
194         memset(visited_attrs, 0, sizeof(visited_attrs));
195         root = recursive_build_tree(visited_exams,visited_attrs);
196     }
197     void traverse()
198     {
199         recursive_traverse(root);
200     }
201     int judge(Example exa)//判断
202     {
203         int result=recursive_judge(exa,root);
204         return result;
205     }
206     void display_attr()//显示属性
207     {
208         cout << "There are " << num_attr << " attributes, they are" << endl;
209         for (int i = 0; i < num_attr; i++)
210         {
211             cout << "[" << a[i].name << "]" << endl;
212             for (int j = 0; j < a[i].count; j++)
213                 cout << a[i].values[j] << " ";
214             cout << endl;
215         }
216     }
217 };
Decision_tree

现在这个版本的代码用了10小时完成,去检查时被研究生贬得一文不值。。。也的确,现在我们写的实验题目面向的都是规模非常小的问题,自然体会不到自己的代码在大数据面前的劣势。不过我现在确实学得太少了,很多数据结构都没有动手实现过,算法也是。对C++也只能算入了门。俗话说“磨刀不误砍柴工”,“工欲善其事,必先利其器”,先把基础知识学好,多做基本练习,学到的数据结构和算法都动手实现一遍,这样遇到实际问题也好对应到合适的数据结构和算法。

另外,参照一本书学习好的代码风格和习惯也是很重要的,因为写代码的习惯是思维习惯的反映,而我现在还处于初学者阶段,按照一种典型的流派模仿,构建起自己的思维模式后再谈其他的。

忽然觉得自己学了快两年编程还这么水实在是不能忍,都怪大一时年少不懂事没好好学基础。。。

不过,“悟已往之不谏,知来者之可追”,有了方向,一步步走下去就好,不求优于别人,但一定要“优于过去的自己”。

 

目录
相关文章
|
5月前
|
存储 监控 算法
基于 C++ 哈希表算法实现局域网监控电脑屏幕的数据加速机制研究
企业网络安全与办公管理需求日益复杂的学术语境下,局域网监控电脑屏幕作为保障信息安全、规范员工操作的重要手段,已然成为网络安全领域的关键研究对象。其作用类似网络空间中的 “电子眼”,实时捕获每台电脑屏幕上的操作动态。然而,面对海量监控数据,实现高效数据存储与快速检索,已成为提升监控系统性能的核心挑战。本文聚焦于 C++ 语言中的哈希表算法,深入探究其如何成为局域网监控电脑屏幕数据处理的 “加速引擎”,并通过详尽的代码示例,展现其强大功能与应用价值。
109 2
|
6月前
|
存储 算法 C++
Windows共享文件:探秘C++实现的B树索引算法奇境
在数字化时代,Windows共享文件的高效管理至关重要。B树算法以其自平衡多路搜索特性,在文件索引与存储优化中表现出色。本文探讨B树在Windows共享文件中的应用,通过C++实现具体代码,展示其构建文件索引、优化数据存储的能力,提升文件检索效率。B树通过减少磁盘I/O操作,确保查询高效,为企业和个人提供流畅的文件共享体验。
|
5月前
|
存储 机器学习/深度学习 算法
KMP、Trie树 、AC自动机‌ ,三大算法实现 优雅 过滤 netty 敏感词
KMP、Trie树 、AC自动机‌ ,三大算法实现 优雅 过滤 netty 敏感词
KMP、Trie树 、AC自动机‌ ,三大算法实现 优雅 过滤 netty  敏感词
|
3月前
|
存储 监控 算法
基于跳表数据结构的企业局域网监控异常连接实时检测 C++ 算法研究
跳表(Skip List)是一种基于概率的数据结构,适用于企业局域网监控中海量连接记录的高效处理。其通过多层索引机制实现快速查找、插入和删除操作,时间复杂度为 $O(\log n)$,优于链表和平衡树。跳表在异常连接识别、黑名单管理和历史记录溯源等场景中表现出色,具备实现简单、支持范围查询等优势,是企业网络监控中动态数据管理的理想选择。
82 0
|
4月前
|
存储 机器学习/深度学习 算法
基于 C++ 的局域网访问控制列表(ACL)实现及局域网限制上网软件算法研究
本文探讨局域网限制上网软件中访问控制列表(ACL)的应用,分析其通过规则匹配管理网络资源访问的核心机制。基于C++实现ACL算法原型,展示其灵活性与安全性。文中强调ACL在企业与教育场景下的重要作用,并提出性能优化及结合机器学习等未来研究方向。
100 4
|
5月前
|
监控 算法 数据处理
基于 C++ 的 KD 树算法在监控局域网屏幕中的理论剖析与工程实践研究
本文探讨了KD树在局域网屏幕监控中的应用,通过C++实现其构建与查询功能,显著提升多维数据处理效率。KD树作为一种二叉空间划分结构,适用于屏幕图像特征匹配、异常画面检测及数据压缩传输优化等场景。相比传统方法,基于KD树的方案检索效率提升2-3个数量级,但高维数据退化和动态更新等问题仍需进一步研究。未来可通过融合其他数据结构、引入深度学习及开发增量式更新算法等方式优化性能。
143 17
|
5月前
|
存储 监控 算法
局域网上网记录监控的 C# 基数树算法高效检索方案研究
在企业网络管理与信息安全领域,局域网上网记录监控是维护网络安全、规范网络行为的关键举措。随着企业网络数据量呈指数级增长,如何高效存储和检索上网记录数据成为亟待解决的核心问题。基数树(Trie 树)作为一种独特的数据结构,凭借其在字符串处理方面的卓越性能,为局域网上网记录监控提供了创新的解决方案。本文将深入剖析基数树算法的原理,并通过 C# 语言实现的代码示例,阐述其在局域网上网记录监控场景中的具体应用。
124 7
|
4月前
|
机器学习/深度学习 算法 搜索推荐
决策树算法如何读懂你的购物心理?一文看懂背后的科学
"你为什么总能收到刚好符合需求的商品推荐?你有没有好奇过,为什么刚浏览过的商品就出现了折扣通知?
|
4月前
|
机器学习/深度学习 存储 算法
基于 C++ 布隆过滤器算法的局域网上网行为控制:URL 访问过滤的高效实现研究
本文探讨了一种基于布隆过滤器的局域网上网行为控制方法,旨在解决传统黑白名单机制在处理海量URL数据时存储与查询效率低的问题。通过C++实现URL访问过滤功能,实验表明该方法可将内存占用降至传统方案的八分之一,查询速度提升约40%,假阳性率可控。研究为优化企业网络管理提供了新思路,并提出结合机器学习、改进哈希函数及分布式协同等未来优化方向。
91 0
|
6月前
|
存储 监控 算法
基于 C++ 哈希表算法的局域网如何监控电脑技术解析
当代数字化办公与生活环境中,局域网的广泛应用极大地提升了信息交互的效率与便捷性。然而,出于网络安全管理、资源合理分配以及合规性要求等多方面的考量,对局域网内计算机进行有效监控成为一项至关重要的任务。实现局域网内计算机监控,涉及多种数据结构与算法的运用。本文聚焦于 C++ 编程语言中的哈希表算法,深入探讨其在局域网计算机监控场景中的应用,并通过详尽的代码示例进行阐释。
119 4

热门文章

最新文章