在关联规则挖掘领域最经典的算法法是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算法的实现(Java版)

插入第二条记录(薯片,鸡蛋,啤酒)

FP-Tree算法的实现(Java版)

插入第三条记录(面包,牛奶,啤酒)

FP-Tree算法的实现(Java版)

估计你也知道怎么插了,最终生成的FP-Tree是:

FP-Tree算法的实现(Java版)

树中相同名称的节点要链接起来,后面的算法要用到。

第四步:从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

FP-Tree算法的实现(Java版)

(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 }

挖掘频繁模式

输出:

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



相关文章:

  • 2021-09-30
  • 2022-12-23
  • 2021-07-17
  • 2021-11-11
  • 2022-12-23
  • 2021-08-03
  • 2021-11-22
  • 2021-09-22
猜你喜欢
  • 2021-08-04
  • 2022-02-25
  • 2021-05-29
  • 2021-11-29
  • 2021-07-17
相关资源
相似解决方案