关联规则FpGrowth算法 Java实现

关联规则算法有Apriori和FpGrowth,与Apriori相比,FpGrowth扫描数据库的次数更少,效率大大提高,FpGrowth算法通过构造一个树结构来压缩数据记录,使得挖掘频繁项集只需要扫描两次数据记录,而且该算法不需要生成候选集合

1. 构造FpTree

fpTree是一种树结构,定义数结构类


import java.util.ArrayList;  
import java.util.List;  

 /**
  * 
  * @author limingxu
  * 
  * 结点类
  */

public class TreeNode2 implements Comparable<TreeNode2>{  

    private String name; // 节点名称  
    private Integer count; // 计数  
    private TreeNode2 parent; // 父节点  
    private List<TreeNode2> children; // 子节点  
    private TreeNode2 nextHomonym; // 下一个同名节点  

    public TreeNode2() {  

    }  

    public String getName() {  
        return name;  
    }  

    public void setName(String name) {  
        this.name = name;  
    }  

    public Integer getCount() {  
        return count;  
    }  

    public void setCount(Integer count) {  
        this.count = count;  
    }  
    public void Sum(Integer count) {  
        this.count =this.count+count;  
    }  
    public TreeNode2 getParent() {  
        return parent;  
    }  

    public void setParent(TreeNode2 parent) {  
        this.parent = parent;  
    }  

    public List<TreeNode2> getChildren() {  
        return children;  
    }  

    public void setChildren(List<TreeNode2> children) {  
        this.children = children;  
    }  

    public TreeNode2 getNextHomonym() {  
        return nextHomonym;  
    }  

    public void setNextHomonym(TreeNode2 nextHomonym) {  
        this.nextHomonym = nextHomonym;  
    }  
    /** 
     * 添加一个节点 
     * @param child 
     */  
    public void addChild(TreeNode2 child) {  
        if (this.getChildren() == null) {  
            List<TreeNode2> list = new ArrayList<TreeNode2>();  
            list.add(child);  
            this.setChildren(list);  
        } else {  
            this.getChildren().add(child);  
        }  
    }  
    /** 
    *  是否存在着该节点,存在返回该节点,不存在返回空 
    * @param name 
    * @return 
    */  
    public TreeNode2 findChild(String name) {  
        List<TreeNode2> children = this.getChildren();  
        if (children != null) {  
            for (TreeNode2 child : children) {  
                if (child.getName().equals(name)) {  
                    return child;  
                }  
            }  
        }  
        return null;  
    }  


    @Override  
    public int compareTo(TreeNode2 arg0) {  
        // TODO Auto-generated method stub  
        int count0 = arg0.getCount();  
        // 跟默认的比较大小相反,导致调用Arrays.sort()时是按降序排列  
        return count0 - this.count;  
    }  
}  

2.算法步骤实例

Step 1:扫描数据记录,生成一级频繁项集,并按出现次数由多到少排序,如下所示:

ItemCount
牛奶4
面包4
尿布4
啤酒3

可以看到,鸡蛋和可乐没有出现在上表中,因为可乐只出现2次,鸡蛋只出现1次,小于最小支持度,因此不是频繁项集,根据Apriori定理,非频繁项集的超集一定不是频繁项集,所以可乐和鸡蛋不需要再考虑。

Step 2:再次扫描数据记录,对每条记录中出现在Step 1产生的表中的项,按表中的顺序排序。初始时,新建一个根结点,标记为null;

1)第一条记录:{牛奶,面包},按Step 1表过滤排序得到依然为{牛奶,面包},新建一个结点,idName为{牛奶},将其插入到根节点下,并设置count为1,然后新建一个{面包}结点,插入到{牛奶}结点下面,插入后如下所示:

这里写图片描述

2)第二条记录:{面包,尿布,啤酒,鸡蛋},过滤并排序后为:{面包,尿布,啤酒},发现根结点没有包含{面包}的儿子(有一个{面包}孙子但不是儿子),因此新建一个{面包}结点,插在根结点下面,这样根结点就有了两个孩子,随后新建{尿布}结点插在{面包}结点下面,新建{啤酒}结点插在{尿布}下面,插入后如下所示:

这里写图片描述

