我就不说FP-Tree的作用、优点什么的了,直接用例子来解释构建FP-Tree和找出所有频繁项集,第一次写博客,不对之处还请指出。
输入文件:
testInput.txt
T1 1 2 5
T2 4 2
T3 2 3
T4 1 2 4
T5 1 3
T6 2 3
T7 1 3
T8 1 2 3 5
T9 1 2 3
先计算所有数据的单项的支持度计数,计算后为{1,(支持度计数:6)} {2,(支持度计数:7)} {3,(支持度计数:6)} {4,(支持度计数:2)} {5,(支持度计数:2)}
然后根据支持度计数将各行数据按支持度计数的大小从大到小进行排序,排序后为:
2 1 5
2 4
2 3
2 1 4
1 3
2 3
1 3
2 1 3 5
2 1 3
然后构建FP-Tree树(最小支持度计数为2),同时构建项头表,项头表按支持度计数排序好(无箭头的表示树之间的连线,有箭头的表示链表之间的连线):
先是数据 2 1 5,括号里的数字为节点当前的支持度计数.
数据2 4
数据2 3
数据 2 1 4
数据 1 3
数据 2 3
数据 1 3
数据 2 1 3 5
数据 2 1 3
项5开始,从树周为5的节点向上遍历,找出条件模式基有{2 (1),1 (1)}和{2 (1),1 (1),3 (1)}两个条件模式基,由于3的支持度计数小于最小支持度计数,在构建的时候不能加入树中,所以构建后的项头表和条件FP-Tree树为:
因为其条件FP-Tree为单路径,所以只要找出{2 (2),1 (2)}的所有子项集加上后缀{5 (2)}即可,得出3个频繁项集{2(2),1(2),5(2)}、{2(2),5(2)}、{1(2),5(2)}.
然后是4,条件模式基为{2(1)}和{2(1),1(1)},1小于最小支持度计数,不参与树的构造,
遍历得出频繁项集{2(2),4(2)}
然后是3,条件模式基为{2(2),1(2)}、{2(2)}、{1(2)}
由于该条件FP-Tree不是单路径,所以应该遍历该项头表,即从1开始,此时后缀为3
遍历时,首先将项头表的1与其前一次后缀连接,得到一个频繁项集{1(4),3(4)},支持度计数为1在树中出现的总次数,1-频繁项集也是如此得到,因为那时前一次后缀为空;
然后得到1的条件模式基为{2(2)},因为只有一个,所以可以直接遍历其所有子项加上此时的后缀13构成一个频繁项集{2(2),1(2),3(2)},支持度计数为条件模式基的支持度计数
如果有两个条件模式基,则继续构建树,直到条件模式基为1个或树只有一条路径
然后是1,条件模式基为{2(4)},遍历后得到{2(4),1(4)}
最后是2,无条件模式基
总结
我在写代码的时候难点主要在构建条件FP-Tree然后找频繁项这里,我一开始并没有用构建条件FP-Tree来找频繁项,而是通过第一次的条件模式基,然后找出各项的所有条件模式基的子项,将满足支持度计数的并不重复的加入挑选出来,支持度计数需要与某项的所有条件模式基进行比较,若包含在该条件模式基中,则加上该条件模式基的支持度计数.
最后贴出我的Java代码(注意:我的代码是针对单项为"1 2 4"、"a v c d"这种单个字符的,若处理的数据为"啤酒 开瓶器 抹布"需要修改代码):
MyFrequentItem.java
public class MyFrequentItem {
//每项中含有的元素
private String array;
//该项的支持度计数
private int support;
//该项的长度,即第几项集
private int arrayLength;
public MyFrequentItem(String array,int support) {
this.array=array;
this.support=support;
arrayLength=array.length();
}
public String getArray() {
return array;
}
public void setArray(String array) {
this.array = array;
}
public int getSupport() {
return support;
}
public void setSupport(int support) {
this.support = support;
}
public int getArrayLength() {
return arrayLength;
}
public void setArrayLength(int arrayLength) {
this.arrayLength = arrayLength;
}
}
FP_tree.java
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
/**
*
* @author 石献衡
*
*/
class FP_Node{//树
String item;
int supportCount;
ArrayList<FP_Node> childNode;
FP_Node parent;
public FP_Node(String item,int supportCount,FP_Node parent) {
this.item=item;
this.supportCount=supportCount;
this.parent = parent;
childNode = new ArrayList<FP_Node>();
}
}
class LinkedListNode{//链表,同一条链表存储的TP_Node节点的item相同
FP_Node node;
LinkedListNode next;
public LinkedListNode(FP_Node node) {
this.node=node;
next=null;
}
}
public class FP_tree {
//文件位置
private String filePath;
//存储从文件中读取的数据
private String[][] datas;
//Integer代表频繁项集的项数,String表示频繁项集各项组成的字符串
private HashMap<Integer, HashMap<String,MyFrequentItem>> myFrequentItems;
//单项出现的次数,用来排序
HashMap<String, Integer> signalCount;
//最小支持度计数
private int minSupportCount;
//最小置信度
private double minConf;
//记录的总条数
private int allDataCount;
//强规则
ArrayList<String> strongRules;
//弱规则
ArrayList<String> weakRules;
private MyFrequentItem frequentItem;;
public FP_tree(String filePath,int minSupportCount) {
this.minSupportCount = minSupportCount;
this.filePath = filePath;
strongRules = new ArrayList<String>();
weakRules = new ArrayList<String>();
myFrequentItems = new HashMap<Integer, HashMap<String,MyFrequentItem>>();
//读取数据
readFile();
}
/**
* .读取文件中的数据储存到datas
*/
private void readFile() {
ArrayList<String[]> list = new ArrayList<String[]>();
try {
String string;
FileReader in = new FileReader(new File(filePath));
BufferedReader reader = new BufferedReader(in);
while((string=reader.readLine())!=null) {
list.add(string.substring(3).split(" "));
}
allDataCount = list.size();
datas = new String[allDataCount][];
list.toArray(datas);
reader.close();in.close();
list.clear();
} catch (Exception e) {
// TODO: handle exception
}
}
public void startTool(double minConf){
this.minConf = minConf;
//扫描并且排序
scanAndSort();
//初始化myFrequentItems
for(int i=1;i<=signalCount.size();i++) {
myFrequentItems.put(i, new HashMap<String, MyFrequentItem>());
}
HashMap<String, Integer> originList = new HashMap<String, Integer>();
//将datas的每条记录转换成一条排序好的字符串和它的支持度的形式
//如记录{2,1,5}转换成215和1,就如条件模式基以及其支持度计数
String s;
for(String[] strs : datas) {
s = "";
for (String string : strs) {
s+=string;
}
if(originList.containsKey(s))
originList.put(s,originList.get(s)+1);
else
originList.put(s,1);
}
//构造TP树,同时构造链表以及找出所有频繁项
fp_Growth(originList, signalCount, "",Integer.MAX_VALUE);
int n = signalCount.size();
HashMap<String, MyFrequentItem> map = new HashMap<String, MyFrequentItem>();
String string;
//输出各频繁项集的频繁项,包括它们的支持度计数
for(int i=1;i<=n;i++) {
map = myFrequentItems.get(i);
System.out.println(i+"-项频繁项集为:");
for (MyFrequentItem myFrequentItem : map.values()) {
System.out.print("{");
string = myFrequentItem.getArray();
for(int j=0;j<myFrequentItem.getArrayLength();j++) {
System.out.print(string.charAt(j)+",");
}
System.out.print("(支持度计数:"+myFrequentItem.getSupport()+")} ");
}
System.out.println();
}
//计算k-频繁项集中各频繁项的置信度
int k=3;
if(myFrequentItems.get(k).size()!=0) {
for (MyFrequentItem frequentItem : myFrequentItems.get(k).values()) {
relevance(frequentItem);
}
}
}
/**
* .计算该最大频繁项集与其子项集的关联性
* @param index 频繁集中的第index个
* @param k 项频繁集
*/
private void relevance(MyFrequentItem myFrequentItem2) {
//该项的支持度
int support = myFrequentItem2.getSupport();
//该频繁项的元素拼接成的字符串
String nowFrequentString = myFrequentItem2.getArray();
//找出所有子项集
childRelevance(nowFrequentString.toCharArray(), 0, "", "", support);
System.out.println();
for(String weakRule : weakRules) {
System.out.println(weakRule);
}
for(String strongRule : strongRules) {
System.out.println(strongRule);
}
//输出之后清空
weakRules.clear();
strongRules.clear();
}
/**
* .找出所有子项集
* @param child
* @param k
* @param childString 为子项集拼接的字符串
* @param otherString除子项集以外的拼接的字符串
* @param support
*/
private void childRelevance(char[] child,int k,String childString,String otherString,int support) {
if(child.length==k) {
//空串和其本身不计算
if(childString.length()==k||childString.length()==0) return;
//计算置信度
calculateRelevance(childString, otherString, support);
}else {
childRelevance(child, k+1, childString, otherString+child[k], support);//该字符不要
childRelevance(child, k+1, childString+child[k], otherString, support);//该字符要
}
}
/**
* .计算置信度
* @param childString
* @param otherString
* @param support
*/
private void calculateRelevance(String childString,String otherString,int support) {
String rule="";
//获取子频繁项childString的支持度计数
int childSupport = myFrequentItems.get(childString.length()).get(childString).getSupport();
double conf = (double)support/(double)childSupport;
rule+="{";
for(int m=0;m<childString.length();m++)rule+=(childString.charAt(m)+",");
rule+="}-->{";
for(int m=0;m<otherString.length();m++) {
rule+=(otherString.charAt(m)+",");
}
rule+=("},confindence(置信度):"+support+"/"+childSupport+"="+conf);
if(conf<minConf) {
rule+=("由于此规则置信度未达到最小置信度的要求,不是强规则");
weakRules.add(rule);
}else {
rule+=("为强规则");
strongRules.add(rule);
}
}
/**
* .构建树并找出所有频繁项
* @param originList 每条路径和该路径的支持度计数
* @param originCount originList中每个字符的支持度计数
* @param suffix 后缀
*/
private void fp_Growth(HashMap<String, Integer> originList,HashMap<String, Integer>originCount,String suffix,int suffixSupport) {
FP_Node root = new FP_Node(null, 0, null);//条件FP-Tree的根节点
//表头项
ArrayList<LinkedListNode> headListNode = new ArrayList<LinkedListNode>();
//构建树,并检查是否为单路径树
if(treeGrowth(suffix,root, originList, originCount, headListNode)) {//如果是单路径树,直接进行递归出所有子频繁项
String string = "";
while(root.childNode.size()!=0) {
root = root.childNode.get(0);
string+=root.item;
}
//递归找出所有频繁项
findFrequentItem(0, "", string.toCharArray(), suffixSupport, suffix,originCount);
}else {
//不是单路径树,从最后一个表头项的最后一个往上找条件模式基
findConditional(headListNode, originCount, suffix);
}
}
private boolean treeGrowth(String suffix,FP_Node root,HashMap<String, Integer> originList,HashMap<String, Integer>originCount,ArrayList<LinkedListNode> headListNode) {
//链表的当前节点
HashMap<String, LinkedListNode> nowNode = new HashMap<String, LinkedListNode>();
//表示是否找到该字符所在的节点,有则true,否则false并创一个新的节点
boolean flag;
//用来记录树是否为单路径
boolean isSingle = true;
FP_Node treeHead;
LinkedListNode listNode;
String[] strings;
int support;
for (String originString : originList.keySet()) {
//获取该条件模式基的支持度计数
support = originList.get(originString);
strings = originString.split("");
treeHead = root;
for(int i=0;i<strings.length; i++) {
//小于最小支持度计数的不加入树中
if(originCount.get(strings[i])<minSupportCount)continue;
flag = false;
for(FP_Node node : treeHead.childNode) {
if(strings[i].equals(node.item)) {
flag = true;
node.supportCount+=support;
treeHead = node;
break;
}
}
if(!flag) {//创建新的树节点,同时创建新的链表节点
for(int j=i;j<strings.length;j++) {
//小于最小支持度计数的不加入树中
if(originCount.get(strings[j])<minSupportCount)continue;
//创建新的树节点
FP_Node node = new FP_Node(strings[j], support,treeHead);
if(nowNode.containsKey(strings[j])) {//构建链表
listNode = new LinkedListNode(node);
nowNode.get(strings[j]).next = listNode;
nowNode.put(strings[j], listNode);
}else {//构建链表
listNode = new LinkedListNode(node);
headListNode.add(listNode);
nowNode.put(strings[j], listNode);
}
//构建树
treeHead.childNode.add(node);
treeHead = node;
}
break;
}
}
}
while(root.childNode.size()!=0) {//判断是否为单路径
if(root.childNode.size()==1) {
root = root.childNode.get(0);
}else {
isSingle = false;
break;
}
}
if(isSingle) return true;
Collections.sort(headListNode,new Comparator<LinkedListNode>() {//将链表的头节点按出现的总次数的降序排列
@Override
public int compare(LinkedListNode o1, LinkedListNode o2) {
int p = originCount.get(o2.node.item)-originCount.get(o1.node.item);
if(p==0)//如果支持度计数相等,按字典序排序
return o1.node.item.compareTo(o2.node.item);
else
return p;
}
});
return isSingle;
}
/**
* .找出各项的条件模式基,从headNode后面遍历链表的每个节点,
* .从每个节点中node开始向树的上方遍历,直到根节点停止
*/
private void findConditional(ArrayList<LinkedListNode> hNode,HashMap<String, Integer> originSupport,String suffix) {
//当前链表节点
LinkedListNode nowListNode;
//当前树节点
FP_Node nowTreeNode;
//条件模式基
HashMap<String, Integer> originList;
//所有条件模式基中各个事务出现的次数,方便条件FP-Tree在构造的时候剪枝
HashMap<String, Integer> originCount;
String ori;
String item;
String suf;
for(int i=hNode.size()-1;i>=0;i--) {
//获取链表头节点
nowListNode = hNode.get(i);
item = nowListNode.node.item;
suf = item+suffix;
//树中的单项支持度计数必定大于或等于最小支持度计数(构建树的完成的剪枝),所以将该项加其后缀加入频繁项集
myFrequentItems.get(suf.length()).put(suf, new MyFrequentItem(suf, originSupport.get(item)));
originList = new HashMap<String, Integer>();
originCount = new HashMap<String, Integer>();
int min;
while(nowListNode!=null) {
//从链表保存的树节点的父节点开始向上遍历
nowTreeNode = nowListNode.node.parent;
//获取该节点的支持度计数
min = nowListNode.node.supportCount;
//用来保存该条件模式基
ori = "";
while(nowTreeNode!=null&&nowTreeNode.item!=null) {
if(originCount.containsKey(nowTreeNode.item)) {
//如果条件模式基有如21和2这样两条及以上的有共同元素的,支持度计数叠加
originCount.put(nowTreeNode.item, originCount.get(nowTreeNode.item)+min);
}else {
originCount.put(nowTreeNode.item, min);
}
//保存条件模式基,按树的上面向下的顺序保存
ori=nowTreeNode.item+ori;
nowTreeNode = nowTreeNode.parent;
}
if(ori!="")
originList.put(ori, min);
nowListNode = nowListNode.next;
}
if(originList.size()!=0) {//条件模式基不为空
if(originList.size()==1) {//只有一条,直接递归其所有子项集
for (String modeBasis : originList.keySet()) {
findFrequentItem(0, "", modeBasis.toCharArray(), originList.get(modeBasis), suf,null);
}
}else {
//构建条件FP-Tree
fp_Growth(originList, originCount,suf,originSupport.get(item));
}
}
}
}
/**
*
* @param j 当前指向的modeBasis中的位置
* @param child 当前子项
* @param modeBasis 条件模式基中各个字符或单路径树各个节点组成的字符串
* @param support 该子项的所有单项中最小的支持度计数
* @param suffix 后缀,子项加上后缀即为新的频繁项
* @param originCount 单路径树各个节点的支持度计数
*/
private void findFrequentItem(int j,String child,char[] modeBasis,int support,String suffix,HashMap<String, Integer> originCount) {
if(j==modeBasis.length) {
if(child.length()!=0) {//子项不为空
child=child+suffix;
frequentItem = new MyFrequentItem(child, support);
myFrequentItems.get(child.length()).put(child, frequentItem);
}
}else {
int p = support;
//originCount为null时,代表为条件模式基,条件模式基中各项支持度计数相等
if(originCount!=null)
p =originCount.get(String.valueOf(modeBasis[j]));
findFrequentItem(j+1, child+modeBasis[j], modeBasis,support<p?support:p,suffix,originCount);//要该字符
findFrequentItem(j+1, child, modeBasis,support,suffix,originCount);//不要该字符
}
}
/**
* .扫描两遍数据,第一遍得出各个事物出现的总次数
* .第二遍将每条记录中的事务根据出现的总次数进行排序
*/
private void scanAndSort() {
//储存单项的总次数
signalCount = new HashMap<String, Integer>();
String c;
for (String[] string : datas) {//第一遍扫描,储存每个字符出现的次数
for(int i=0;i<string.length;i++) {
c = string[i];
if(signalCount.containsKey(c)) {
signalCount.put(c, signalCount.get(c)+1);
}else {
signalCount.put(c, 1);
}
}
}
for (String[] string : datas) {//第二遍扫描,按每个字符出现的总次数的降序进行排序
Arrays.sort(string,new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
int p = signalCount.get(o2)-signalCount.get(o1);
if(p==0)//如果出现的次数相等,按字典序排序
return o1.compareTo(o2);
else
return p;
}
});
}
}
}
MyClient.java
public class MyClient {
public static void main(String[] args) {
String filePath = "src/apriori/testInput.txt";
FP_tree tp_tree = new FP_tree(filePath, 2);
tp_tree.startTool(0.7);
}
}
输出:
1-项频繁项集为:
{1,(支持度计数:6)} {2,(支持度计数:7)} {3,(支持度计数:6)} {4,(支持度计数:2)} {5,(支持度计数:2)}
2-项频繁项集为:
{2,3,(支持度计数:4)} {2,4,(支持度计数:2)} {1,3,(支持度计数:4)} {2,5,(支持度计数:2)} {1,5,(支持度计数:2)} {2,1,(支持度计数:4)}
3-项频繁项集为:
{2,1,3,(支持度计数:2)} {2,1,5,(支持度计数:2)}
4-项频繁项集为:
5-项频繁项集为:
{3,}-->{2,1,},confindence(置信度):2/6=0.3333333333333333由于此规则置信度未达到最小置信度的要求,不是强规则
{1,}-->{2,3,},confindence(置信度):2/6=0.3333333333333333由于此规则置信度未达到最小置信度的要求,不是强规则
{1,3,}-->{2,},confindence(置信度):2/4=0.5由于此规则置信度未达到最小置信度的要求,不是强规则
{2,}-->{1,3,},confindence(置信度):2/7=0.2857142857142857由于此规则置信度未达到最小置信度的要求,不是强规则
{2,3,}-->{1,},confindence(置信度):2/4=0.5由于此规则置信度未达到最小置信度的要求,不是强规则
{2,1,}-->{3,},confindence(置信度):2/4=0.5由于此规则置信度未达到最小置信度的要求,不是强规则
{1,}-->{2,5,},confindence(置信度):2/6=0.3333333333333333由于此规则置信度未达到最小置信度的要求,不是强规则
{2,}-->{1,5,},confindence(置信度):2/7=0.2857142857142857由于此规则置信度未达到最小置信度的要求,不是强规则
{2,1,}-->{5,},confindence(置信度):2/4=0.5由于此规则置信度未达到最小置信度的要求,不是强规则
{5,}-->{2,1,},confindence(置信度):2/2=1.0为强规则
{1,5,}-->{2,},confindence(置信度):2/2=1.0为强规则
{2,5,}-->{1,},confindence(置信度):2/2=1.0为强规则
若有不足之处,还请指出.