FP_growth算法是韩家炜老师在2000年提出的关联分析算法,该算法和Apriori算法最大的不同有两点:

第一,不产生候选集,第二,只需要两次遍历数据库,大大提高了效率,用31646条测试记录,最小支持度是2%,

用Apriori算法要半个小时但是用FP_growth算法只要6分钟就可以了,效率非常明显。

它的核心是FP_tree,一种树型数据结构,特点是尽量把相同元素用一个节点表示,这样就大大减少了空间,和birch算法有类似的思想。还是以如下数据为例。


每一行表示一条交易,共有9行,既9笔交易,左边表示交易ID,右边表示商品名称。最小支持度是22%,那么每件商品至少要出现9*22%=2次才算频繁。第一次扫描数据库,统计每件商品出现的次数,按次数对各个商品递减排序,有:。然后第二次扫描数据库,在每条交易中按此种顺序给商品排序,如果有某个商品出现的次数小于阈值2,则删除该商品,有:


剩下的就是构造FP_tree了,这是核心,树的每个节点的结构体如下:

//FP-tree的存储结构
typedef struct CSNode{
 //商品编号
 int item;
 //次数
 int count;
 //父节点,孩子节点,兄弟节点
 CSNode *parent,*firstchild,*nextsibling;
 //相同商品的前驱,后继节点,方便将相同商品的节点连接起来,根节点的直接孩子节点的这两个指针都是空
 CSNode *pre,*next;
}*CSTree;
其中item,*firstchild,*nextsibling是树这个结构体常用的属性。count记录商品item出现的次数,*parent是为了方便从叶子节点逆向访问根节点而设置的。*pre,*next的注释已经很清楚了。构造树的原则是:将每条记录看做一个从根节点到叶子节点的路径,如果某个商品在节点中已经存在了,则对应count计数器加1,相当于所有的前缀都要加1,如果不存在则在该条记录的后面商品开辟一条新的路径。下面一条一条记录演示怎么构造FP_tree。

第三次访问数据库,构造FP_tree。第一条记录:I2,I1,I5,有:


父节点没有表示出来,根节点是空节点。2:1表示商品2出现了1次,其他表示类推。左边的数组按照商品顺序递减排列,保存了各个商品的当前指针,目的是为了在后面找到相同的后缀,将相同的商品用单项箭头虚线连起来,实际是双向链表链接的,并且将此时的节点商品1和节点商品5保存为商品1和商品5的当前指针,而对于商品2,商品3,商品4的当前指针还在左边的数组中保存。注意根节点的直接孩子不用连起来,后面会讲理由。第二条记录:I2,I4,有:


该记录和第一条记录共用前缀I2,所以商品2的次数要加1,而商品4则作为商品2的一个新孩子节点,这里没有把兄弟节点画出来。并且左边商品4要指向该节点,此时商品4的当前指针指向节点商品4。第三条记录:I2,I3,类似,结果是:


第四条记录:I2,I1,I4有:


当添加完商品4后,商品4的当前指针要指向新的节点商品4,此时两条红色的虚线就把以商品4为后缀的节点连起来了。第5条记录:I1,I3,有:


商品1由于和根节点的所有直接子孩子(这里只有商品2这个子孩子)不同,因此要另外开辟一条路径。商品3的当前指针要指向新的节点商品3,如图中的黄色虚线所指,到这里体现了构造FP_tree的一般性了。再把剩下的记录都加进来,最终的FP_tree是:


这颗FP_tree最大程度的把相同的商品放在用同一个节点保存,最大限度的节省了空间。剩下的工作就是挖掘这颗FP_tree了。

挖掘的目的是找出FP_tree的各个路径中相同的集合,有两中方式,方式一,从根节点朝叶子节点顺着遍历树,方式二,从叶子节点朝根节点逆着遍历树。想想方式一挺麻烦的,幸亏我们设置了*parent指针,通过它就可以很方便的用方式二。我们从商品出现次数由少到多的顺序开始遍历树,先从商品5开始,由于有*pre,*next指针分方便将所有以商品5做为叶子节点的路径全找出来,然后再根据*parent指针找到父节点,根节点是空不用找。以I5做元素的条件模式基是:{(I2 I1:1),(I2 I1 I3:1)}。后面的1表示出现商品I2,I1,I5同时出现的次数。现在解释为什么:根节点的直接孩子不用*pre,*next指针连起来,因为假如连起来的话,那么以它为后缀时,将没有前缀,也就是说它的频繁项集是1,这在大多数情况下没意义。由它构造出条件FP_tree,注意由于开始按照商品名称排序了,那么条件模式基中的每一项也会按照这种方式排序。如果条件模式基中某项A是另外一项B的子集那么在算B时,要将A出现的次数加上,实现这个功能最简单明了的方法就是一一匹配,假如条件模式基共有N项,则时间复杂度是N的平方,若先按照条件模式基的长度递增排序得到:{(I2 I1:1),(I2 I1 I3:1)},排序的时间复杂度是N*log(N),那么只有可能是长度短的项是长度长的项的子集,此时总匹配次数是:N-1 + N-2 + ,,, + 1 = N*(N-1)/2,和前面的排序时间加起来是:N*log(N) + N*(N-1)/2当N大于时4时,该值小于N的平方。在实际中N一般会大于4。最终我们得到以I5作为后缀的频繁项集是:{I2 I5:2},{I1 I5:2},{I2 I1 I5:2}他们出现的次数都大于等于最小支持度。类似可以得到其它后缀的频繁项集。

