在关联规则挖掘领域最经典的算法法是Apriori,其致命的缺点是需要多次扫描事务数据库。于是人们提出了各种裁剪(prune)数据集的方法以减少I/O开支,韩嘉炜老师的FP-Tree算法就是其中非常高效的一种。
我们举个例子来详细讲解FP-Tree算法的完整实现。
事务数据库如下,一行表示一条购物记录:
牛奶,鸡蛋,面包,薯片
鸡蛋,爆米花,薯片,啤酒
鸡蛋,面包,薯片
牛奶,鸡蛋,面包,爆米花,薯片,啤酒
牛奶,面包,啤酒
鸡蛋,面包,啤酒
牛奶,面包,薯片
牛奶,鸡蛋,面包,黄油,薯片
牛奶,鸡蛋,黄油,薯片
我们的目的是要找出哪些商品总是相伴出现的,比如人们买薯片的时候通常也会买鸡蛋,则[薯片,鸡蛋]就是一条频繁模式(frequent pattern)。
FP-Tree算法第一步:扫描事务数据库,每项商品按频数递减排序,并删除频数小于最小支持度MinSup的商品。(第一次扫描数据库)
薯片:7 鸡蛋:7 面包:7 牛奶:6 啤酒:4 (这里我们令MinSup=3)
以上结果就是频繁1项集,记为F1。
第二步:对于每一条购买记录,按照F1中的顺序重新排序。(第二次也是最后一次扫描数据库)
薯片,鸡蛋,面包,牛奶
薯片,鸡蛋,啤酒
薯片,鸡蛋,面包
薯片,鸡蛋,面包,牛奶,啤酒
面包,牛奶,啤酒
鸡蛋,面包,啤酒
薯片,面包,牛奶
薯片,鸡蛋,面包,牛奶
薯片,鸡蛋,牛奶
第三步:把第二步得到的各条记录插入到FP-Tree中。
插入每一条(薯片,鸡蛋,面包,牛奶)之后
插入第二条记录(薯片,鸡蛋,啤酒)
插入第三条记录(面包,牛奶,啤酒)
估计你也知道怎么插了,最终生成的FP-Tree是:
树中相同名称的节点要链接起来,后面的算法要用到。
第四步:从FP-Tree中找出频繁项。
遍历F1中的每一项(我们拿“牛奶:6”为例),对于各项都执行以下(1)到(5)的操作:
(1)从FP-Tree中找到所有的“牛奶”节点,向上遍历它的祖先节点,得到4条路径:
薯片:7,鸡蛋:6,牛奶:1
薯片:7,鸡蛋:6,面包:4,牛奶:3
薯片:7,面包:1,牛奶:1
面包:1,牛奶:1
对于每一条路径上的节点,其count都设置为牛奶的count
薯片:1,鸡蛋:1,牛奶:1
薯片:3,鸡蛋:3,面包:3,牛奶:3
薯片:1,面包:1,牛奶:1
面包:1,牛奶:1
因为每一项末尾都是牛奶,可以把牛奶去掉,得到条件模式基(Conditional Pattern Base,CPB)
薯片:1,鸡蛋:1
薯片:3,鸡蛋:3,面包:3
薯片:1,面包:1
面包:1
(2)我们把上面的结果当作原始的事务数据库,对于进行每一步和第二步的处理,得到条件FP-Tree
(3)从树中找到所有的长路径
(薯片:4,面包:4,鸡蛋:3)
(薯片:1,鸡蛋:1)
(面包:1)
(4)对于(3)中的每一条路径找出所有的组合方式
第一条:(薯片:4)(面包:4)(鸡蛋:3)(薯片:3,鸡蛋:3)(面包:3,鸡蛋:3)(薯片:4,面包:4)(薯片:3,面包:3,鸡蛋:3)
第二条:(薯片:1)(鸡蛋:1)(薯片:1,鸡蛋:1)
第三条:(面包:1)
每一个组合中的count要一致,都取最小的那一项。
然后把三条得到的组合合并到一起,合并的方法是:对于序列相同的组合,其count相加。比如第一条中的(面包:4)和第三条中的(面包:1)合并后成为(面包:5),而第一条中的(薯片:3,鸡蛋:3)和第二条中的(薯片:1,鸡蛋:1)合并后成为(薯片:4,鸡蛋:4)。最后删除count小于MinSup的组合。只剩下:
面包:4 薯片:4
薯片:5
鸡蛋: 4
鸡蛋: 3 面包:3
鸡蛋: 4 薯片:4
鸡蛋: 3 面包:3 薯片: 3
面包: 5
(5)与“牛奶”合并,得到频繁项集
面包 薯片 牛奶 4
薯片 牛奶 5
鸡蛋 牛奶 4
鸡蛋 面包 牛奶 3
鸡蛋 薯片 牛奶 4
鸡蛋 面包 薯片 牛奶 3
面包 牛奶 5
源代码实现:
FP树节点定义
001
package fptree;
|
002 |
|
003 |
import java.util.ArrayList;
|
004 |
import java.util.List;
|
005 |
|
006 |
public class TreeNode implements Comparable<TreeNode> {
|
007 |
|
008 |
private String name; // 节点名称
|
009 |
private int count; // 计数
|
010 |
private TreeNode parent; // 父节点
|
011 |
private List<TreeNode> children; // 子节点
|
012 |
private TreeNode nextHomonym; // 下一个同名节点
|
013 |
|
014 |
public TreeNode() {
|
015 |
|
016 | } |
017 |
|
018 |
public TreeNode(String name) {
|
019 | this.name = name; |
020 | } |
021 |
|
022 |
public String getName() {
|
023 |
return name;
|
024 | } |
025 |
|
026 |
public void setName(String name) {
|
027 | this.name = name; |
028 | } |
029 |
|
030 |
public int getCount() {
|
031 |
return count;
|
032 | } |
033 |
|
034 |
public void setCount(int count) {
|
035 | this.count = count; |
036 | } |
037 |
|
038 |
public TreeNode getParent() {
|
039 |
return parent;
|
040 | } |
041 |
|
042 |
public void setParent(TreeNode parent) {
|
043 | this.parent = parent; |
044 | } |
045 |
|
046 |
public List<TreeNode> getChildren() {
|
047 |
return children;
|
048 | } |
049 |
|
050 |
public void addChild(TreeNode child) {
|
051 |
if (this.getChildren() == null) {
|
052 |
List<TreeNode> list = new ArrayList<TreeNode>();
|
053 | list.add(child); |
054 | this.setChildren(list); |
055 |
} else {
|
056 | this.getChildren().add(child); |
057 | } |
058 | } |
059 |
|
060 |
public TreeNode findChild(String name) {
|
061 | List<TreeNode> children = this.getChildren(); |
062 |
if (children != null) {
|
063 |
for (TreeNode child : children) {
|
064 |
if (child.getName().equals(name)) {
|
065 |
return child;
|
066 | } |
067 | } |
068 | } |
069 |
return null;
|
070 | } |
071 |
|
072 |
public void setChildren(List<TreeNode> children) {
|
073 | this.children = children; |
074 | } |
075 |
|
076 |
public void printChildrenName() {
|
077 | List<TreeNode> children = this.getChildren(); |
078 |
if (children != null) {
|
079 |
for (TreeNode child : children) {
|
080 | System.out.print(child.getName() + " "); |
081 | } |
082 |
} else {
|
083 | System.out.print("null"); |
084 | } |
085 | } |
086 |
|
087 |
public TreeNode getNextHomonym() {
|
088 |
return nextHomonym;
|
089 | } |
090 |
|
091 |
public void setNextHomonym(TreeNode nextHomonym) {
|
092 | this.nextHomonym = nextHomonym; |
093 | } |
094 |
|
095 |
public void countIncrement(int n) {
|
096 | this.count += n; |
097 | } |
098 |
|
099 | @Override |
100 |
public int compareTo(TreeNode arg0) {
|
101 | // TODO Auto-generated method stub |
102 |
int count0 = arg0.getCount();
|
103 | // 跟默认的比较大小相反,导致调用Arrays.sort()时是按降序排列 |
104 |
return count0 - this.count;
|
105 | } |
106 | } |
挖掘频繁模式
001 |
package fptree;
|
002 |
|
003 |
import java.io.BufferedReader;
|
004 |
import java.io.File;
|
005 |
import java.io.FileReader;
|
006 |
import java.io.IOException;
|
007 |
import java.util.ArrayList;
|
008 |
import java.util.Collections;
|
009 |
import java.util.Comparator;
|
010 |
import java.util.HashMap;
|
011 |
import java.util.Iterator;
|
012 |
import java.util.LinkedList;
|
013 |
import java.util.List;
|
014 |
import java.util.Map;
|
015 |
import java.util.Map.Entry;
|
016 |
import java.util.Set;
|
017 |
|
018 |
public class FPTree {
|
019 |
|
020 |
private int minSup; // 最小支持度
|
021 |
|
022 |
public int getMinSup() {
|
023 |
return minSup;
|
024 | } |
025 |
|
026 |
public void setMinSup(int minSup) {
|
027 | this.minSup = minSup; |
028 | } |
029 |
|
030 | /** |
031 | * 1.读入事务记录 |
032 | * |
033 | * @param filenames |
034 | * @return |
035 | */ |
036 |
public List<List<String>> readTransData(String... filenames) {
|
037 |
List<List<String>> records = new LinkedList<List<String>>();
|
038 | List<String> record; |
039 | // 从文件读入 |
040 |
if (filenames.length > 0) {
|
041 |
for (String filename : filenames) {
|
042 |
try {
|
043 |
FileReader fr = new FileReader(new File(filename));
|
044 |
BufferedReader br = new BufferedReader(fr);
|
045 | String line = null; |
046 |
while ((line = br.readLine()) != null) {
|
047 |
if (line.trim() != "") {
|
048 |
record = new LinkedList<String>();
|
049 | String[] items = line.split("[,|,]"); |
050 |
for (String item : items) {
|
051 | record.add(item); |
052 | } |
053 | records.add(record); |
054 | } |
055 | } |
056 |
} catch (IOException e) {
|
057 | System.out.println("读取事务数据库失败。"); |
058 | System.exit(-2); |
059 | } |
060 | } |
061 | } |
062 | // 直接在代码里指定 |
063 |
else {
|
064 |
record = new LinkedList<String>();
|
065 |
String[] trans = new String[] { "f", "a", "c", "d", "g", "i", "m",
|
066 |
"p" };
|
067 |
for (String t : trans)
|
068 | record.add(t); |
069 | records.add(record); |
070 |
record = new LinkedList<String>();
|
071 |
trans = new String[] { "a", "b", "c", "f", "l", "m", "o" };
|
072 |
for (String t : trans)
|
073 | record.add(t); |
074 | records.add(record); |
075 |
record = new LinkedList<String>();
|
076 |
trans = new String[] { "b", "f", "h", "j", "o" };
|
077 |
for (String t : trans)
|
078 | record.add(t); |
079 | records.add(record); |
080 |
record = new LinkedList<String>();
|
081 |
trans = new String[] { "b", "c", "k", "s", "p" };
|
082 |
for (String t : trans)
|
083 | record.add(t); |
084 | records.add(record); |
085 |
record = new LinkedList<String>();
|
086 |
trans = new String[] { "a", "f", "c", "e", "l", "p", "m", "n" };
|
087 |
for (String t : trans)
|
088 | record.add(t); |
089 | records.add(record); |
090 | } |
091 |
return records;
|
092 | } |
093 |
|
094 | /** |
095 | * 2.构造频繁1项集 |
096 | * |
097 | * @param transRecords |
098 | * @return |
099 | */ |
100 |
public ArrayList<TreeNode> buildF1Items(List<List<String>> transRecords) {
|
101 | ArrayList<TreeNode> F1 = null; |
102 |
if (transRecords.size() > 0) {
|
103 |
F1 = new ArrayList<TreeNode>();
|
104 |
Map<String, TreeNode> map = new HashMap<String, TreeNode>();
|
105 | // 计算事务数据库中各项的支持度 |
106 |
for (List<String> record : transRecords) {
|
107 |
for (String item : record) {
|
108 |
if (!map.keySet().contains(item)) {
|
109 |
TreeNode node = new TreeNode(item);
|
110 | node.setCount(1); |
111 | map.put(item, node); |
112 |
} else {
|
113 | map.get(item).countIncrement(1); |
114 | } |
115 | } |
116 | } |
117 | // 把支持度大于(或等于)minSup的项加入到F1中 |
118 | Set<String> names = map.keySet(); |
119 |
for (String name : names) {
|
120 | TreeNode tnode = map.get(name); |
121 |
if (tnode.getCount() >= minSup) {
|
122 | F1.add(tnode); |
123 | } |
124 | } |
125 | Collections.sort(F1); |
126 |
return F1;
|
127 |
} else {
|
128 |
return null;
|
129 | } |
130 | } |
131 |
|
132 | /** |
133 | * 3.建立FP-Tree |
134 | * |
135 | * @param transRecords |
136 | * @param F1 |
137 | * @return |
138 | */ |
139 |
public TreeNode buildFPTree(List<List<String>> transRecords,
|
140 | ArrayList<TreeNode> F1) { |
141 |
TreeNode root = new TreeNode(); // 创建树的根节点
|
142 |
for (List<String> transRecord : transRecords) {
|
143 | LinkedList<String> record = sortByF1(transRecord, F1); |
144 | TreeNode subTreeRoot = root; |
145 | TreeNode tmpRoot = null; |
146 |
if (root.getChildren() != null) {
|
147 |
while (!record.isEmpty()
|
148 | && (tmpRoot = subTreeRoot.findChild(record.peek())) != null) { |
149 | tmpRoot.countIncrement(1); |
150 | subTreeRoot = tmpRoot; |
151 | record.poll(); |
152 | } |
153 | } |
154 | addNodes(subTreeRoot, record, F1); |
155 | } |
156 |
return root;
|
157 | } |
158 |
|
159 | /** |
160 | * 3.1把事务数据库中的一条记录按照F1(频繁1项集)中的顺序排序 |
161 | * |
162 | * @param transRecord |
163 | * @param F1 |
164 | * @return |
165 | */ |
166 |
public LinkedList<String> sortByF1(List<String> transRecord,
|
167 | ArrayList<TreeNode> F1) { |
168 |
Map<String, Integer> map = new HashMap<String, Integer>();
|
169 |
for (String item : transRecord) {
|
170 | // 由于F1已经是按降序排列的, |
171 |
for (int i = 0; i < F1.size(); i++) {
|
172 | TreeNode tnode = F1.get(i); |
173 |
if (tnode.getName().equals(item)) {
|
174 | map.put(item, i); |
175 | } |
176 | } |
177 | } |
178 |
ArrayList<Entry<String, Integer>> al = new ArrayList<Entry<String, Integer>>(
|
179 | map.entrySet()); |
180 |
Collections.sort(al, new Comparator<Map.Entry<String, Integer>>() {
|
181 | @Override |
182 |
public int compare(Entry<String, Integer> arg0,
|
183 | Entry<String, Integer> arg1) { |
184 | // 降序排列 |
185 |
return arg0.getValue() - arg1.getValue();
|
186 | } |
187 | }); |
188 |
LinkedList<String> rest = new LinkedList<String>();
|
189 |
for (Entry<String, Integer> entry : al) {
|
190 | rest.add(entry.getKey()); |
191 | } |
192 |
return rest;
|
193 | } |
194 |
|
195 | /** |
196 | * 3.2 把若干个节点作为指定指定节点的后代插入树中 |
197 | * |
198 | * @param ancestor |
199 | * @param record |
200 | * @param F1 |
201 | */ |
202 |
public void addNodes(TreeNode ancestor, LinkedList<String> record,
|
203 | ArrayList<TreeNode> F1) { |
204 |
if (record.size() > 0) {
|
205 |
while (record.size() > 0) {
|
206 | String item = record.poll(); |
207 |
TreeNode leafnode = new TreeNode(item);
|
208 | leafnode.setCount(1); |
209 | leafnode.setParent(ancestor); |
210 | ancestor.addChild(leafnode); |
211 |
|
212 |
for (TreeNode f1 : F1) {
|
213 |
if (f1.getName().equals(item)) {
|
214 |
while (f1.getNextHomonym() != null) {
|
215 | f1 = f1.getNextHomonym(); |
216 | } |
217 | f1.setNextHomonym(leafnode); |
218 | break; |
219 | } |
220 | } |
221 |
|
222 | addNodes(leafnode, record, F1); |
223 | } |
224 | } |
225 | } |
226 |
|
227 | /** |
228 | * 4. 从FPTree中找到所有的频繁模式 |
229 | * |
230 | * @param root |
231 | * @param F1 |
232 | * @return |
233 | */ |
234 |
public Map<List<String>, Integer> findFP(TreeNode root,
|
235 | ArrayList<TreeNode> F1) { |
236 |
Map<List<String>, Integer> fp = new HashMap<List<String>, Integer>();
|
237 |
|
238 | Iterator<TreeNode> iter = F1.iterator(); |
239 |
while (iter.hasNext()) {
|
240 | TreeNode curr = iter.next(); |
241 | // 寻找cur的条件模式基CPB,放入transRecords中 |
242 |
List<List<String>> transRecords = new LinkedList<List<String>>();
|
243 | TreeNode backnode = curr.getNextHomonym(); |
244 |
while (backnode != null) {
|
245 |
int counter = backnode.getCount();
|
246 |
List<String> prenodes = new ArrayList<String>();
|
247 | TreeNode parent = backnode; |
248 | // 遍历backnode的祖先节点,放到prenodes中 |
249 |
while ((parent = parent.getParent()).getName() != null) {
|
250 | prenodes.add(parent.getName()); |
251 | } |
252 |
while (counter-- > 0) {
|
253 | transRecords.add(prenodes); |
254 | } |
255 | backnode = backnode.getNextHomonym(); |
256 | } |
257 |
|
258 | // 生成条件频繁1项集 |
259 | ArrayList<TreeNode> subF1 = buildF1Items(transRecords); |
260 | // 建立条件模式基的局部FP-tree |
261 | TreeNode subRoot = buildFPTree(transRecords, subF1); |
262 |
|
263 | // 从条件FP-Tree中寻找频繁模式 |
264 |
if (subRoot != null) {
|
265 | Map<List<String>, Integer> prePatterns = findPrePattern(subRoot); |
266 |
if (prePatterns != null) {
|
267 | Set<Entry<List<String>, Integer>> ss = prePatterns |
268 | .entrySet(); |
269 |
for (Entry<List<String>, Integer> entry : ss) {
|
270 | entry.getKey().add(curr.getName()); |
271 | fp.put(entry.getKey(), entry.getValue()); |
272 | } |
273 | } |
274 | } |
275 | } |
276 |
|
277 |
return fp;
|
278 | } |
279 |
|
280 | /** |
281 | * 4.1 从一棵FP-Tree上找到所有的前缀模式 |
282 | * |
283 | * @param root |
284 | * @return |
285 | */ |
286 |
public Map<List<String>, Integer> findPrePattern(TreeNode root) {
|
287 | Map<List<String>, Integer> patterns = null; |
288 | List<TreeNode> children = root.getChildren(); |
289 |
if (children != null) {
|
290 |
patterns = new HashMap<List<String>, Integer>();
|
291 |
for (TreeNode child : children) {
|
292 | // 找到以child为根节点的子树中的所有长路径(所谓长路径指它不是其他任何路径的子路径) |
293 | LinkedList<LinkedList<TreeNode>> paths = buildPaths(child); |
294 |
if (paths != null) {
|
295 |
for (List<TreeNode> path : paths) {
|
296 | Map<List<String>, Integer> backPatterns = combination(path); |
297 | Set<Entry<List<String>, Integer>> entryset = backPatterns |
298 | .entrySet(); |
299 |
for (Entry<List<String>, Integer> entry : entryset) {
|
300 | List<String> key = entry.getKey(); |
301 |
int c1 = entry.getValue();
|
302 |
int c0 = 0;
|
303 |
if (patterns.containsKey(key)) {
|
304 | c0 = patterns.get(key).byteValue(); |
305 | } |
306 | patterns.put(key, c0 + c1); |
307 | } |
308 | } |
309 | } |
310 | } |
311 | } |
312 |
|
313 | // 过滤掉那些小于MinSup的模式 |
314 | Map<List<String>, Integer> rect = null; |
315 |
if (patterns != null) {
|
316 |
rect = new HashMap<List<String>, Integer>();
|
317 | Set<Entry<List<String>, Integer>> ss = patterns.entrySet(); |
318 |
for (Entry<List<String>, Integer> entry : ss) {
|
319 |
if (entry.getValue() >= minSup) {
|
320 | rect.put(entry.getKey(), entry.getValue()); |
321 | } |
322 | } |
323 | } |
324 |
return rect;
|
325 | } |
326 |
|
327 | /** |
328 | * 4.1.1 找到从指定节点(root)到所有可达叶子节点的路径 |
329 | * |
330 | * @param stack |
331 | * @param root |
332 | */ |
333 |
public LinkedList<LinkedList<TreeNode>> buildPaths(TreeNode root) {
|
334 | LinkedList<LinkedList<TreeNode>> paths = null; |
335 |
if (root != null) {
|
336 |
paths = new LinkedList<LinkedList<TreeNode>>();
|
337 | List<TreeNode> children = root.getChildren(); |
338 |
if (children != null) {
|
339 | //在从树上分离单条路径时,对分叉口的节点,其count也要分到各条路径上去 |
340 | //条件FP-Tree是多枝的情况 |
341 |
if (children.size() > 1) {
|
342 |
for (TreeNode child : children) {
|
343 |
int count = child.getCount();
|
344 | LinkedList<LinkedList<TreeNode>> ll = buildPaths(child); |
345 |
for (LinkedList<TreeNode> lp : ll) {
|
346 |
TreeNode prenode = new TreeNode(root.getName());
|
347 | prenode.setCount(count); |
348 | lp.addFirst(prenode); |
349 | paths.add(lp); |
350 | } |
351 | } |
352 | } |
353 | //条件FP-Tree是单枝的情况 |
354 | else{ |
355 |
for (TreeNode child : children) {
|
356 | LinkedList<LinkedList<TreeNode>> ll = buildPaths(child); |
357 |
for (LinkedList<TreeNode> lp : ll) {
|
358 | lp.addFirst(root); |
359 | paths.add(lp); |
360 | } |
361 | } |
362 | } |
363 |
} else {
|
364 |
LinkedList<TreeNode> lp = new LinkedList<TreeNode>();
|
365 | lp.add(root); |
366 | paths.add(lp); |
367 | } |
368 | } |
369 |
return paths;
|
370 | } |
371 |
|
372 | /** |
373 | * 4.1.2 |
374 | * 生成路径path中所有元素的任意组合,并记下每一种组合的count--其实就是组合中最后一个元素的count,因为我们的组合算法保证了树中 |
375 | * (或path中)和组合中元素出现的相对顺序不变 |
376 | * |
377 | * @param path |
378 | * @return |
379 | */ |
380 |
public Map<List<String>, Integer> combination(List<TreeNode> path) {
|
381 |
if (path.size() > 0) {
|
382 | // 从path中移除首节点 |
383 | TreeNode start = path.remove(0); |
384 | // 首节点自己可以成为一个组合,放入rect中 |
385 |
Map<List<String>, Integer> rect = new HashMap<List<String>, Integer>();
|
386 |
List<String> li = new ArrayList<String>();
|
387 | li.add(start.getName()); |
388 | rect.put(li, start.getCount()); |
389 |
|
390 | Map<List<String>, Integer> postCombination = combination(path); |
391 |
if (postCombination != null) {
|
392 | Set<Entry<List<String>, Integer>> set = postCombination |
393 | .entrySet(); |
394 |
for (Entry<List<String>, Integer> entry : set) {
|
395 | // 把首节点之后元素的所有组合放入rect中 |
396 | rect.put(entry.getKey(), entry.getValue()); |
397 | // 首节点并上其后元素的各种组合放入rect中 |
398 |
List<String> ll = new ArrayList<String>();
|
399 | ll.addAll(entry.getKey()); |
400 | ll.add(start.getName()); |
401 | rect.put(ll, entry.getValue()); |
402 | } |
403 | } |
404 |
|
405 |
return rect;
|
406 |
} else {
|
407 |
return null;
|
408 | } |
409 | } |
410 |
|
411 | /** |
412 | * 输出频繁1项集 |
413 | * |
414 | * @param F1 |
415 | */ |
416 |
public void printF1(List<TreeNode> F1) {
|
417 | System.out.println("F-1 set: "); |
418 |
for (TreeNode item : F1) {
|
419 |
System.out.print(item.getName() + ":" + item.getCount() + "\t");
|
420 | } |
421 | System.out.println(); |
422 | System.out.println(); |
423 | } |
424 |
|
425 | /** |
426 | * 打印FP-Tree |
427 | * |
428 | * @param root |
429 | */ |
430 |
public void printFPTree(TreeNode root) {
|
431 | printNode(root); |
432 | List<TreeNode> children = root.getChildren(); |
433 |
if (children != null && children.size() > 0) {
|
434 |
for (TreeNode child : children) {
|
435 | printFPTree(child); |
436 | } |
437 | } |
438 | } |
439 |
|
440 | /** |
441 | * 打印树上单个节点的信息 |
442 | * |
443 | * @param node |
444 | */ |
445 |
public void printNode(TreeNode node) {
|
446 |
if (node.getName() != null) {
|
447 |
System.out.print("Name:" + node.getName() + "\tCount:"
|
448 | + node.getCount() + "\tParent:" |
449 | + node.getParent().getName()); |
450 |
if (node.getNextHomonym() != null)
|
451 | System.out.print("\tNextHomonym:" |
452 | + node.getNextHomonym().getName()); |
453 | System.out.print("\tChildren:"); |
454 | node.printChildrenName(); |
455 | System.out.println(); |
456 |
} else {
|
457 | System.out.println("FPTreeRoot"); |
458 | } |
459 | } |
460 |
|
461 | /** |
462 | * 打印最终找到的所有频繁模式集 |
463 | * |
464 | * @param patterns |
465 | */ |
466 |
public void printFreqPatterns(Map<List<String>, Integer> patterns) {
|
467 | System.out.println(); |
468 |
System.out.println("MinSupport=" + this.getMinSup());
|
469 | System.out.println("Frequent Patterns and their Support"); |
470 | Set<Entry<List<String>, Integer>> ss = patterns.entrySet(); |
471 |
for (Entry<List<String>, Integer> entry : ss) {
|
472 | List<String> list = entry.getKey(); |
473 |
for (String item : list) {
|
474 | System.out.print(item + " "); |
475 | } |
476 | System.out.print("\t"+entry.getValue()); |
477 | System.out.println(); |
478 | } |
479 | } |
480 |
|
481 |
public static void main(String[] args) {
|
482 |
FPTree fptree = new FPTree();
|
483 | fptree.setMinSup(3); |
484 | List<List<String>> transRecords = fptree.readTransData("/home/orisun/test/market"); //第一组测试 |
485 | //List<List<String>> transRecords = fptree.readTransData(); //第二组测试 |
486 | ArrayList<TreeNode> F1 = fptree.buildF1Items(transRecords); |
487 | fptree.printF1(F1); |
488 | TreeNode treeroot = fptree.buildFPTree(transRecords, F1); |
489 | fptree.printFPTree(treeroot); |
490 |
|
491 | Map<List<String>, Integer> patterns = fptree.findFP(treeroot, F1); |
492 | fptree.printFreqPatterns(patterns); |
493 | } |
494 | } |
输出:
F-1 set: |
薯片:7 鸡蛋:7 面包:7 牛奶:6 啤酒:4 |
|
FPTreeRoot |
Name:薯片 Count:7 Parent:null Children:鸡蛋 面包
|
Name:鸡蛋 Count:6 Parent:薯片 NextHomonym:鸡蛋 Children:面包 啤酒 牛奶 |
Name:面包 Count:4 Parent:鸡蛋 NextHomonym:面包 Children:牛奶 |
Name:牛奶 Count:3 Parent:面包 NextHomonym:牛奶 Children:啤酒 |
Name:啤酒 Count:1 Parent:牛奶 NextHomonym:啤酒 Children:null |
Name:啤酒 Count:1 Parent:鸡蛋 NextHomonym:啤酒 Children:null |
Name:牛奶 Count:1 Parent:鸡蛋 Children:null |
Name:面包 Count:1 Parent:薯片 Children:牛奶 |
Name:牛奶 Count:1 Parent:面包 NextHomonym:牛奶 Children:null |
Name:面包 Count:1 Parent:null NextHomonym:面包 Children:牛奶
|
Name:牛奶 Count:1 Parent:面包 NextHomonym:牛奶 Children:啤酒 |
Name:啤酒 Count:1 Parent:牛奶 NextHomonym:啤酒 Children:null |
Name:鸡蛋 Count:1 Parent:null Children:面包
|
Name:面包 Count:1 Parent:鸡蛋 NextHomonym:面包 Children:啤酒 |
Name:啤酒 Count:1 Parent:面包 Children:null |
|
MinSupport=3 |
Frequent Patterns and their Support |
面包 薯片 牛奶 4 |
薯片 牛奶 5 |
鸡蛋 薯片 面包 4 |
鸡蛋 牛奶 4 |
鸡蛋 面包 牛奶 3 |
薯片 面包 5 |
鸡蛋 薯片 牛奶 4 |
面包 啤酒 3 |
鸡蛋 面包 薯片 牛奶 3 |
面包 牛奶 5 |
鸡蛋 啤酒 3 |
薯片 鸡蛋 6 |
鸡蛋 面包 5
转载至:http://www.kuqin.com/algorithm/20111004/312340.html