数据挖掘关联规则FP-growth算法
- 格式:doc
- 大小:120.00 KB
- 文档页数:21
目录
摘要:....................................................................................................... 11.介绍..................................................................................................... 1
2.数据挖掘................................................................................................ 1
3.关联规则................................................................................................ 34.数据采掘工具的研制及其应用 ........................................................ 45.程序实现................................................................................................ 5算法描述................................................................................................ 6数据结构............................................................................................ 10算法实现细节.................................................................................... 13
6.总结.................................................................................................... 20
7.致谢.................................................................................................... 20
摘要:
关联规则在数据挖掘是一个重要的研究内容。
而产生频繁集则是产生关联规则的第一步。
在大多数以前的实现中,人们普遍采用了类似于Apriori[2]的算法。
这种算法有一个很大的缺点,就是使用了不断产生候选集并加以测试的方式来得到频繁集。
但是,产生候选集的代价是很大的。
本文分析并且实现了在论文[1]中提出的FP-growth算法。
FP-growth算法的优点是节省时间和空间,对大规模数据采用分治的办法以避免规模巨大难以接受。
FP-growth算法主要通过FP-tree来构造频繁集。
FP-tree是一个数据库里跟产生频繁集有关的信息的压缩表示。
在具体的实现中,我通过了一系列的从低到高的数据结构来实现它,并进而实现整个算法。
该实现基于Windows 平台,编程工具是Visual C++ 6.0,许多地方还用到了C++的标准模板库。
1.介绍
数据挖掘技术的出现是伴随着当今时代信息的爆炸性增长和人们面对纷繁的数据得到决策支持而出现的.数据挖掘工具中要实现的一个很重要的功能就是关联规则的找寻,而关联规则找寻的第一步就是要找到相应的频繁集.
本文就是建立在对一个频繁集产生算法的分析和实现的基础上的.通过一个程序具体实现了FP-growth算法,并将它作为一个使用数据挖掘工具,ARMiner的一部分.
本文的第2部分将介绍一些数据挖掘的基本知识.
第3部分讨论关联规则的一些问题.
第4部分是本文所实现的程序所属的数据挖掘工具ARMiner的一些介绍.
第5部分结合程序设计着重讨论一下本文是怎样实现FP-growth算法的。
2.数据挖掘
数据挖掘(Data Mining)就是从大量的、不完全的、有噪声的、模糊的、随机的实际应用数据中,提取隐含在其中的、人们事先不知道的、但又是潜在有用的信息和知识的过程。
何为知识?从广义上理解,数据、信息也是知识的表现形式,但是人们更把概念、规则、模式、规律和约束等看作知识。
人们把数据看作是形成知识的源泉,好像从矿石中采矿或淘金一样。
原始数据可以是结构化的,如关系数据库中的数据;也可以是半结构化的,如文本、图形和图像数据;甚至是分布在网络上的异构型数据。
发现知识的方法可以是数学的,也可以是非数学的;可以是演绎的,也可以是归纳的。
发现的知识可以被用于信息管理,查询优化,决策支持和过程控制等,还可以用于数据自身的维护。
因此,数据挖掘是一门交叉学科,它把人们对数据的应用从低层次的简单查询,提升到从数据中挖掘知识,提供决策支持。
在这种需求牵引下,汇聚了不同领域的研究者,尤其是数据库技术、人工智能技术、数理统计、可视化技术、并行计算等方面的学者和工程技术人员,投身到数据挖掘这一新兴的研究领域,形成新的技术热点。
这里所说的知识发现,不是要求发现放之四海而皆准的真理,也不是要去发现崭新的自然科学定理和纯数学公式,更不是什么机器定理证明。
实际上,所有发现的知识都是相对的,是有特定前提和约束条件,面向特定领域的,同时还要能够易于被用户理解。
最好能用自然语言表达所发现的结果。
数据挖掘的主要过程如下:
1. 确定业务对象
清晰地定义出业务问题,认清数据挖掘的目的是数据挖掘的重要一步.挖掘的最后结构是不可预测的,但要探索的问题应是有预见的,为了数据挖掘而数据挖掘则带有盲目性,是不会成功的.
2. 数据准备
1) 数据的选择
搜索所有与业务对象有关的内部和外部数据信息,并从中选择出适用于数据挖掘应用的数据.
2) 数据的预处理
研究数据的质量,为进一步的分析作准备.并确定将要进行的挖掘操作的类型.
3) 数据的转换
将数据转换成一个分析模型.这个分析模型是针对挖掘算法建立的.建立一个真正适合挖掘算法的分析模型是数据挖掘成功的关键.
3. 数据挖掘
对所得到的经过转换的数据进行挖掘.除了完善从选择合适的挖掘算法外,其余一切工作都能自动地完成.
4. 结果分析
解释并评估结果.其使用的分析方法一般应作数据挖掘操作而定,通常会用到可视化技术.
5. 知识的同化
将分析所得到的知识集成到业务信息系统的组织结构中去.
数据挖掘技术目前已经有不少成功的范例.其实在日常生活中我们也可以看到许多数据挖掘的应用.例如,如果你在沪上一家比较著名的电子商务网站购买了一张周星驰的经典搞笑片”大话西游”,该网站会提醒你,
【购买该商品的用户还买了这些商品】
行运一条龙
97家有喜事
武状元苏乞儿
月光宝盒
秀兰邓波儿(12套装)
这些就是用数据挖掘技术从购买这部片子的人群中统计出来的.当然这只是一种比较简单的应用.更复杂的应用见下面这个例子:
美国Firstar银行使用Marksman数据挖掘工具,根据客户的消费模式预测何时为客户提供何种产品。
Firstar银行市场调查和数据库营销部经理发现:公共数据库中存储着关于每位消费者的大量信息,关键是要透彻分析消费者投入到新产品中的原因,在数据库中找到
一种模式,从而能够为每种新产品找到最合适的消费者。
Marksman能读取800到1000个变量并且给它们赋值,根据消费者是否有家庭财产贷款、赊帐卡、存款证或其它储蓄、投资产品,将它们分成若干组,然后使用数据挖掘工具预测何时向每位消费者提供哪种产品。
预测准客户的需要是美国商业银行的竞争优势。
3.关联规则
关联规则是关联分析中的一种常用技术(另一种是序列模式)。
关联规则是寻找在同一个事件中出现的不同项的相关性。
其形式化的陈述如下。
(参照[2])
设L={i1,i2,...im}是所有项的集合。
D是交易集合,其中每个交易T是一个项的集合并且T⊆L。
每一个交易T都有一个唯一的标识,TID。
如果项集合X⊆L且X⊆T,我们就说交易T包含X.一个关联规则(association rule)就是这样一种形式的关系:X==>Y,其中X⊂L,Y⊂L,并且X⋃Y=∅.
另外两个和关联规则有关的概念是支持度(support)和可信度(confidence)
根据[2]的定义,对于一个关联规则X==>Y,.在交易集合D中, Txy={T|(X⋃Y)⊆T⋂T∈D},Tx={T|X⊂T⋂T∈D},支持度为s,如果|Txy|/|D|=s%;可信度为c,如果|Txy|/|Tx|=c%.
举例来说,有一个特定的关联规则,锤子==>钉子,这个规则可能意味着买锤子的人也有倾向买钉子.在一个有10000条交易记录的交易数据库中,若有300条记录既包含了锤子又包含了钉子,则我们说此关联规则的支持度为300/10000=3%.这个支持度还是比较的高,但我们并不能就此作出这个关联有意义的结论.假如通过统计发现一共有6000条记录包含了”锤子”,则仅有可信度300/6000=5%的购买了”锤子”的人又去购买了钉子,那么这个关联规则的可信度不高,它并没有告诉我们什么.但是假如只有600人购买了锤子,则我们可以知道其中有一半的人又去购买了钉子,这个现象就值得注意了.
另一个更详细的例子来自于[4].
总交易笔数:1000
包含”锤子”:50
包含”钉子”:80
包含”钳子”:20
包含”锤子”和”钉子”:15
包含”钳子”和”钉子”:10
包含”锤子”和”钳子”:10
包含”锤子”,”钳子”和”钉子”:15
则可以计算出:
“锤子和钉子”的支持度=1.5%(15/1000)
“锤子,钉子和钳子”的支持度=0.5(5/1000)
“锤子==>钉子”的可信度=30%(15/50)
“钉子==>锤子”的可信度=19%(15/80)
“锤子和钉子==>钳子”的可信度=33%(5/15)
“钳子==>锤子和钉子”的可信度=25%(5/20)
注意到数据挖掘得到的关联规则,它只是对数据库中数据之间相关性的一种描述。
还没有其他数据来验证得到的规则的正确性.
除了支持度和可信度还有其他的关联规则评价标准如改善度(lift)和兴趣度(interest).
本文的实现当中,对支持度的定义和上述不同,参见第5部分.
4.数据采掘工具的研制及其应用
“数据采掘工具的研制及其应用”(863-306-02-05)是国家863项目,是前一阶段863项目研究的继续,由复旦大学计算机系数据库组承担。
目前已经设计并实现系统原型数据采掘工具ARMiner(图1)。
图1
该工具的主要功能块的说明如下:(上图及说明主要参考[3])
数据预处理负责对待采掘的数据源作必要的准备,
其各模块功能如下:
(1)数据获取
指定要使用的数据集的名称及位置。
(2)数据取样
对获取的数据从中取样,取样的方式有很多种,根据数据采掘的目的性灵活选用。
例如,随机取样,数据集中每一组观测值都有相同的被取样概率;等距取样,对数据编号,取样的观测值之间的距离相等;分层取样,将样本总体分成若干层次,每个层次中的观测值都具有相同的被选用概率,但不同层次之间设定的概率可不同,使模型具有更好的拟和度;起始顺序取样,从输入数据的起始处开始取样,对取样数量预先规定;分类取样,按观测值的某种属性分类,取样以类为单位。
(3)数据筛选
通过数据筛选筛选掉不希望包括进来的观测值。
例如,对于分类变量可给定某一类的
类值说明此类观测值是要排除于取样范围之外的,对于区间变量可指定其值大于或小于某值的那些观测值排除于取样范围之外。
(4)数据转换
将某一个数据进行某种转换操作,然后将转换后的值作为新的变量存放在样本数据中,而转换的目的是为了把数据和将来要建立的模型拟和得更好。
数据采掘即对经过预处理的数据进行采掘,可分为两个模块:
(1)数据采掘数据库
在进行数据采掘分析模型的操作之前,要建立一个数据采掘的数据库(DMDB),放置此次要进行操作的数据,可预先进行一些诸如变量最大、最小、平均、标准差等处理,为数据采掘建立一个良好的工作环境。
(2)数据采掘过程
利用某一种数据采掘算法进行数据采掘。
数据评价即用一种通用的数据采掘评价的架构来比较不同模型的效果;预报各种不同类型分析工具的结果。
在进行各种比较和预报的评价之后,给出一系列标准的图表,供用户进行定量评价。
本文所基于的程序实现即是属于上面数据采掘部分,数据采掘过程中的关联规则部分.
5.程序实现
程序所要实现的FP-growth算法是一个频繁集产生算法,与一般的类似于Apriori的频繁集产生算法相比,FP-growth的优点在于它不需要产生大量的候选集,因而在时间和空间上都有很好的效率.关于FP-growth请参看本部分中的算法描述部分。
程序的实现是基于Windows95/NT平台,编译器是Visual C++6.0.这是为了与程序所属的数据挖掘工具ARMiner相兼容.
程序在定义数据结构和实现算法的时候主要有以下一些考量。
首先,程序必须注意速度.速度问题在频繁集产生的算法中应该是比较重要的.因为它们常常涉及大量数据的处理.因此在程序中许多需要排序的数据结构都使用了平衡树,这是一种高效的对全序元素的组织方式.
其次,是对空间占用的考量。
同样因为要处理大量的数据,所以对内存的管理要尤其严格,如果出现内存泄漏将是很麻烦的事情.
第三,是编程风格的考虑.要尽量采用通用化的代码.因为对于一个比较大的系统来说,任何一个小的部分都应该清楚明白,便于别人阅读和修改.
基于上面三点的考量,我在对程序的数据结构的定义和算法的实现的时候大量采用了C++的标准模板库(STL,Standard Template Library).这是基于以下的事实,关联规则的挖掘所涉及到的数据结构和基本算法都是以往的常用数据结构和算法,如向量类,集合类,快速排序算法等.而STL正是包含了这样许多通用的数据结构和基本算法的库.
模板实际上是一种宏,但比宏安全可靠的多.它在编译期间就被处理了,因此根本不会影响程序的执行速度.又因为,STL中的种种数据结构和算法都是精心编写的,对同一问题的实现一般是比较经典的.不仅在速度上有优势,而且还避免了许多低层次上的内存泄漏问题.使我们把注意力集中到各个函数接口上面.
由于STL本身就是一中很通用的库,因此可以很好的满足第三个条件.用他编写的代码,对类似的数据结构和算法的处理都很相象.
例如,在STL中有一大类模板叫容器模板(container),简单的说就是包含了许多元素的类的模板,象vector,array,set等就属于容器模板.对于这些模板中的元素的访问都可以通过它们的遍历子,用完全一致的方式进行.请看下面两段程序清单:
void print(FreqSet* pSet){
FreqSet_Iter fit;
for(fit=pSet->begin();fit!=pSet->end();fit++){
printf("%s %d \n",(*fit),(*fit).second.count);
}
}
void print(Table* pTable){
Table_Iter tit;
for(tit=pTable->begin();tit!=pTable->end();tit++){
printf("%s %d \n",(*tit).name,(*tit).count);
}
}
这两个函数是在调试的时候分别打印出频繁集pSet和头表pTable中的元素的.
尽管pSet的类FreqSet是由map模板生成的,pTable的类Table是由multiset生成的,但对它们的元素的访问形式几乎一模一样都是利用相应的遍历子.涉及的函数名也很相似.其实,所有的容器模板都定义了函数begin()和end()代表元素列表的起始和结束.另外还有大量的具有相同名字和类似功能的函数,方便了我们对数据结构的管理。
在选择数据库访问方式的时候,用了最近比较流行的ADO方式,它比ODBC更加灵活,速度也有改善.对网络数据库的支持也很好.
下面首先对要实现的算法做一个描述,然后针对数据结构和算法两方面对程序的实现进行详细说明.
算法描述
本文所实现的频繁集产生算法建立在两个基本的函数之上.
在具体描述这两个算法之前,还要介绍一些建立它们所需要的知识.
FP-tree
论文[1]对FP-tree的定义如下:
1,它有一个标记为”null”的根节点,它的子节点为一个项前缀子树(item prefix subtree)的集合,还有一个频繁项(frequent item)组成的头表(header table).
2,每个项前缀子树的节点有三个域:item-name,count,node_link.item-name记录了该节点所代表的项的名字.count记录了所在路径代表的交易(transaction)中达到此节点的交易个数.node_link指向下一个具有同样的item-name域的节点,要是没有这样一个节点,就为null.
3,频繁项头表(frequent item header table)的每个表项(entry)由两个域组成:(1)item-name.(2)node_link.node_link指向FP-tree中具有与该表项相同item-name域的第一个节点.
第一个算法的就是根据一个数据库建立这样一棵FP-tree.
下面是一个比较形式化的描述.
Algorithm1
输入:一个交易数据库DB和一个最小支持度threshold.
输出:它的FP-tree.
步骤:
1,扫描数据库DB一遍.得到频繁项的集合F和每个频繁项的支持度.把F按支持度递降排序,结果记为L.
2,创建FP-tree的根节点,记为T,并且标记为’null’.然后对DB中的每个交易Trans做如下的步骤.
根据L中的顺序,选出并排序Trans中的频繁项.把Trans中排好序的频繁项列表记为[p|P],其中p是第一个元素,P是列表的剩余部分.调用insert_tree([p|P],T).
函数insert_tree([p|P],T)的运行如下.
如果T有一个子结点N,其中N.item-name=p.item-name,则将N的count域值增加1;否则,创建一个新节点N,使它的count为1,使它的父节点为T,并且使它的node_link和那些具有相同item_name域串起来.如果P非空,则递归调用insert_tree(P,N).
完毕.
通过一个例子,可以更清楚的看到一棵FP-tree是怎样建立的.
设有如下交易数据库:
我们首先扫描一遍这个数据库,计算每个项的计数值并保存在频繁项的集合F 中,F={(a:3),(b:3),(c:4),(d:1),(e:1),(f:4),(g:1),(h:1),(i:1),(j:1),(k:1),(l:2),(m:3),(n:1),(o:2),(p:3)}.集合中每个元素的第二个分量代表第一个分量所代表项的支持度.我们假定最小支持度为3.选出F中支持度大于3的项,并按支持度递降排列,将结果放入列表L中,此时,L={(f:4),(c:4),(a:3),(b:3),(m:3),(p:3)}.
执行算法的第二步,创建一个标记为”null”的根节点.开始对数据库的第二遍扫描.对第一个交易的扫描将建立这棵树的第一个分支:<(f:1),(c:1),(a:1),(m:1),(p:1)>.注意,在这个交易中的频繁项已经被按照L中的顺序进行排序了.对于第二个交易来说,它已经排序好的频繁项列表<f,c,a,b,m>同已经存在的路径<f,c,a,m,p>有共同的前缀<f,c,a>,所以把这个前缀中的所有节点的count增加1.然后新节点(b:1)被创建并且被作为节点(a:2)的子节点,随后,新节点(m:1)被创建并做为节点(b:1)的子节点.对第三个交易,因为它的频繁项列表只同以f为前缀的子树有一个共同节点<f>.所以把这个节点的count增加1,并且创建新节点(b:1),把它作为(f::3)的子节点.以此类推,扫描完整个数据库.
为了方便对树的遍历.一个频繁项头表(frequent item header table)被建立了,头表表项的node_link指向树里面具有相同item_name的节点.具有相同item_name的节点通过node_link 被连结在一起.
c
a
b
m
p
图2
FP-tree是一个压缩的数据结构,它用较少的空间存储了后面频繁集挖掘所需要的全部信息.
算法二建立在算法一所产生的F-tree上面.它会递归调用自己,并且反复调用算法一产生新的FP-tree.
输入:一棵用算法一建立的树Tree
输出:所有的频繁集
步骤:
调用FP-growth(Tree,null).
下面是对过程FP-growth的伪码描述.
Procedure FP-growth(Tree,α)
{
if Tree只有一条路径P
then 对P中的节点的每一个组合(记为β)做(1)
(1)产生频繁集β⋃α,并且把它的支持度指定为β中节点的最小支持度.
else 对Tree的头表从表尾到表头的每一个表项(记为a)做(2)-(5)
(2)产生频繁集β=a⋃α,并且支持度为a的支持度
(3)建立β的条件模式库(conditional pattern base)和β的条件树(conditional FP-tree)Tree2
(4)i f Tree2!=∅
(5)t hen调用FP-growth(Tree2,β)
}
关于什么是条件模式库(conditional pattern base)和条件树(conditional FP-tree),我们继续上面那个例子来介绍.
我们把图2中已经得到的FP-tree和相应的头表作为算法二的输入.按照从表尾到表头的顺序考察表中的每一个表项.
从表项p出发,先可以得到一个频繁集(p:3).然后,我们可以得到包含p的所有模式.顺着p 表项的node_link域,我们找到所有包含p的路径<f:4,c:3,a:3,m:2,p:2>和<c:1,b:1,p:1>.对第一条路径,虽然f出现了3次,c和a各出现了两次,但它们同p在一起只出现了2次,所以把它们的
计数改为2,得到<f:2,c:2,a:2,m:2,p:2>.第二条路径中各项的计数都已相同,不用修改了.把这2
条路径中的p 项去掉,就得到了p 的条件模式库,{(f:2,c:2,a:2,m:2),(c:1,b:1)},这是下一步递归的依据.我们把这个条件模式库看作一个数据库,在上面运用算法一产生一个新的FP-tree,这就是算法二中的条件树,这个新树只有一个节点(c:3).这时又得到一个新的频繁集(cp:3).包含p 项的频繁集就挖掘完了.
接着考察m,先还是得到(m:3).顺着它的node_link 得到2条路径<f:4,c:3,a:3,m:2>和<f:4,c:3,a:3,b:1,m:1>.这时,不需要考虑p 项了,因为含有它的频繁集都已经产生完毕了.按照上面的做法,得到一个条件模式库{(f:2,c:2,a:3),(f:1,c:1,b:1,m:1)}.我们在上面构建条件树,得到<f:3,c:3,a:3>,这是一棵只有一条路径的FP-tree.我们对这个路径中的所有节点的组合产生频繁集,得到{(am:3),(cm:3),(acm:3),(fm:3),(fam:3),(fcm:3),(fcam:3)}.
类似的,表项b 产生一个频繁集(b:3)和3条路径<f:4,c:3,a:3,b:1>,<f:4,b:1>,和<c:1,b:1>.同样的,这里也不在考虑项p 和项m 了.b 的条件模式库{(f:1,c:1,a:1),(f:1,b:1),(c:1,b:1)},在这个条件模式库上面运用算法一,得到一棵空树,对含有b 的频繁集的挖掘到此为止.
表2
(f:2,c:2,a:2)
(f:1.c:1,a:1.b:1)
‘m’的条件模式库
头表
算法一产生的FP-tree
图2
数据结构
对应于算法描述中所出现的各个对象分别定义了一些数据结构.下面予以分别介绍.(有关代码的清单只列出重要部分,如类中的普通的构造函数之类就不予详细描述了.) 1.Item
class Item{
public:
CString name;
int count;
LPVOID lpvoid;
Item();
Item(const Item& item);
Item(CString strName,int icount);
~Item();
Item& operator=(const Item& right);
bool operator==(const Item& right);
bool operator<(const Item& right);
};
Item类对应于算法中的项.为了操作方便其中的成员变量和成员函数都定义为public类型.
成员变量name用来存放项的名字.
count则是每个项的记数值,在最后的输出之中,它的意义就变成了支持度.
还有一个变量lpvoid,是没有在算法描述中出现过的,用来在使用过程中指向一个新分配的Table_Iter对象.因为Table_Iter类还没有声明,所以只好用LPVOID类型的指针.对它的释放在析构函数~Item()中.~Item()又调用DeleteV oid(LPVOID),这个函数在class Item声明之前声明,但又在class Table_Iter定义之后定义,因此可以用它来删除这个对象.
三个构造函数Item(),Item(const Item& item)和Item(CString strName,int icount)在实例化对象的时候起到不同的作用.其中第二个是一个赋值构造函数,因为在后面定义的一些类中需要使用class Item做为类型参数,根据约定,需要class Item实现一个赋值构造函数.最后三个成员函数重载了两个运算符’==’和’<’,是因为在后面的函数中我们需要对频繁的项集进行查找,插入,所以要对比较作出规定.
2.Trans
class Trans:public std::vector<Item>
{
public:
int TID;
Trans(){TID=0;};
Trans(int t){
TID=t;
}
bool operator==(const Trans& right);
bool operator<(const Trans& right);
}
typedef Trans::iterator Trans_Iter;
Trans类对应于交易对象(Transaction).对它的定义利用了STL中的vector模板,把它定义为一个以Item为元素的向量.把它定义为向量,是因为后来需要对其中的元素进行排序,向量可以使用快速排序.
这个Trans 类身肩两任,它不仅代表交易对象,还在最后的结果输出中还被用来存放频繁集.
Trans_Iter是Trans类的遍历子.在以后使用STL容器模板定义的类都会有一个与之相对应的遍历子,不再赘述.
3.DB
typedef std::list<Trans> DB;
typedef DB::iterator DB_Iter;
DB对应于数据库对象,它是一个存在于内存中的”数据库”.它的定义借助于STL的list模板就非常的简明.之所以使用list模板而不是vector是因为对一个数据库的操作我们通常只需要对其进行遍历,不需要查找,排序等操作,因此list最合适,效率因此也最高。
4.FreqSet
typedef std::map<CString,Item> FreqSet;
typedef FreqSet::iterator FreqSet_Iter;
FreqSet跟我们要得到的频繁集并不是一回事,它只是一个中间的数据结构.我们在扫描数据库的时候需要计算每个项的记数值,因此需要建立一个项的集合类,其中有所有的项,每次在数据库中扫描到一个项就增加其中相应的项的记数值.它相当于算法描述中的频繁项集合.
为了使得查找相应项的速度加快,用map模板来定义FreqSet.
STL在实现map的时候,实际上在其内部维持了一个平衡树, 因此其插入,查找,删除的速度是O(ln).map模板使用第一个类型参数作为比较的键值类型,第二个类型参数做为节点内部值.在这里,键值类型为CString,节点内部值类型为Item.我们就利用Item的成员变量name 为键值.就可以根据Item的名字来寻找它在FreqSet中的位置,在O(ln)时间内完成对相应项的操作.
使用map模板的另外一个原因参见后面的算法实现细节部分对函数(f5)的说明.
5.NodeVector和Node
class Node;
typedef std::vector<Node*> NodeVector;
typedef NodeVector::iterator NVector_Iter;
class Node{
public:
CString name;
int count;
Node* node_link;
NodeVector* pChildren;
Node* pParent;
Node(){
pChildren=(NodeV ector*) new NodeVector;
node_link=NULL;
pParent=NULL;
count=0;
name="";
}
~Node(){
if(pChildren)delete pChildren;//在析构函数中删除
}
};
class Node 是建立FP-tree的关键数据结构,节点。
其中pChildren指针指向子节点,由于一个节点的子节点的数目是不定的,所以用了一个NodeVector类来管理子节点.因为我们要在Node类的构造函数中给pChildren分配空间,所以需要在定义Node之前定义NodeVector.
NodeVector是一个以Node*为元素的向量类.所以我们在定义它之前声明了Node类.虽然我们在后面的算法中需要根据名字在pChildren中查找子节点,我们并没有使用hash表或者平衡树这样复杂但高效的数据结构,是因为子节点的数目一般都不是很多,使用复杂的这样复杂的数据结构对于少量的数据来说时间并没有缩短多少,空间却要浪费一些.因此使用了一个向量类,查找的时候使用简单的遍历算法.
成员变量name和count的意义很清楚,它们分别是节点的名字和计数值.
pParent指向父节点,定义这样一个变量的作用在函数DB* Generate2(Node* node,int& retcount)中可以看到.
node_link也是一个Node*类型的变量,它用来指向下一个同名的节点,其作用在函数Node* SetupFP(FreqSet* pSet,Table* pTable,DB* pDB)中可以看到.
6.Entry
class Entry{
public:
CString name;
int count;
Node* head_link;
Entry();
Entry(const Entry& entry);
Entry(const Item& item);
};
Entry类对应于头表中的表项.因此它有一个name成员,所指项的名字,还有一个Node*类,head_link,用来串起同名的项.
成员变量count是为了优化算法而添加的,它的作用可以在下面Table的说明看到.
7.Table。