FP_growth算法不产生候选序列,并且只需要3次遍历数据库,对比Apriori算法而言有了很大的改进。其实想想这也符合历史发展的规律,Apriori在1993年才提出来的,那是数据挖掘才刚起步,而到2000年时,已经有了一定的发展,FP_growth是站在Apriori的肩膀上发明的,这种现象具有普遍性。

FP—growth代码实现部分

主程序部分

  1. package DataMining_FPTree;
  2. /**
  3. * FPTree频繁模式树算法
  4. * 一个使用的这个算法的用例是输入一个单词或者单词的一部分,搜索引擎就会自动 补全查询词项,通过查看互联网上的用词来找出经常在一块出现的词对(使用Aporior算法也是找出经常出现的词对,这两种方法都是无监督学习),这需要一种发现频繁集的方法
  5. * @author clj
  6. *
  7. */
  8. public class Client {
  9. public static void main(String[] args){
  10. //这里使用的是输入文件的绝对路径
  11. String filePath="E:\\code\\data mining\\DataMining_FPTree\\src\\DataMining_FPTree\\testInput.txt";
  12. //最小支持度阈值
  13. int minSupportCount = 2;
  14. //构造函数
  15. FPTreeTool tool = new FPTreeTool(filePath, minSupportCount);
  16. //调用构建树
  17. tool.startBuildingTree();
  18. }
  19. }

树节点的数据结构

  1. package DataMining_FPTree;
  2.  
  3. import java.util.ArrayList;
  4.  
  5. /**
  6. * FP树节点
  7. * 这里使用Comparable的原因是因为每个项集要进行排序
  8. * 按照节点的count来排序
  9. * @author clj
  10. *
  11. */
  12. public class TreeNode implements Comparable<TreeNode>, Cloneable{
  13. // 节点类别名称
  14. private String name;
  15. // 计数数量
  16. private Integer count;
  17. // 父亲节点,这个节点的用法是根据给定叶子节点上溯到整棵树,这时就需要指向父节点
  18. private TreeNode parentNode;
  19. // 孩子节点,可以为多个
  20. private ArrayList<TreeNode> childNodes;
  21.  
  22. public TreeNode(String name, int count){
  23. this.name = name;
  24. this.count = count;
  25. }
  26.  
  27. public String getName() {
  28. return name;
  29. }
  30.  
  31. public void setName(String name) {
  32. this.name = name;
  33. }
  34.  
  35. public Integer getCount() {
  36. return count;
  37. }
  38.  
  39. public void setCount(Integer count) {
  40. this.count = count;
  41. }
  42.  
  43. public TreeNode getParentNode() {
  44. return parentNode;
  45. }
  46.  
  47. public void setParentNode(TreeNode parentNode) {
  48. this.parentNode = parentNode;
  49. }
  50.  
  51. public ArrayList<TreeNode> getChildNodes() {//孩子节点可能不止一个,所以需要用list来保存
  52. return childNodes;
  53. }
  54.  
  55. public void setChildNodes(ArrayList<TreeNode> childNodes) {
  56. this.childNodes = childNodes;
  57. }
  58.  
  59. @Override
  60. public int compareTo(TreeNode o) {
  61. // TODO Auto-generated method stub
  62. return o.getCount().compareTo(this.getCount());
  63. }
  64.  
  65. @Override
  66. protected Object clone() throws CloneNotSupportedException {//如果想重写父类的方法,比如toString()方法的话,在方法前面加上@Override 系统可以帮你检查方法的正确性,
  67. // TODO Auto-generated method stub
  68. //因为对象内部有引用,需要采用深拷贝,这里就相当于是一个深度优先搜索,这里的clone相当于没有用
  69. //System.out.println("The name="+this.getName());
  70.  
  71. TreeNode node = (TreeNode)super.clone();
  72. if(this.getParentNode() != null){
  73. node.setParentNode((TreeNode) this.getParentNode().clone());
  74. }
  75.  
  76. if(this.getChildNodes() != null){
  77. node.setChildNodes((ArrayList<TreeNode>) this.getChildNodes().clone());
  78. }
  79.  
  80. return node;
  81. }
  82.  
  83. }

