FP-Tree题材关联规则Java实现

FP-Tree算法和Apriori算法都属于基于关联规则的分类算法,前者在实现时采用树形结构,避免了产生候选集的过程,使算法效率得到提升。

1.题材数据

动作 战争 
喜剧 爱情 
剧情 动作 犯罪 
剧情 动作 战争 
科幻 灾难 
喜剧 爱情 奇幻 
动作 战争 
喜剧 奇幻 
剧情 
剧情 

2.事务存储

对于1中的数据,需要使用List>的结构进行存储。

        List> datasList = new ArrayList<>();
        //1.读文件
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(new File("trolley.txt"))));
            String datasLine = null;
            while((datasLine = bufferedReader.readLine())!=null){
                String[] datas = datasLine.split(",");
                List dataList = new ArrayList<>();
                for (String s:datas){
                    dataList.add(s);
                    System.out.print(s+" ");
                }
                System.out.println();
                datasList.add(dataList);
            }
            bufferedReader.close();

3.建树

(1)获取属性集和属性频度

Map freqOneMap = new HashMap<>();
        for (List datalist: datasList){
            for (String s:datalist){
                if (freqOneMap.keySet().contains(s)){
                    freqOneMap.put(s, freqOneMap.get(s)+1);
                }else{
                    freqOneMap.put(s,1);
                }
            }
        }

freqOneMap 中包含所有属性和其频度,之后的建树、链表只取其中支持度合格的属性。
(2)将频繁一项集的属性作为链表头

Map headers = new HashMap<>();
for (Map.Entry entry: freqOneMap.entrySet()){
    if (entry.getValue() >= SUPPORT){
        TreeNode header = new TreeNode(entry.getKey(), entry.getValue());
        headers.put(header.getName(), header);
    }
}
 

(3)为每一个事务中的属性进行排序(排序规则是按属性的频度)

for (List dataList:datasList){
            Collections.sort(dataList, new Comparator() {
                @Override
                //o2大于o1则返回正数
                public int compare(String o1, String o2) {
                    return freqOneMap.get(o2) - freqOneMap.get(o1);
                }
            });
        }

(4)扫描每个事务中的属性,建树

扫描时会出现2种情况:树中有当前节点(沿路径存在),则为为该节点的count值加一;

                                       树中无当前节点,则该属性与在其之后的属性(同一事务下)都需要进行建立节点。

TreeNode root = new TreeNode();
        for (List dataList :datasList){
            //需要  添加或修改  节点的父节点
            TreeNode subTreeRoot = root;
            //需要将频度高的先进行连接,故之前进行排序
            //判断是否有孩子节点,有的话需要进行比较
            if (root.getChildren() != null){
                //当树中已存在节点时,无需添加,只为该节点count++
                while(!dataList.isEmpty() && root.getChildren().contains(dataList.get(0))){
                    for (TreeNode treeNode: root.getChildren()){
                        if (treeNode.getName().equals(dataList.get(0))){
                            int count = treeNode.getCount()+1;
                            treeNode.setCount(count);
                            dataList.remove(0);
                            subTreeRoot = treeNode;
                        }
                    }
                }

            }
            //树中不存在该节点时,需要添加新的节点,递归创建剩余节点
            addNode(subTreeRoot,dataList,headers);
        }

(5)创建节点方法

每个节点既是树中的节点,也是链表中的节点,因此每个节点可分为三个域看待:数据域,树指针域,链表指针域。

private static void addNode(TreeNode subTreeRoot, List dataList, Map headers) {
        while(!dataList.isEmpty()){
            String data = dataList.get(0);
            dataList.remove(0);
            //需要在频繁集(链表头集)中,其它数据直接抛弃
            if (headers.containsKey(data)){
                //数据域
                TreeNode treeNode = new TreeNode(data, 1);
                //指针域
                //树
                treeNode.setParent(subTreeRoot);
                subTreeRoot.addChild(treeNode);
                subTreeRoot = treeNode;
                //链表
                TreeNode header = headers.get(data);
                if (header.getTail() == null){
                    header.setNextHomonym(treeNode);
                }else{
                    header.getTail().setNextHomonym(treeNode);
                }
                header.setTail(treeNode);
                //递归
                addNode(subTreeRoot,dataList,headers);
            }
        }
    }

建立节点时采用递归建立,直至将该事务中剩余的属性全部(需要是频繁属性,不频繁属性直接跳过建立)建立节点。

节点的数据域包括属性名,属性频度;树指针域包括孩子节点(多个)、父节点;链表指针域包括尾节点、下一个节点;

private String name;
    private int count;
    private List children;
    private TreeNode parent;
    private TreeNode tail;//链表尾节点
    private TreeNode nextHomonym;//链表下一个节点

(6)缩小树

for (TreeNode header: headers.values()){

            //添加关联规则 且可作为下次递归的postModel(后缀模式)
            List ruleList = new ArrayList<>();
            ruleList.add(header.getName());
            if (postModel!=null){
                ruleList.addAll(postModel);
            }

            //添加到关联规则集中
            frequentMap.put(ruleList, header.getCount());

            //获得新的CPB(条件模式基)
            List> newDatasList = new ArrayList<>();
            TreeNode nextNode = header;
            while((nextNode = nextNode.getNextHomonym())!= null){
                TreeNode parent = nextNode.getParent();
                //为了保持父节点原有顺序,需要使用链表进行存储
                List tempList = new LinkedList<>();
                while(parent.getName() != null){
                    ((LinkedList) tempList).push(parent.getName());
                    parent = parent.getParent();
                }
                int count = nextNode.getCount();
                while(count--> 0){
                    newDatasList.add((List) ((LinkedList) tempList).clone());
                }
            }
            //递归
            FPTreeRun(newDatasList, ruleList);

        }

根据扫描“链表头集合”,扫描时根据每个链表头遍历该链表,得到规则,并产生新的条件模式基和后缀模式。
 

 

本文参考自https://www.cnblogs.com/zhangchaoyang/articles/2198946.html

你可能感兴趣的:(数据挖掘)