DM&ML_note.1-Apriori

这个学期要学DM&ML,用的是《数据挖掘算法原理与实现》王振武 本着造福同学的思想,开一个DM&ML的笔记系列,打算给书上的源代码添加一点注释,方便阅读和理解。


前置知识要求:

C++,STL,离散数学
这份算法的实现,大量使用了STL的容器和迭代器,对于不熟悉C++的同学,请顺便了解什么是命名空间(using namespace std;Apriori:: 之类的),稍微看一下用到的vector,map,pair容器大概是怎么样,了解下模版(vector

具体实现:

//书中原注释直接给出,我加的注释应该都加上了hiro:的字样,仅供参考。
#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());

        //hiro:map 的键值对 用pair来存放,插入后返回迭代器和bool组成的pair
        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()
{
    /*hiro:1.检查初始的事务集非空*/
    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)
            break;
    }
    if (mit == item.end())//hiro:事务集为空
    {
        isEmpty = true;
        cout<<"事务集为空!程序无法进行..."<<endl;
        map< vector<string>,unsigned int> empty;
        return empty;//hiro:局部变量不会被吞掉吗,,,,不是等价于NULL吗
    }
    /*hiro:2.*/
    while(1)
    {
        map< vector<string>,unsigned int > K_itemTemp = K_item;

        K_item = apri_gen(i++,K_item);//hiro:将i传进去函数以后,i变量本身才+1s,

        if (K_itemTemp == K_item)//hiro:筛选到最后,结束
        {
            i = UINT_MAX;
            break;
        }
        //判断是否需要进行下一次的寻找
        map< vector<string>,unsigned int > pre_K_item = K_item;
        size_t Kitemsize = K_item.size();
        //存储应该删除的第K级频繁项集,不能和其他K级频繁项集构成第K+1级项集的集合
        /*hiro:看了一些资料,都没有提及都下面的这个要删除的项到底是什么,但是根据上面的注释以及代码阅读得知
        这里是打算删除K级项中,两两间交集的所有集合?*/
        if (Kitemsize != 1 && i != 1)
        {
            vector< map< vector<string>,unsigned int >::iterator > eraseVecMit;//hiro:保存要删除的项的迭代器
            map< vector<string>,unsigned int >::iterator pre_K_item_it1 = pre_K_item.begin() , pre_K_item_it2;
            //hiro:这个谜之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);
                /*hiro:end()本身指向vector末尾,相当于 定义了 int a[5],然后a[5]就是这里的end()
                / 而这个vector的构造器构造的范围是[begin,end),所以vec11复制了vec1的最后一个元素以外所有元素,即
                /int a[5]={0,1,2,3,4},b={0,1,2,3}*/
                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;
}

/*hiro:完成了连接和剪枝,返回符合支持度的
    1.求候选集C_K:把两个K级频繁项集在满足连接条件的情况下连接起来
    2.统计K+1级候选频繁项集的支持度并进行剪枝

*/
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
    {
        /*  hiro:1.求候选集C_K:把两个K级频繁项集在满足连接条件的情况下连接起来*/
        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;
                    /*hiro:新的候选项按字典序存放 所以>*/
                    if (str1>str2)
                    {
                        vec.push_back(str2);
                        vec.push_back(str1);
                    }
                    else
                    {
                        vec.push_back(str1);
                        vec.push_back(str2);
                    }
                    /*hiro:至此为止生成了一个新的候选项*/
                    /*2.统计K + 1级候选频繁项集的支持度并进行剪枝*/
                    map< int , vector<string> >::iterator base_item = item.begin();
                    unsigned int Acount = 0 ;
                    /*hiro:子集的判断*/
                    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++;
                            }
                            /*hiro:这里的数据设计很巧妙,一旦count曾经为0,即有一个不相符,min就永远为0*/
                            /*然而是不是应该有更优雅的方式,,,?*/
                            mincount = (count < mincount ? count : mincount );
                        }
                        if (mincount >=1 && mincount != UINT_MAX)
                            Acount += mincount;
                        ++base_item;
                    }
                    /*hiro:这里的Acount直接解决了样例数据中 {I1,I2,I3,I5},子集{I2,I3,I5}不是频繁的问题,
                    貌似可以证明这里用Acount就足够判断{I1,I2,I3,I5}的子集{I2,I3,I5}不是频繁集
                    因为K+1级是由两个K级的集合组成的,这两个K级的频繁项集不能保证链接后的结果也是频繁,
                    而造成连接后的结果不频繁,也正是因为有不频繁的子集存在;
                    对不起水平有限,或许再理一理清思路应该可以证明的,但是【摊手】,只能表达个大概意思了。
                    其实就是书上37页关于剪枝步的说明。
                     */
                    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;
        //hiro:size_t类型  用于跨平台,取该硬件平台下最大的数值,比如32位取4B,64位取8B
        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;

    /*hiro:为何不直接cin>>??为了无限长输入整数?不明所以*/
    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");
}

感想

首先这份代码目前还有一段不是很明白他的目的,为何要删掉两两间没有交集的频繁项?这个留着给老师发邮件解决,我会更新这个问题【如果还记得】
正式接触DM&ML,之前大致了解过,知道是做一些数据的统计和分析的。但是这个书上的实现版本,说实话不敢恭维。如果设计一下数据结构的话,应该不至于把遍历写得如此的别扭,源码中使用了很大量的局部变量进行副本存档,效率也是各种堪忧。。。
当然我自己编码经验不丰富,也可能是我不习惯这种编码的风格吧。
算法本身的效率也是很成问题的,空间开销巨大,时间开销也是N^2以上的级别,应该是会有优化的方案的,我暂时还没有去了解。
今天就先到这里吧。

附样例数据:

9
2
I1 I2 I5 123
I2 I4 123
I2 I3 123
I1 I2 I4 123
I1 I3 123
I2 I3 123
I1 I3 123
I1 I2 I3 I5 123
I1 I2 I3 123

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值