程序的主要部分FPTreeTool

  1. package DataMining_FPTree;
  2.  
  3. import java.io.BufferedReader;
  4. import java.io.File;
  5. import java.io.FileReader;
  6. import java.io.IOException;
  7. import java.util.ArrayList;
  8. import java.util.Collections;
  9. import java.util.HashMap;
  10. import java.util.Iterator;
  11. import java.util.Map;
  12. import java.util.Map.Entry;
  13.  
  14. /**
  15. * FPTree算法工具类
  16. * 与Apriori算法不同的是FP树需要将非频繁项移除并且重排序
  17. * @author clj
  18. *
  19. */
  20. public class FPTreeTool {
  21. // 输入数据文件位置
  22. private String filePath;
  23. // 最小支持度阈值
  24. private int minSupportCount;
  25. // 所有事物ID记录
  26. private ArrayList<String[]> totalGoodsID;
  27. // 各个ID的统计数目映射表项,计数用于排序使用,用于项集
  28. private HashMap<String, Integer> itemCountMap;
  29. //后面的成员方法中并没有重新定义成员变量,所以成员函数中可以改变的成员变量的值
  30.  
  31. public FPTreeTool(String filePath, int minSupportCount) {
  32. this.filePath = filePath;
  33. this.minSupportCount = minSupportCount;
  34. readDataFile();
  35. }
  36.  
  37. /**
  38. * 从文件中读取数据,至此还没有对数据进行排序
  39. */
  40. private void readDataFile() {
  41. File file = new File(filePath);
  42. ArrayList<String[]> dataArray = new ArrayList<String[]>();
  43.  
  44. try {
  45. BufferedReader in = new BufferedReader(new FileReader(file));//这一句话相当于新建了两个对象
  46. String str;
  47. String[] tempArray;
  48. while ((str = in.readLine()) != null) {
  49. tempArray = str.split(" ");
  50. dataArray.add(tempArray);
  51. }
  52. in.close();
  53. } catch (IOException e) {
  54. e.getStackTrace();
  55. }
  56.  
  57. String[] temp;
  58. int count = 0;
  59. itemCountMap = new HashMap<>();//之所以使用会使用hashMap的形式是因为后面会更改key所对应的value的值,时间复杂度小
  60. totalGoodsID = new ArrayList<>();//totalGoodsId只需要将其保存在矩阵中
  61. for (String[] a : dataArray) {
  62. temp = new String[a.length - 1];
  63. System.arraycopy(a, 1, temp, 0, a.length - 1);//和Apriori算法一样第一个保存的是第几笔记录
  64. totalGoodsID.add(temp);
  65. for (String s : temp) {
  66. if (!itemCountMap.containsKey(s)) {
  67. count = 1;
  68. } else {
  69. count = ((int) itemCountMap.get(s));
  70. // 支持度计数加1
  71. count++;
  72. }
  73. // 更新表项,如果有key s,则直接更新,否则创建
  74. itemCountMap.put(s, count);
  75. }
  76. }
  77. System.out.println("name="+itemCountMap.keySet()+" count="+itemCountMap.values());
  78.  
  79. }
  80.  
  81. /**
  82. * 根据事务记录构造FP树
  83. * 当suffixPattern不为空的时候,建立的就是条件FP树
  84. * surffixPatter是后缀模式
  85. */
  86. private void buildFPTree(ArrayList<String> suffixPattern,
  87. ArrayList<ArrayList<TreeNode>> transctionList) {
  88.  
  89. // 设置一个空根节点
  90. TreeNode rootNode = new TreeNode(null, 0);
  91. int count = 0;
  92. // 节点是否存在
  93. boolean isExist = false;
  94. ArrayList<TreeNode> childNodes;
  95. ArrayList<TreeNode> pathList;
  96. // 相同类型节点链表,用于构造的新的FP树
  97. HashMap<String, ArrayList<TreeNode>> linkedNode = new HashMap<>();//每个节点的LinkNode
  98. HashMap<String, Integer> countNode = new HashMap<>();
  99. // 根据事务记录,一步步构建FP树,逐个读入事务记录,并把每个事务映射到FP树中的一条路径中
  100. for (ArrayList<TreeNode> array : transctionList) {
  101. TreeNode searchedNode;//TreeNode节点中每个项集中应该是只有一个元素
  102. pathList = new ArrayList<>();//在构建的时候,将读入的每个项集添加到一条已经存在的路径中
  103. /*
  104. System.out.print("array=");
  105. for(int i=0;i<array.size();i++)
  106. System.out.print("\t"+array.get(i).getName());
  107. System.out.println();
  108. */
  109. for (TreeNode node : array) {//array保存的是FP中的一条路径
  110. pathList.add(node);//pathList开始为空,在事务中读到一个节点就把它放到pathList中
  111. //System.out.println("正在处理的节点node="+node.getName()+" count="+node.getCount());
  112. //System.out.println("before keySets="+countNode.keySet()+"count="+countNode.values());
  113. nodeCounted(node, countNode);//countNode是一个HashMap类型,初始时为一个空的HashMap,在读事务过程中依次进行修改
  114. //System.out.println("after keySets="+countNode.keySet()+"count="+countNode.values());
  115. /*System.out.print("pathList=");
  116. for(int i=0;i<pathList.size();i++)
  117. System.out.print("\t"+pathList.get(i).getName());
  118. System.out.println();
  119. */
  120. searchedNode = searchNode(rootNode, pathList);//这里只是查找,不会影响count的变化
  121. childNodes = searchedNode.getChildNodes();
  122.  
  123. if (childNodes == null) {//如果正好找到路径中的结尾,则直接加入到结尾
  124. //System.out.println("找到了对应的叶节点,在叶节点下存储");
  125. childNodes = new ArrayList<>();
  126. childNodes.add(node);
  127. searchedNode.setChildNodes(childNodes);
  128. node.setParentNode(searchedNode);
  129. nodeAddToLinkedList(node, linkedNode);
  130. } else {
  131. isExist = false;
  132. for (TreeNode node2 : childNodes) {
  133. // 如果找到名称相同,则更新支持度计数
  134. //System.out.println("##############");
  135. if (node.getName().equals(node2.getName())) {
  136. //System.out.println("在父节点下找到了对应的节点");
  137. count = node2.getCount() + node.getCount();
  138. node2.setCount(count);
  139. // 标识已找到节点位置
  140. isExist = true;
  141. break;
  142. }
  143. }
  144.  
  145. if (!isExist) {
  146. // 如果没有找到,需添加子节点
  147. //System.out.println("&没有在父节点下找到了对应的节点");
  148. childNodes.add(node);
  149. node.setParentNode(searchedNode);
  150. nodeAddToLinkedList(node, linkedNode);
  151. }
  152. }
  153. //System.out.println("countNode.key="+countNode.keySet()+" value="+countNode.values());
  154.  
  155. /*Iterator<Entry<String, ArrayList<TreeNode>>> it = linkedNode.entrySet().iterator();
  156. while( it.hasNext())
  157. {
  158. Map.Entry<String, ArrayList<TreeNode>> entry = it.next();
  159. String key = entry.getKey();
  160. ArrayList<TreeNode> values=(ArrayList<TreeNode>)entry.getValue();
  161. for(TreeNode value:values)
  162. {
  163. System.out.print(" linkedNode.name="+value.getName()+"\tLinkedNode.count="+value.getCount());
  164. }
  165. System.out.println();
  166. //TreeNode tempNode= entry.getValue().get(i);
  167. }*/
  168.  
  169. }
  170. }
  171.  
  172. // 如果FP树已经是单条路径,则输出此时的频繁模式
  173. if(suffixPattern!=null)
  174. {
  175. System.out.println("suffixPattern.size="+suffixPattern.size());
  176. for(int i=0;i<suffixPattern.size();i++)
  177. System.out.print(suffixPattern.get(i)+"\t ");
  178. System.out.println();
  179. }
  180. else
  181. System.out.println("suffixPattern.size=0");
  182. if (isSinglePath(rootNode)) {
  183. System.out.println("issinglePath-------");
  184. printFrequentPattern(suffixPattern, rootNode);
  185.  
  186. } else {
  187. ArrayList<ArrayList<TreeNode>> tList;
  188. ArrayList<String> sPattern;
  189. if (suffixPattern == null) {
  190. sPattern = new ArrayList<>();
  191. } else {
  192. // 进行一个拷贝,避免互相引用的影响
  193. sPattern = (ArrayList<String>) suffixPattern.clone();
  194. }
  195.  
  196. // 利用节点链表构造新的事务
  197. for (Map.Entry entry : countNode.entrySet()) {
  198. // 添加到后缀模式中
  199. sPattern.add((String) entry.getKey());
  200. System.out.println("entry.key="+entry.getKey()+"\tentry.value="+entry.getValue());
  201.  
  202. //获取到了条件模式机,作为新的事务
  203. tList = getTransactionList((String) entry.getKey(), linkedNode);
  204.  
  205. System.out.print("[后缀模式]:{");
  206. for(String s: sPattern){
  207. System.out.print(s + ", ");
  208. }
  209. System.out.print("}, 此时的条件模式基:");
  210. for(ArrayList<TreeNode> tnList: tList){
  211. System.out.print("{");
  212. for(TreeNode n: tnList){
  213. System.out.print(n.getName() + ", ");
  214. }
  215. System.out.print("}, ");
  216. }
  217. System.out.println();
  218. // 递归构造FP树
  219. buildFPTree(sPattern, tList);
  220. // 再次移除此项,构造不同的后缀模式,防止对后面造成干扰
  221. sPattern.remove((String) entry.getKey());
  222. }
  223. }
  224. }
  225.  
  226. /**
  227. * 将节点加入到同类型节点的链表中
  228. *
  229. * @param node
  230. * 待加入节点
  231. * @param linkedList
  232. * 链表图
  233. */
  234. private void nodeAddToLinkedList(TreeNode node,
  235. HashMap<String, ArrayList<TreeNode>> linkedList) {
  236. String name = node.getName();
  237. ArrayList<TreeNode> list;
  238.  
  239. if (linkedList.containsKey(name)) {
  240. list = linkedList.get(name);
  241. // 将node添加到此队列末尾
  242. list.add(node);
  243. } else {
  244. list = new ArrayList<>();
  245. list.add(node);
  246. linkedList.put(name, list);
  247. }
  248. }
  249.  
  250. /**
  251. * 根据链表构造出新的事务,根据name,得到以name为尾的各记录
  252. *
  253. * @param name
  254. * 节点名称
  255. * @param linkedList
  256. * 链表
  257. * @return
  258. */
  259. private ArrayList<ArrayList<TreeNode>> getTransactionList(String name,
  260. HashMap<String, ArrayList<TreeNode>> linkedList) {
  261. ArrayList<ArrayList<TreeNode>> tList = new ArrayList<>();
  262. ArrayList<TreeNode> targetNode = linkedList.get(name);
  263. ArrayList<TreeNode> singleTansaction;
  264. TreeNode temp;
  265. System.out.println("#getTransaction中name="+name);
  266. for (TreeNode node : targetNode) {
  267. singleTansaction = new ArrayList<>();
  268.  
  269. temp = node;
  270. while (temp.getParentNode().getName() != null) {
  271. System.out.println("temp.name="+temp.getName()+"\tcount="+temp.getCount());
  272. temp = temp.getParentNode();
  273.  
  274. singleTansaction.add(new TreeNode(temp.getName(), 1));
  275. }
  276. System.out.println("temp.name="+temp.getName()+"\tcount="+temp.getCount());
  277. System.out.println("singleTansaction=");
  278. for(int i=0;i<singleTansaction.size();i++)
  279. {
  280. System.out.println("("+singleTansaction.get(i).getName()+","+singleTansaction.get(i).getCount()+")");
  281. }
  282. System.out.println();
  283. // 按照支持度计数得反转一下
  284. Collections.reverse(singleTansaction);
  285.  
  286. for (TreeNode node2 : singleTansaction) {
  287. // 支持度计数调成与模式后缀一样
  288. node2.setCount(node.getCount());
  289. }
  290. System.out.println("##singleTansaction=");
  291. for(int i=0;i<singleTansaction.size();i++)
  292. {
  293. System.out.println("("+singleTansaction.get(i).getName()+","+singleTansaction.get(i).getCount()+")");
  294. }
  295. System.out.println();
  296.  
  297. if (singleTansaction.size() > 0) {
  298. tList.add(singleTansaction);
  299. }
  300. }
  301.  
  302. return tList;
  303. }
  304.  
  305. /**
  306. * 节点计数
  307. *
  308. * @param node
  309. * 待加入节点
  310. * @param nodeCount
  311. * 计数映射图
  312. */
  313. private void nodeCounted(TreeNode node, HashMap<String, Integer> nodeCount) {
  314. int count = 0;
  315. String name = node.getName();
  316.  
  317. if (nodeCount.containsKey(name)) {
  318. count = nodeCount.get(name);
  319. count++;
  320. } else {
  321. count = 1;
  322. }
  323.  
  324. nodeCount.put(name, count);
  325. }
  326.  
  327. /**
  328. * 显示决策树
  329. *
  330. * @param node
  331. * 待显示的节点
  332. * @param blankNum
  333. * 行空格符,用于显示树型结构
  334. */
  335. private void showFPTree(TreeNode node, int blankNum) {
  336. System.out.println("¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥显示FPTree");
  337. for (int i = 0; i < blankNum; i++) {
  338. System.out.print("\t");
  339. }
  340. System.out.print("--");
  341. System.out.print("--");
  342.  
  343. if (node.getChildNodes() == null) {//叶子节点
  344. System.out.print("[");
  345. System.out.print("I" + node.getName() + ":" + node.getCount());
  346. System.out.print("]");
  347. } else {
  348. // 递归显示子节点
  349. System.out.print("【" + node.getName() + "】");
  350. for (TreeNode childNode : node.getChildNodes()) {
  351. showFPTree(childNode, 2 * blankNum);
  352. }
  353. }
  354.  
  355. }
  356.  
  357. /**
  358. * 待插入节点的抵达位置节点,从根节点开始向下寻找待插入节点的位置,返回待插入节点的父节点
  359. *
  360. * @param root
  361. * @param list
  362. * @return
  363. */
  364. private TreeNode searchNode(TreeNode node, ArrayList<TreeNode> list) {
  365. ArrayList<TreeNode> pathList = new ArrayList<>();
  366. TreeNode tempNode = null;
  367. TreeNode firstNode = list.get(0);
  368. boolean isExist = false;
  369. // 重新转一遍,避免出现同一引用
  370. for (TreeNode node2 : list) {
  371. pathList.add(node2);
  372. }
  373. //System.out.println("待插入的节点:name="+node.getName()+" count="+node.getCount());
  374. /*for(int i=0;i<list.size();i++)
  375. System.out.print("\t("+list.get(i).getName()+","+list.get(i).getCount()+")");
  376. System.out.println();*/
  377. // 如果没有孩子节点,则直接返回,在此节点下添加子节点,查找已构建树中的叶子节点
  378. if (node.getChildNodes() == null) {
  379. //System.out.println("此节点为叶子节点,为返回的节点,node.name="+node.getName()+" count="+node.getCount());
  380. return node;
  381. }
  382.  
  383. for (TreeNode n : node.getChildNodes()) {
  384. if (n.getName().equals(firstNode.getName()) && list.size() == 1) {//list中只有一个元素,即路径中的第一个元素
  385. tempNode = node;
  386. isExist = true;
  387. //System.out.println("第一个元素恰好为要查找的节点,且节点长度为1");
  388. break;
  389. } else if (n.getName().equals(firstNode.getName())) {
  390. // 还没有找到最后的位置,继续找,在查找的过程中时是正好匹配,从路径中消除
  391. //System.out.println("#第一个元素恰好为要查找的节点,且节点长度不为1");
  392. pathList.remove(firstNode);
  393. tempNode = searchNode(n, pathList);//使用递归的形式去查询子节点
  394. //System.out.println("¥¥¥返回节点:tempNode.name="+tempNode.getName()+" count="+tempNode.getCount());
  395. return tempNode;
  396. }
  397. }
  398.  
  399. // 如果没有找到,则新添加到孩子节点中
  400. if (!isExist) {
  401. //System.out.println("没有找到");
  402. tempNode = node;
  403. }
  404. //System.out.println("@@@@返回节点:node.name="+tempNode.getName()+" count="+tempNode.getCount());
  405. return tempNode;
  406. }
  407.  
  408. /**
  409. * 判断目前构造的FP树是否是单条路径的
  410. *
  411. * @param rootNode
  412. * 当前FP树的根节点
  413. * @return
  414. */
  415. private boolean isSinglePath(TreeNode rootNode) {
  416. // 默认是单条路径
  417. boolean isSinglePath = true;
  418. ArrayList<TreeNode> childList;
  419. TreeNode node;
  420. node = rootNode;
  421. //是使用循环而不是递归判断是否是单条路径
  422. while (node.getChildNodes() != null) {
  423. childList = node.getChildNodes();
  424. if (childList.size() == 1) {
  425. node = childList.get(0);
  426. } else {
  427. isSinglePath = false;
  428. break;
  429. }
  430. }
  431.  
  432. return isSinglePath;
  433. }
  434.  
  435. /**
  436. * 开始构建FP树
  437. */
  438. public void startBuildingTree() {
  439. ArrayList<TreeNode> singleTransaction;//单条事务
  440. ArrayList<ArrayList<TreeNode>> transactionList = new ArrayList<>();//事务总链
  441. TreeNode tempNode;
  442. int count = 0;
  443.  
  444. for (String[] idArray : totalGoodsID) {
  445. singleTransaction = new ArrayList<>();
  446. for (String id : idArray) {
  447. count = itemCountMap.get(id);
  448. tempNode = new TreeNode(id, count);
  449. singleTransaction.add(tempNode);
  450. }
  451.  
  452. // 根据支持度数的多少进行排序
  453. Collections.sort(singleTransaction);
  454.  
  455. /*System.out.println("singleTansaction as following:");
  456. for(int i=0;i<singleTransaction.size();i++)
  457. System.out.print("("+singleTransaction.get(i).getName()+","+singleTransaction.get(i).getCount()+")");
  458. System.out.println();*/
  459.  
  460. for (TreeNode node : singleTransaction) {
  461. // 支持度计数重新归为1,将事务路径节点的count设置为1
  462. node.setCount(1);
  463. }
  464. /*System.out.println("singleTansaction");
  465. for(int i=0;i<singleTransaction.size();i++)
  466. System.out.print("***("+singleTransaction.get(i).getName()+","+singleTransaction.get(i).getCount()+")");
  467. System.out.println();*/
  468. transactionList.add(singleTransaction);
  469. }
  470. for(int i=0;i<transactionList.size();i++)
  471. {
  472. ArrayList<TreeNode> singleTransaction1=new ArrayList<>();
  473. singleTransaction1=transactionList.get(i);
  474. for(int j=0;j<singleTransaction1.size();j++)
  475. {
  476. System.out.print("("+singleTransaction1.get(j).getName()+","+singleTransaction1.get(j).getCount()+")");
  477. }
  478. System.out.println();
  479.  
  480. }
  481. buildFPTree(null, transactionList);
  482. }
  483.  
  484. /**
  485. * 输出此单条路径下的频繁模式
  486. *
  487. * @param suffixPattern
  488. * 后缀模式
  489. * @param rootNode
  490. * 单条路径FP树根节点
  491. */
  492. private void printFrequentPattern(ArrayList<String> suffixPattern,
  493. TreeNode rootNode) {
  494. ArrayList<String> idArray = new ArrayList<>();
  495. TreeNode temp;
  496. temp = rootNode;
  497. // 用于输出组合模式
  498. int length = 0;
  499. int num = 0;
  500. int[] binaryArray;
  501.  
  502. while (temp.getChildNodes() != null) {
  503. temp = temp.getChildNodes().get(0);
  504.  
  505. // 筛选支持度系数大于最小阈值的值,P(A)>P(AB),若P(A)<阈值,则删除这个节点即不添加到里面
  506. if (temp.getCount() >= minSupportCount) {
  507. idArray.add(temp.getName());
  508. }
  509. }
  510.  
  511. length = idArray.size();
  512. num = (int) Math.pow(2, length);
  513. for (int i = 0; i < num; i++) {
  514. binaryArray = new int[length];
  515. numToBinaryArray(binaryArray, i);
  516.  
  517. // 如果后缀模式只有1个,不能输出自身
  518. if (suffixPattern.size() == 1 && i == 0) {
  519. continue;
  520. }
  521.  
  522. System.out.print("频繁模式:{【后缀模式:");
  523. // 先输出固有的后缀模式
  524. if (suffixPattern.size() > 1
  525. || (suffixPattern.size() == 1 && idArray.size() > 0)) {
  526. for (String s : suffixPattern) {
  527. System.out.print(s + ", ");
  528. }
  529. }
  530. System.out.print("】");
  531. // 输出路径上的组合模式
  532. for (int j = 0; j < length; j++) {
  533. if (binaryArray[j] == 1) {
  534. System.out.print(idArray.get(j) + ", ");
  535. }
  536. }
  537. System.out.println("}");
  538. }
  539. }
  540.  
  541. /**
  542. * 数字转为二进制形式
  543. *
  544. * @param binaryArray
  545. * 转化后的二进制数组形式
  546. * @param num
  547. * 待转化数字
  548. */
  549. private void numToBinaryArray(int[] binaryArray, int num) {
  550. int index = 0;
  551. while (num != 0) {
  552. binaryArray[index] = num % 2;
  553. index++;
  554. num /= 2;
  555. }
  556. }
  557.  
  558. }

