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

你可能感兴趣的:(算法)