FP—Growth算法

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

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

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

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

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

FP_growth算法
剩下的就是构造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,有:

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

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

FP_growth算法
第四条记录:I2,I1,I4有:

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

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

FP_growth算法
这颗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 }
View Code

树节点的数据结构

 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 }
View Code

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

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

posted on 2015-03-16 11:12  未选择的路  阅读(1693)  评论(0编辑  收藏  举报

导航