我就不說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為強規則
若有不足之處,還請指出.