readDataFile从文件中读取数据,

buildFPTree(ArrayList<String> suffixPattern,ArrayList<ArrayList<TreeNode>> transctionList)构建FP树(包括FP条件树),当suffixpatter不为空的时候构建的就是FP条件树,

nodeAddToLinkedList(TreeNode node,HashMap<String, ArrayList<TreeNode>> linkedList),和邻接表类似,某个Node在树中出现的位置保存在linkedList中

private ArrayList<ArrayList<TreeNode>> getTransactionList(String name,HashMap<String, ArrayList<TreeNode>> linkedList)得到还有name节点的交易记录

nodeCounted(TreeNode node, HashMap<String, Integer> nodeCount)因为最后交易记录是以节点的计数多少进行排序的,这一个记录node在所有记录中出现的次数,同一条事务其实是没有先后顺序的,为了把树尽可能的减小才这样进行排序的

showFPTree(TreeNode node, int blankNum) 展示树

private TreeNode searchNode(TreeNode node, ArrayList<TreeNode> list) 要插入的节点在树中应该插入到哪个节点的下面呢,这里返回的是待插入节点的父节点

printFrequentPattern(ArrayList<String> suffixPattern,TreeNode rootNode)输出单条路径下的频繁模式,

常见的频繁项集挖掘算法有两类,一类是Apriori算法,另一类是FPGrowth。Apriori通过不断的构造候选集、筛选候选集挖掘出频繁项集,需要多次扫描原始数据,当原始数据较大时,磁盘I/O次数太多,效率比较低下。FPGrowth算法则只需扫描原始数据两遍,通过FP-tree数据结构对原始数据进行压缩,效率较高。

