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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值