1.基本概念
Apriori算法是一種挖掘關聯規則的頻繁項集算法,最早由R.Agrawal提出,現已廣泛的運用到商業、網絡安全等領域。最常見的淘寶相關推薦便包含有這一算法。
該算法的主要步驟爲:
(1) 找到所有支持度大於最小支持度的項目集,即頻繁項集(Frequent Itemset);
(2) 使用第(1)步的頻繁項目集產生期望的規則。
Apriori算法着重與第一步,挖掘頻繁項集。
形式化描述如下:令項集I={i1,i2,...in}且有一個數據集合D,它其中的每一條記錄T,都是I的子集。
那麼關聯規則都是形如A->B的表達式,A、B均爲I的子集,且A與B的交集爲空。
這條關聯規則的支持度:support = P(A∪B)
這條關聯規則的置信度:confidence = support(A∪B)/suport(A)
如果存在一條關聯規則,它的支持度和置信度都大於預先定義好的最小支持度與置信度,我們就稱它爲強關聯規則。強關聯規則就可以用來了解項之間的隱藏關係。所以關聯分析的主要目的就是爲了尋找強關聯規則,而Apriori算法則主要用來幫助尋找強關聯規則。
2.算法描述
符號說明:
k-itemset k維項目集
L(k) 具有最小支持度的最大 k-itemset
C(k) 候選的k維項目集
首先用sc.candidate通過第(k-1)步中生成的最大項目集L(k-1)來生成候選項目集
然後搜索數據庫計算候選項目集的C(k)的支持度 By count_support 函數
--------------------------------------------------------------------
綜述
C(1) = { candidate 1-itemsets };
L(1) = {c ∈ C(1) c.count >= minsupport};
for(k=2; L(k-1) != NULL; k++) //直到不能再生成最大項目集爲止
C(k) = sc.candidate( L(k-1) );//生成含k個元素的候選項目集C(k)
for all transactions t ∈ D//事務 D爲數據庫
<span style="white-space:pre"> </span>C(t) = count_support(C(k),t);//包含在事務t中的候選項目集 計算支持度
<span style="white-space:pre"> </span>for all candidates c ∈ C(t)
<span style="white-space:pre"> </span> c.count = c.count + 1;
<span style="white-space:pre"> </span> next
<span style="white-space:pre"> </span>L(k) = {c ∈ C(k) c.count >= minsupport};
next
resultset = resultset L(k) //resultset 最大項目集
sc.candidate 函數
該函數的參數爲 L(k-1) 即:所有最大 k-1 維項目集,結果返回含有 K 個項目的候選項目集 C(k)事實上,C(k)是 k 維最大項目集的超集,通過函數 count_support 計算項目的支持度,然後生成 L(k)
該函數的實現步驟如下:
首先,通過對 L(k-1) 自連接操作生成 C(k) ,稱 join (連接)步。
insert into C(K)
select P.item1,P.item2,......P.itemk-1,Q.itemk-1
from L(k-1)P,L(k-1)Q
where P.item1 = Q.item1,......P.itemk-2 = Q.itemk-2,P.itemk-1 < Q.itemk-1
然後是 prune(修剪)步,即對任意的 c , c ∈ C(k) ,刪除 C(k) 中所有那些(k-1)維子集不在 L(k-1) 中的項目集得到候選項目集 C(k) ,表述爲:
for all itemset c ∈ C(k)
<span style="white-space:pre"> </span>for all (k-1)維子集 s of c
<span style="white-space:pre"> </span>if(s !∈ L(k-1)) then
<span style="white-space:pre"> </span>delete c from C(k)
用集合表示 C(k) == { X ∈ C(k) , X 的所有k-1維子集在 L(k-1) 中}count_support 函數
參數爲候選項目集 C(k) 和某一事務記錄 t , 結果返回這一事務 t 中包含的候選項目集個數它的主要功能是找到包含在事務 t 中所有的候選項目集。
3.代碼
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <algorithm>
using namespace std;
class Apriori
{
public:
//構造函數
Apriori(size_t is =0,unsigned int mv=0)
{
item_size = is;
min_value = mv;
}
//~Apriori() {};
void getItem();
map< vector<string>,unsigned int> find_freitem();//求事務的頻繁項
//連接兩個k-1級頻繁項,得到第k級頻繁項
map< vector<string>,unsigned int > apri_gen(unsigned int K , map< vector<string>,unsigned int > K_item);
//展示頻繁項集
void showAprioriItem(unsigned int K,map< vector<string>,unsigned int > showmap);
private:
map< int , vector<string> > item;//存儲所有最開始的事務及其項
map< vector<string>,unsigned int > K_item;//存儲頻繁項集
size_t item_size;//事務數目
unsigned int min_value;//最小閾值
};
void Apriori::getItem()//用戶輸入最初的事務集
{
int ci = item_size;
for (int i=0;i<ci;i++)
{
string str;
vector<string> temp;
cout<<"請輸入第 "<<i+1<<"個事務的項集(123 end):";
while (cin>>str && str !="123")
{
temp.push_back(str);
}
sort(temp.begin(),temp.end());
pair< map<int ,vector<string> >::iterator , bool> ret = item.insert(make_pair(i+1 ,temp));
if (!ret.second)
{
--i;
cout<<"你輸入的元素已存在!請重新輸入!"<<endl;
}
}
cout<<"-------------運行結果如下:--------------"<<endl;
}
//求事務的頻繁項
map< vector<string>,unsigned int> Apriori::find_freitem()
{
unsigned int i = 1;
bool isEmpty = false;
map< int , vector<string> >::iterator mit ;
for (mit=item.begin();mit != item.end();mit++)
{
vector<string> vec = mit->second;
if (vec.size() != 0) //vec不爲空則跳出
break;
}
if (mit == item.end())//事務集爲空
{
isEmpty = true;
cout<<"事務集爲空!程序無法進行..."<<endl;
map< vector<string>,unsigned int> empty;
return empty;
}
while(1)
{
map< vector<string>,unsigned int > K_itemTemp = K_item;//k_item 儲存頻繁項集
K_item = apri_gen(i++,K_item);//循環生成k_item
if (K_itemTemp == K_item)
{
i = UINT_MAX;
break;
}
//判斷是否需要進行下一次的尋找
map< vector<string>,unsigned int > pre_K_item = K_item;
size_t Kitemsize = K_item.size();
//存儲應該刪除的第K級頻繁項集,不能和其他K級頻繁項集構成第K+1級項集的集合
if (Kitemsize != 1 && i != 1)
{
vector< map< vector<string>,unsigned int >::iterator > eraseVecMit;
map< vector<string>,unsigned int >::iterator pre_K_item_it1 = pre_K_item.begin() , pre_K_item_it2;
while (pre_K_item_it1 != pre_K_item.end() )
{
map< vector<string>,unsigned int >::iterator mit = pre_K_item_it1;
bool isExist = true;
vector<string> vec1;
vec1 = pre_K_item_it1->first;
vector<string> vec11(vec1.begin(),vec1.end()-1);
while (mit != pre_K_item.end())
{
vector<string> vec2;
vec2 = mit->first;
vector<string> vec22(vec2.begin(),vec2.end()-1);
if (vec11 == vec22)
break;
++mit;
}
if (mit == pre_K_item.end())
isExist = false;
if (!isExist && pre_K_item_it1 != pre_K_item.end())
eraseVecMit.push_back(pre_K_item_it1);//該第K級頻繁項應該刪除
++pre_K_item_it1;
}
size_t eraseSetSize = eraseVecMit.size();
if (eraseSetSize == Kitemsize)
break;
else
{
vector< map< vector<string>,unsigned int >::iterator >::iterator currentErs = eraseVecMit.begin();
while (currentErs != eraseVecMit.end())//刪除所有應該刪除的第K級頻繁項
{
map< vector<string>,unsigned int >::iterator eraseMit = *currentErs;
K_item.erase(eraseMit);
++currentErs;
}
}
}
else
if(Kitemsize == 1 )
break;
}
cout<<endl;
showAprioriItem(i,K_item);
return K_item;
}
map< vector<string>,unsigned int > Apriori::apri_gen(unsigned int K , map< vector<string>,unsigned int > K_item)
{
if (1 == K)//求候選集C1
{
size_t c1 = item_size;
map< int , vector<string> >::iterator mapit = item.begin();
vector<string> vec;
map<string,unsigned int> c1_itemtemp;
while (mapit != item.end() )//將原事務中所有的單項統計出來
{
vector<string> temp = mapit->second;
vector<string>::iterator vecit = temp.begin();
while (vecit != temp.end() )
{
pair< map<string,unsigned int>::iterator , bool > ret = c1_itemtemp.insert(make_pair(*vecit++ , 1));
if (!ret.second)
{
++ ret.first->second;
}
}
++mapit;
}
map<string,unsigned int>::iterator item_it = c1_itemtemp.begin();
map< vector<string>,unsigned int > c1_item;
while (item_it != c1_itemtemp.end() )//構造第一級頻繁項集
{
vector<string> temp;
if ( item_it->second >= min_value)
{
temp.push_back(item_it->first);
c1_item.insert(make_pair(temp , item_it->second) );
}
++item_it;
}
return c1_item;
}
else
{
cout<<endl;
showAprioriItem(K-1,K_item);
map< vector<string>,unsigned int >::iterator ck_item_it1 = K_item.begin(),ck_item_it2;
map< vector<string>,unsigned int > ck_item;
while (ck_item_it1 != K_item.end() )
{
ck_item_it2 = ck_item_it1;
++ck_item_it2;
map< vector<string>,unsigned int >::iterator mit = ck_item_it2;
//取當前第K級頻繁項與其後面的第K級頻繁項集聯合,但要注意聯合條件
//聯合條件:連個頻繁項的前K-1項完全相同,只是第K項不同,然後兩個聯合生成第K+1級候選頻繁項
while(mit != K_item.end() )
{
vector<string> vec,vec1,vec2;
vec1 = ck_item_it1->first;
vec2 = mit->first;
vector<string>::iterator vit1,vit2;
vit1 = vec1.begin();
vit2 = vec2.begin();
while (vit1 < vec1.end() && vit2 < vec2.end() )
{
string str1 = *vit1;
string str2 = *vit2;
++vit1;
++vit2;
if ( K ==2 || str1 == str2 )
{
if (vit1 != vec1.end() && vit2 != vec2.end() )
{
vec.push_back(str1);
}
}
else
break;
}
if (vit1 == vec1.end() && vit2 == vec2.end() )
{
--vit1;
--vit2;
string str1 = *vit1;
string str2 = *vit2;
if (str1>str2)
{
vec.push_back(str2);
vec.push_back(str1);
}
else
{
vec.push_back(str1);
vec.push_back(str2);
}
map< int , vector<string> >::iterator base_item = item.begin();
unsigned int Acount = 0 ;
while (base_item != item.end() )//統計該K+1級候選項在原事務集出現次數
{
unsigned int count = 0 ,mincount = UINT_MAX;
vector<string> vv = base_item->second;
vector<string>::iterator vecit , bvit ;
for (vecit = vec.begin();vecit < vec.end();vecit++)
{
string t = *vecit;
count = 0;
for (bvit=vv.begin();bvit < vv.end();bvit++)
{
if (t == *bvit)
count++;
}
mincount = (count < mincount ? count : mincount );
}
if (mincount >=1 && mincount != UINT_MAX)
Acount += mincount;
++base_item;
}
if (Acount >= min_value && Acount != 0)
{
sort(vec.begin(),vec.end());
//該第K+1級候選項爲頻繁項,插入頻繁項集
pair< map< vector<string>,unsigned int >::iterator , bool> ret = ck_item.insert(make_pair(vec,Acount));
if (! ret.second)
{
ret.first->second += Acount;
}
}
}
++mit;
}
++ck_item_it1;
}
if (ck_item.empty())//該第K+1級頻繁項集爲空,說明調用結束,把上一級頻繁項集返回
return K_item;
else
return ck_item;
}
}
void Apriori::showAprioriItem(unsigned int K,map< vector<string>,unsigned int > showmap)
{
map< vector<string>,unsigned int >::iterator showit = showmap.begin();
if (K != UINT_MAX)
cout<<endl<<"第 "<<K<<" 級頻繁項集爲:"<<endl;
else
cout<<"最終的頻繁項集爲:"<<endl;
cout<<"項 集"<<" \t "<<"頻率"<<endl;
while (showit != showmap.end() )
{
vector<string> vec = showit->first;
vector<string>::iterator vecit = vec.begin();
cout<<"{ ";
while (vecit != vec.end())
{
cout<<*vecit<<" ";
++vecit;
}
cout<<"}"<<"\t";
cout<<showit->second<<endl;
++showit;
}
}
unsigned int parseNumber(const char * str)//對用戶輸入的數字進行判斷和轉換
{
if (str == NULL)
return 0;
else
{
unsigned int num = 0;
size_t len = strlen(str);
for (size_t i=0;i<len;i++)
{
num *= 10;
if (str[i]>= '0' && str[i] <= '9')
num += str[i] - '0';
else
return 0;
}
return num;
}
}
void main()
{
//Apriori a;
unsigned int itemsize = 0;
unsigned int min;
//輸入 事務數(itemsize) 和 最小閥值(minsupport)
do
{
cout<<"請輸入事務數:";
char * str = new char;
cin>>str;
itemsize = parseNumber(str);
if (itemsize == 0)
{
cout<<"請輸入大於0正整數!"<<endl;
}
} while (itemsize == 0);
do
{
cout<<"請輸入最小閾值:";
char * str = new char;
cin>>str;
min = parseNumber(str);
if (min == 0)
{
cout<<"請輸入大於0正整數!"<<endl;
}
} while (min == 0);
Apriori a(itemsize,min);
a.getItem();
map< vector<string>,unsigned int> AprioriMap = a.find_freitem();
//a.showAprioriItem(UINT_MAX,AprioriMap);
system("pause");
}
map
//vector從後面快速的插入與刪除,直接訪問任何元素
//map 一對多映射,基於關鍵字快速查找,不允許重複值
For example, the following code creates a map that associates a string with an integer
map<const char*, int, strCmp> ages;
ages["Homer"] = 38;
ages["Marge"] = 37;
ages["Lisa"] = 8;
ages["Maggie"] = 1;
ages["Bart"] = 11;
insert的使用
pair<iterator, bool> insert( const pair<KEY_TYPE,VALUE_TYPE> &val );
//只有在val不存在時插入val。返回值是一個指向被插入元素的迭代器和一個描述是否插入的bool值。
迭代器 iterator 的使用
迭代器提供對一個容器中的對象的訪問方法,並且定義了容器中對象的範圍。迭代器就如同一個指針。事實上,C++的指針也是一種迭代器。但是,迭代器不僅僅是指針,因此你不能認爲他們一定具有地址值。
如下代碼對vector容器對象生成和使用了迭代器:
vector<int> the_vector;
vector<int>::iterator the_iterator;
for( int i=0; i < 10; i++ )
the_vector.push_back(i);//push_back() 在Vector最後添加一個元素
int total = 0;
the_iterator = the_vector.begin();
while( the_iterator != the_vector.end() ) {
total += *the_iterator; //point是地址 *p 是取地址p裏面的值
the_iterator++;
}
cout << "Total=" << total << endl;<pre code_snippet_id="1952980" snippet_file_name="blog_20161027_7_793811" name="code" class="cpp">//提示:通過對一個迭代器的解引用操作(*),可以訪問到容器所包含的元素。
cout << "Bart is " << ages["Bart"] << " years old" << endl;
map容器的迭代器裏面 有first 和 second例如map<string, int> m;
m["one"] = 1;
map<string, int>::iterator p = m.begin();
p->first; // 這個是 string 值是 "one"
p->second; //這個是 int 值是 1
pair類型 (http://blog.csdn.net/xywlpo/article/details/6458867)
pair是一種模版類型,其中包含兩個數值,兩個數據的類型可以不同,基本的定義如下:pair<int, string> a;
表示a中有兩個類型,第一個元素是int型的,第二個元素是string類型的,
如果創建pair的時候沒有對其進行初始化,則調用默認構造函數對其初始化。
pair<string, string> a("James", "Joy");
也可以像上面一樣在定義的時候直接對其初始化。
由於pair類型的使用比較繁瑣,因爲如果要定義多個形同的pair類型的時候,可以時候typedef簡化聲明:
typedef pair<string, string> author;
author pro("May", "Lily");
author joye("James", "Joyce");
pair對象的操作:
對於pair類,它只有兩個元素,分別爲first 和 secondpair<string, string> a("Lily", "Poly");
string name;
name = pair.second;
生成新的pair對象可以使用make_pair對已存在的兩個數據構造一個新的pair類型:
int a = 8;
string m = "James";
pair<int, string> newone;
newone = make_pair(a, m);