也许有人会问?如果这个数据库足够大,以至于构造的FP树大到无法完全保存在内存中,这该如何是好.这的确是个问题. Han Jiawei在论文中也给出了一种思路,就是通过将原来的大的数据库分区成几个小的数据库(这种小的数据库称之为投射数据库),对这几个小的数据库分别进行FP Growth算法.
还是拿上面的例子来说事,我们把包含p的所有数据库记录都单独存成一个数据库,我们称之为p-投射数据库,类似的m,b,a,c,f我们都可以生成相应的投射数据库,这些投射数据库构成的FP树相对而言大小就小得多,完全可以放在内存里.
在现代数据挖掘任务中,数据量越来越大,因此并行化的需求越来越大,上面提出的问题也越来越迫切.下一篇博客,博主将分析一下,FP Growth如何在MapReduce的框架下并行化.
[1]Mining Frequent Patterns  without Candidate Gen

FP—Growth算法的更多相关文章

  1. Frequent Pattern 挖掘之二(FP Growth算法)(转)

    FP树构造 FP Growth算法利用了巧妙的数据结构,大大降低了Aproir挖掘算法的代价,他不需要不断得生成候选项目队列和不断得扫描整个数据库进行比对.为了达到这样的效果,它采用了一种简洁的数据结 ...

  2. 关联规则算法之FP growth算法

    FP树构造 FP Growth算法利用了巧妙的数据结构,大大降低了Aproir挖掘算法的代价,他不需要不断得生成候选项目队列和不断得扫描整个数据库进行比对.为了达到这样的效果,它采用了一种简洁的数据结 ...

  3. Frequent Pattern (FP Growth算法)

    FP树构造 FP Growth算法利用了巧妙的数据结构,大大降低了Aproir挖掘算法的代价,他不需要不断得生成候选项目队列和不断得扫描整个数据库进行比对.为了达 到这样的效果,它采用了一种简洁的数据 ...

  4. 机器学习(十五)— Apriori算法、FP Growth算法

    1.Apriori算法 Apriori算法是常用的用于挖掘出数据关联规则的算法,它用来找出数据值中频繁出现的数据集合,找出这些集合的模式有助于我们做一些决策. Apriori算法采用了迭代的方法,先搜 ...

  5. Frequent Pattern 挖掘之二(FP Growth算法)

    Frequent Pattern 挖掘之二(FP Growth算法) FP树构造 FP Growth算法利用了巧妙的数据结构,大大降低了Aproir挖掘算法的代价,他不需要不断得生成候选项目队列和不断 ...

  6. FP Tree算法原理总结

    在Apriori算法原理总结中,我们对Apriori算法的原理做了总结.作为一个挖掘频繁项集的算法,Apriori算法需要多次扫描数据,I/O是很大的瓶颈.为了解决这个问题,FP Tree算法(也称F ...

  7. FP Tree算法原理总结(转载)

    FP Tree算法原理总结 在Apriori算法原理总结中,我们对Apriori算法的原理做了总结.作为一个挖掘频繁项集的算法,Apriori算法需要多次扫描数据,I/O是很大的瓶颈.为了解决这个问题 ...

  8. FP - growth 发现频繁项集

    FP - growth是一种比Apriori更高效的发现频繁项集的方法.FP是frequent pattern的简称,即常在一块儿出现的元素项的集合的模型.通过将数据集存储在一个特定的FP树上,然后发 ...

  9. Fp关联规则算法计算置信度及MapReduce实现思路

    说明:參考Mahout FP算法相关相关源代码. 算法project能够在FP关联规则计算置信度下载:(仅仅是单机版的实现,并没有MapReduce的代码) 使用FP关联规则算法计算置信度基于以下的思 ...

随机推荐

  1. 使用jcifs.smb.SmbFile读取Windows上共享目录的文件

    protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws Servl ...

  2. PYTHON线程知识再研习A

    前段时间看完LINUX的线程,同步,信息号之类的知识之后,再在理解PYTHON线程感觉又不一样了. 作一些测试吧. thread:模块提供了基本的线程和锁的支持 threading:提供了更高级别,功 ...

  3. 简述TVS是的命名和封装

    1. 瞬态抑制二极管简称TVS (Transient Voltage Suppressor ),TVS的电气特性由P-N结面积,参杂浓度及晶片阻质决定的.其耐突波电流的能力与其P-N结面积成正比. 特 ...

  4. WCF的基本知识-仅Http绑定的认知

    有关WCF,这3个字母代表的含义,鄙人不会在此细说.喜欢或者不喜欢的,大家勿喷. 入正题,微软从设计.net框架开始,就一直着力于解决程序间的互通信问题.从古老的套接字(Socket)通信到后来的Re ...

  5. linux磁盘限额配置:quota命令

    LINUX下也有类似WINDOWS NTFS所用的磁盘限额,用的是quota来实现通过rpm -q quota确定是否已安装用quota只能对patation做限额,要做到针对某个目录来做只能靠ln ...

  6. BZOJ1106: [POI2007]立方体大作战tet

    1106: [POI2007]立方体大作战tet Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 419  Solved: 302[Submit][St ...

  7. CodeForces 19D Points

    Pete and Bob invented a new interesting game. Bob takes a sheet of paper and locates a Cartesian coo ...

  8. 第20讲- Spinner与适配器模式

    第20讲 Spinner与适配器模式 使用Spinner相当于从下拉列表中选择项目,Spinner是一个每次只能选择所有项的一个项的控件.它的项来自于与之相关联的适配器中.Spinner的重点问题就是 ...

  9. genymotion+Oracle VM VirtualBox + eclipse + appium 脚本运行慢解决步骤

    genymotion+Oracle VM VirtualBox + eclipse + appium 脚本运行慢解决步骤 1.lenove 机器启动时按F1 进入bios 设置,设置cpu virtu ...

  10. c语言编程之sglib库的简单使用

    说实话自从大学毕业后已经很久没有用c语言写过程序了,一般都是使用c++,c++的stl和boost等,这些代码库大大简化了我们的编程复杂度.由于最近某种原因在次开始用c写程序.我是个比较懒的人,比较喜 ...