zoukankan      html  css  js  c++  java
  • 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("	"+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("	"+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()+"	LinkedNode.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)+"	 ");
    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()+"	entry.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()+"	count="+temp.getCount());
    274                 temp = temp.getParentNode();
    275                 
    276                 singleTansaction.add(new TreeNode(temp.getName(), 1));
    277             }
    278             System.out.println("temp.name="+temp.getName()+"	count="+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("	");
    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("	("+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

  • 相关阅读:
    flask 本地局域网连接
    python
    Python 有关网址
    Python 字典(Dictionary)操作详解
    Elasticsearch集成HanLP分词器-个人学习
    知识图谱构建
    项目实战:如何构建知识图谱
    10分钟上手图数据库Neo4j
    知识图谱技术原理介绍
    知识图谱的应用
  • 原文地址:https://www.cnblogs.com/huicpc0212/p/4341334.html
Copyright © 2011-2022 走看看