3)第三条记录:{牛奶,尿布,啤酒,可乐},过滤并排序后为:{牛奶,尿布,啤酒},这时候发现根结点有儿子{牛奶},因此不需要新建结点,只需将原来的{牛奶}结点的count加1即可,往下发现{牛奶}结点有一个儿子{尿布},于是新建{尿布}结点,并插入到{牛奶}结点下面,随后新建{啤酒}结点插入到{尿布}结点后面。插入后如下图所示:

这里写图片描述

4)第四条记录:{面包,牛奶,尿布,啤酒},过滤并排序后为:{牛奶,面包,尿布,啤酒},这时候发现根结点有儿子{牛奶},因此不需要新建结点,只需将原来的{牛奶}结点的count加1即可,往下发现{牛奶}结点有一个儿子{面包},于是也不需要新建{面包}结点,只需将原来{面包}结点的count加1,由于这个{面包}结点没有儿子,此时需新建{尿布}结点,插在{面包}结点下面,随后新建{啤酒}结点,插在{尿布}结点下面,插入后如下图所示:

5)第五条记录:{面包,牛奶,尿布,可乐},过滤并排序后为:{牛奶,面包,尿布},检查发现根结点有{牛奶}儿子,{牛奶}结点有{面包}儿子,{面包}结点有{尿布}儿子,本次插入不需要新建结点只需更新count即可,示意图如下:

这里写图片描述
按照上面的步骤,我们已经基本构造了一棵FpTree(Frequent Pattern Tree),树中每天路径代表一个项集,因为许多项集有公共项,而且出现次数越多的项越可能是公公项,因此按出现次数由多到少的顺序可以节省空间,实现压缩存储,另外我们需要一个表头和对每一个idName相同的结点做一个线索,方便后面使用,线索的构造也是在建树过程形成的,但为了简化FpTree的生成过程,我没有在上面提到,这个在代码有体现的,添加线索和表头的Fptree如下:

这里写图片描述

至此,整个FpTree就构造好了

1. 利用FpTree挖掘频繁项集

FpTree建好后,就可以进行频繁项集的挖掘,挖掘算法称为FpGrowth(Frequent Pattern Growth)算法,挖掘从表头header的最后一个项开始。

代码实现


import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;


/**
 * 
 * @author limingxu
 * 
 *  基于POI浏览行为的协同过滤  ItemCF
 *
 */
public class Myfptree {  
    public static final int  support = 1; // 设定最小支持频次为2  
    private static final String ITEM_SPLIT = ";";
    private final static double CONFIDENCE = 0.5; // 置信度阈值
    public Map<String,Integer> frequentCollectionMap = new HashMap<String, Integer>();
    public Map<Map<String,String>,Double> rules = new HashMap<Map<String,String>, Double>();
    public Map<String,Integer> oneCount = new HashMap<String, Integer>();


    private StringBuilder builder= new StringBuilder(); 
    //保存第一次的次序  
    public Map<String,Integer> ordermap=new HashMap<String,Integer>();  
    public LinkedList<LinkedList<String>> readF1() throws IOException {        
//        String filePath="scripts/clustering/canopy/canopy.dat";  
        LinkedList<LinkedList<String>> records =new LinkedList<LinkedList<String>>();  
        //数据文件位置,txt,用; 分隔
        String filePath="";
        BufferedReader br = new BufferedReader(new InputStreamReader(  
        new FileInputStream(filePath)));  
        for (String line = br.readLine(); line != null; line = br.readLine()) {  
            if(line.length()==0||"".equals(line))continue;  
            String[] str=line.split(";");     
            LinkedList<String> litm=new LinkedList<String>();  
            for(int i=0;i<str.length;i++){  
                litm.add(str[i].trim());  
            }  
            records.add(litm);               
        }  
        br.close();  
        return records;  
    }  

