本文參考韓家煒《數據挖掘-概念與技術》一書第六章,前提條件要理解 apriori算法。
另外一篇寫得較好的文章在此推薦:
http://hi.baidu.com/nefzpohtpndhovr/item/9d5c371ba2dbdc0ed1d66dca
0.實驗數據集:
I1,I2,I5 I2,I4 I2,I3 I1,I2,I4 I1,I3 I2,I3 I1,I3 I1,I2,I3,I5 I1,I2,I3
1.算法原理
構造FPTree1、首先讀取數據庫中全部種類的項和這些項的支持度計數。
存入到itTotal鏈表中。
2、將itTotal鏈表依照支持度計數從大到小排序
3、將itTotal鏈表插入到ItemTb表中
4、第二便讀取數據庫中的事務,將事務中的項依照支持度計數由大到小的順序插入到樹中。
5、遍歷樹,將屬於同一項的結點通過bnode指針連接起來。
本程序中,FP-tree中存儲了全部的項集,沒有考慮最小支持度。
僅僅是在FP-growth中挖掘頻繁項集時考慮最小支持度
/**
*
* @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;
}從一棵FPTree的ItemTb表中取得第一個項I1。假設該項的支持度計數滿足最小支持度計數{
1、把該項I1增加到存儲挖掘到的頻繁項集的數據結構ItemSet中
2、得到該項I1在眼下FPTree中的條件模式基。即該項在樹中的結點的前綴路徑(路徑中不再包含該項)。
注意該項I1的條件模式基中各個項的支持度計數相等。等於該項I1的支持度計數
3、每條路徑看作一個事務,用這些路徑建造該項的條件FPTree,然后遞歸調用FP_growth算法。
在遞歸調用FP_growth算法時,那些大於支持度計數的項作為項I1的孩子結點存儲在ItemSet中。
}
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){
System.out.println("-----------------");
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();
}
//打印頻繁項集
System.out.println(head.getName()+","+item+"\t"+count);
}
}
//尋找條件模式基,從鏈尾開始
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);
}
}
//System.out.println("-----------------");
//遞歸之,以求子FP-Tree
fpgrowth(newrecords,itemname);
}
}
2.tree的結構
private Integer count; // 計數
private TreeNode2 parent; // 父節點
private List<TreeNode2> children; // 子節點
private TreeNode2 nextHomonym; // 下一個同名節點
3.完整的源代碼:
package mysequence.machineleaning.association.fpgrowth;
import java.io.BufferedReader;
import java.io.FileInputStream;
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;
public class Myfptree2 {
public static final int support = 2; // 設定最小支持頻次為2
//保存第一次的次序
public Map<String,Integer> ordermap=new HashMap<String,Integer>();
public LinkedList<LinkedList<String>> readF1() throws IOException {
LinkedList<LinkedList<String>> records=new LinkedList<LinkedList<String>>();
//String filePath="scripts/clustering/canopy/canopy.dat";
String filePath="datafile/association/user2items.csv";
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 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){
System.out.println("-----------------");
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();
}
//打印頻繁項集
System.out.println(head.getName()+","+item+"\t"+count);
}
}
//尋找條件模式基,從鏈尾開始
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);
}
}
//System.out.println("-----------------");
//遞歸之,以求子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 static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
/*String s1="i1";
int flag=s1.compareTo("I1");
System.out.println(flag);*/
//讀取數據
Myfptree2 fpg=new Myfptree2();
LinkedList<LinkedList<String>> records=fpg.readF1();
LinkedList<TreeNode2> orderheader=fpg.buildHeaderLink(records);
fpg.orderF1(orderheader);
fpg.fpgrowth(records,null);
}
}
樹的結構:
package mysequence.machineleaning.association.fpgrowth;
import java.util.ArrayList;
import java.util.List;
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;
}
}
