Apriori 初步學習 + C++實現

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");
}





在學習代碼中,也學習了STL容器的一些相關使用方法,並記錄筆記。

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 和 second
pair<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);



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章