    //读取每个单项出现次数
    public void count ()
    {
        LinkedList<LinkedList<String>> records;
        try {
            records = readF1();
            for (LinkedList<String> l :records) {
                for(String s : l)
                {
                    if (oneCount.keySet().contains(s+";")) {
                        oneCount.put(s+";", oneCount.get(s+";")+1);
                    }else {
                        oneCount.put(s+";", 1);
                    }
                }

            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    //创建表头链  
    /**
     * 
     * @param records   原始记录List
     * @return
     */
    public LinkedList<TreeNode2> buildHeaderLink(LinkedList<LinkedList<String>> records){  
        LinkedList<TreeNode2> header=null;  
        if(records.size()>0){  
            header=new LinkedList<TreeNode2>();  
        }else{  
            return null;  
        }  
        Map<String, TreeNode2> map = new HashMap<String, TreeNode2>();  
        for(LinkedList<String> items:records){  

            for(String item:items){  
                //如果存在数量增1,不存在则新增  
                if(map.containsKey(item)){  
                    map.get(item).Sum(1);  
                }else{  
                    TreeNode2 node=new TreeNode2();  
                    node.setName(item);  
                    node.setCount(1);  
                    map.put(item, node);  
                }  
             }  
        }  
         // 把支持度大于(或等于)minSup的项加入到F1中  
        Set<String> names = map.keySet();  
        for (String name : names) {  
            TreeNode2 tnode = map.get(name);  
            if (tnode.getCount() >= support) {  
                header.add(tnode);  
            }  
        }  
        sort(header);  

        String test="ddd";  
        return header;  
    }  

    //选择法排序,如果次数相等,则按名字排序,字典顺序,先小写后大写  
    public List<TreeNode2> sort(List<TreeNode2> list){  
        int len=list.size();  
        for(int i=0;i<len;i++){  

            for(int j=i+1;j<len;j++){  
                TreeNode2 node1=list.get(i);  
                TreeNode2 node2=list.get(j);  
                if(node1.getCount()<node2.getCount()){  
                    TreeNode2 tmp=new TreeNode2();  
                    tmp=node2;  
                    list.remove(j);  
                    //list指定位置插入,原来的>=j元素都会往下移,不会删除,所以插入前要删除掉原来的元素  
                    list.add(j,node1);  
                    list.remove(i);  
                    list.add(i,tmp);  
                }  
                //如果次数相等,则按名字排序,字典顺序,先小写后大写  
                if(node1.getCount()==node2.getCount()){  
                    String name1=node1.getName();  
                    String name2=node2.getName();  
                    int flag=name1.compareTo(name2);  
                    if(flag>0){  
                        TreeNode2 tmp=new TreeNode2();  
                        tmp=node2;  
                        list.remove(j);  
                        //list指定位置插入,原来的>=j元素都会往下移,不会删除,所以插入前要删除掉原来的元素  
                        list.add(j,node1);  
                        list.remove(i);  
                        list.add(i,tmp);  
                    }  


                }  
            }  
        }  

        return list;  
    }  

    //选择法排序,降序,如果同名按L 中的次序排序  
    public   List<String> itemsort(LinkedList<String> lis,List<TreeNode2> header){  
        //List<String> list=new ArrayList<String>();  
        //选择法排序  
        int len=lis.size();  
        for(int i=0;i<len;i++){  
            for(int j=i+1;j<len;j++){  
                String key1=lis.get(i);  
                String key2=lis.get(j);  
                Integer value1=findcountByname(key1,header);  
                if(value1==-1)continue;  
                Integer value2=findcountByname(key2,header);  
                if(value2==-1)continue;  
                if(value1<value2){  
                    String tmp=key2;  
                    lis.remove(j);  
                    lis.add(j,key1);  
                    lis.remove(i);  
                    lis.add(i,tmp);  
                }  
                if(value1==value2){  
                    int v1=ordermap.get(key1);  
                    int v2=ordermap.get(key2);  
                    if(v1>v2){  
                        String tmp=key2;  
                        lis.remove(j);  
                        lis.add(j,key1);  
                        lis.remove(i);  
                        lis.add(i,tmp);  
                    }  
                }  
             }  
        }  
        return lis;  
    }  

    public Integer findcountByname(String itemname,List<TreeNode2> header){  
        Integer count=-1;  
        for(TreeNode2 node:header){  
            if(node.getName().equals(itemname)){  
                count= node.getCount();  
            }  
        }  
        return count;  
    }  

    /** 
     *  
     * @param records 构建树的记录,如I1,I2,I3 
     * @param header 韩书中介绍的表头 
     * @return 返回构建好的树 
     */  

    public TreeNode2 builderFpTree(LinkedList<LinkedList<String>> records,List<TreeNode2> header){  
           TreeNode2 root;  
           if(records.size()<=0){  
               return null;  
           }  
           root=new TreeNode2();  
           for(LinkedList<String> items:records){  
               itemsort(items,header);  
              addNode(root,items,header);  
            }  
        String dd="dd";   
        String test=dd;  
        return root;  
    }  
    //当已经有分枝存在的时候,判断新来的节点是否属于该分枝的某个节点,或全部重合,递归  
    public  TreeNode2 addNode(TreeNode2 root,LinkedList<String> items,List<TreeNode2> header){  
        if(items.size()<=0)return null;  
        String item=items.poll();  
        //当前节点的孩子节点不包含该节点,那么另外创建一支分支。  
        TreeNode2 node=root.findChild(item);  
        if(node==null){  
            node=new TreeNode2();  
            node.setName(item);  
            node.setCount(1);  
            node.setParent(root);  
            root.addChild(node);  

            //加将各个节点加到链头中   
            for(TreeNode2 head:header){  
                if(head.getName().equals(item)){  
                    while(head.getNextHomonym()!=null){  
                        head=head.getNextHomonym();  
                    }  
                    head.setNextHomonym(node);  
                    break;  
                }  
            }  
            //加将各个节点加到链头中  
        }else{  
            node.setCount(node.getCount()+1);  
        }  

        addNode(node,items,header);  
        return root;  
    }  
    //从叶子找到根节点,递归之  
    public void toroot(TreeNode2 node,LinkedList<String> newrecord){  
        if(node.getParent()==null)return;  
        String name=node.getName();  
        newrecord.add(name);  
        toroot(node.getParent(),newrecord);  
    }  
    //对条件FP-tree树进行组合,以求出频繁项集  
    public void combineItem(TreeNode2 node,LinkedList<String> newrecord,String Item){  
        if(node.getParent()==null)return;  
        String name=node.getName();  
        newrecord.add(name);  
        toroot(node.getParent(),newrecord);  
    }  
    //fp-growth  

    public void fpgrowth(LinkedList<LinkedList<String>> records,String item){  
        //保存新的条件模式基的各个记录,以重新构造FP-tree  
        LinkedList<LinkedList<String>> newrecords=new LinkedList<LinkedList<String>>();  
        //构建链头  
        LinkedList<TreeNode2> header=buildHeaderLink(records);  
        //创建FP-Tree  
        TreeNode2 fptree= builderFpTree(records,header);  
        //结束递归的条件  
        if(header.size()<=0||fptree==null){  
            return;  
        }  
        //打印结果,输出频繁项集  
        if(item!=null){  
            //寻找条件模式基,从链尾开始  
            for(int i=header.size()-1;i>=0;i--){  
                TreeNode2 head=header.get(i);  
                String itemname=head.getName();  
                Integer count=0;  
                while(head.getNextHomonym()!=null){  
                    head=head.getNextHomonym();  
                    //叶子count等于多少,就算多少条记录  
                    count=count+head.getCount();  

                }  
                //打印频繁项集  
                String items = "";
                items = item+";"+head.getName()+";";
                if (items.split(";").length<3) {
                    frequentCollectionMap.put(items, count);
                }

//                System.out.println(item+";"+head.getName()+"="+count);  
//                builder.append(item+";"+head.getName()+"="+count+"\n");
            }  
        }  
        //寻找条件模式基,从链尾开始  
        for(int i=header.size()-1;i>=0;i--){  
            TreeNode2 head=header.get(i);  
            String itemname;  
            //再组合  
            if(item==null){  
                itemname=head.getName();  
            }else{  
                itemname=head.getName()+";"+item;  
            }  

            while(head.getNextHomonym()!=null){  
                head=head.getNextHomonym();  
                //叶子count等于多少,就算多少条记录  
                Integer count=head.getCount();  
                for(int n=0;n<count;n++){  
                   LinkedList<String> record=new LinkedList<String>();  
                   toroot(head.getParent(),record);  
                   newrecords.add(record);  
                }  
            }  
            //递归之,以求子FP-Tree  
            fpgrowth(newrecords,itemname);  
        }  
    }  
    //保存次序,此步也可以省略,为了减少再加工结果的麻烦而加  
    public void orderF1(LinkedList<TreeNode2> orderheader){  
        for(int i=0;i<orderheader.size();i++){  
            TreeNode2 node=orderheader.get(i);  
            ordermap.put(node.getName(), i);  
        }  

    }  

    public void getRelationRules(
            Map<String, Integer> frequentCollectionMap) {
        count();
        Map<String, Double> relationRules = new HashMap<String, Double>();
        Set<String> keySet = frequentCollectionMap.keySet();
        for (String key : keySet) {
            double countAll = frequentCollectionMap.get(key);
            String[] keyItems = key.split(ITEM_SPLIT);
            if (keyItems.length > 1) {
                List<String> source = new ArrayList<String>();
                Collections.addAll(source, keyItems);
                List<List<String>> result = new ArrayList<List<String>>();

                buildSubSet(source, result);// 获得source的所有非空子集

//              System.out.println("----------\n"+source.toString()+"\n"+result.toString()+"\n-----------------\n");
                for (List<String> resultItemList : result) {
                    if (resultItemList.size() < source.size()) {// 只处理真子集
                        List<String> otherList = new ArrayList<String>();
                        for (String sourceItem : source) {
                            if (!resultItemList.contains(sourceItem)) {
                                otherList.add(sourceItem);
                            }
                        }
                        String reasonStr = "";// 前置
                        String resultStr = "";// 结果
                        for (String item : resultItemList) {
                            reasonStr = reasonStr + item + ITEM_SPLIT;
                        }
                        for (String item : otherList) {
                            resultStr = resultStr + item + ITEM_SPLIT;
                        }

                        double countReason = oneCount
                                .get(reasonStr);
                        double itemConfidence = countAll / countReason;// 计算置信度
                        if (itemConfidence >= CONFIDENCE) {
//                          String rule = reasonStr + "->" + resultStr;
                            Map rule = new HashMap<String,String>();
                            rule.put(reasonStr, resultStr);
                            rules.put(rule, itemConfidence);
                        }
                    }
                }
            }
        }
    }

        private void buildSubSet(List<String> sourceSet, List<List<String>> result) {
            // 仅有一个元素时,递归终止。此时非空子集仅为其自身,所以直接添加到result中
            if (sourceSet.size() == 1) {
                List<String> set = new ArrayList<String>();
                set.add(sourceSet.get(0));
                result.add(set);
            } else if (sourceSet.size() > 1) {
                // 当有n个元素时,递归求出前n-1个子集,在于result中
                buildSubSet(sourceSet.subList(0, sourceSet.size() - 1), result);
                int size = result.size();// 求出此时result的长度,用于后面的追加第n个元素时计数
                // 把第n个元素加入到集合中
                List<String> single = new ArrayList<String>();
                single.add(sourceSet.get(sourceSet.size() - 1));
                result.add(single);
                // 在保留前面的n-1子集的情况下,把第n个元素分别加到前n个子集中,并把新的集加入到result中;
                // 为保留原有n-1的子集,所以需要先对其进行复制
                List<String> clone;
                for (int i = 0; i < size; i++) {
                    clone = new ArrayList<String>();
                    for (String str : result.get(i)) {
                        clone.add(str);
                    }
                    clone.add(sourceSet.get(sourceSet.size() - 1));

                    result.add(clone);
                }
            }
        }


    public static void main(String[] args) throws IOException {  
        // TODO Auto-generated method stub  
        /*String s1="i1"; 
        int flag=s1.compareTo("I1"); 
        System.out.println(flag);*/  
        //读取数据  
        Myfptree fpg=new Myfptree();  
        LinkedList<LinkedList<String>> records=fpg.readF1();  
        LinkedList<TreeNode2> orderheader=fpg.buildHeaderLink(records);  
        fpg.orderF1(orderheader);  
        fpg.fpgrowth(records,null);  
        fpg.getRelationRules(fpg.frequentCollectionMap);
//        System.out.println(fpg.frequentCollectionMap);
        System.out.println(fpg.rules);

    }  

}  
  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值