DM&ML_note.6-K-中心点聚类算法

本文是关于数据挖掘与机器学习课程中K-中心点聚类算法的笔记,介绍了前置知识要求(C++,队列),详细阐述了算法的具体实现,并对算法的效率和源代码优化进行了讨论。作者指出,虽然K-中心点算法时间复杂度较高,但可以通过并发处理提升性能。同时,源码中存在优化空间,如使用STL队列可简化代码。
摘要由CSDN通过智能技术生成

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


前置知识要求

C++,队列

具体实现

#include "iostream"
#include "time.h"
using namespace std;

struct mem             //成员结构体包含符号和一个表示是否是中心点的属性
{
    bool isMedoid;
    char symbol;
};

struct Node;           //队列节点
typedef struct Node * PNode;    //队列节点指针
struct Node           //队列节点结构体
{
    mem info;
    PNode link;
};
struct LinkQueue{   //队列数据结构 
    PNode f;//hiro:队列头
    PNode r;//hiro:队列尾
};
typedef struct LinkQueue * PLinkQueue;         //队列指针

PLinkQueue createEmptyQueue_link()     //创建空队列函数
{
    PLinkQueue plqu;
    plqu=(PLinkQueue)malloc(sizeof(struct LinkQueue));
    if(plqu!=NULL)
    {
        plqu->f=NULL;
        plqu->r=NULL;
    }
    else
        cout<<"Out of space!"<<endl;
    return plqu;
}

int isEmptyQueue_link(PLinkQueue plqu)    //判断队列是否为空函数
{
    return (plqu->f==NULL);
}

void enQueue_Link(PLinkQueue plqu, mem x)       //元素入队函数
{
    PNode p;
    p=(PNode)malloc(sizeof(struct Node));
    if(p==NULL)cout<<"Out of space!"<<endl;
    else
    {
        p->info=x;
        p->link=NULL;
        if(plqu->f==NULL) plqu->f=p;
        else plqu->r->link=p;
        plqu->r=p;
    }
}

void deQueue_link(PLinkQueue plqu)          //队列元素出队并打印函数
{
    PNode p;
    if(plqu->f==NULL)cout<<"Empty Queue"<<endl;
    else
    {
        p=plqu->f;
        cout<<p->info.symbol;
        plqu->f=p->link;
        free(p);
    }

}

void showQueue(PLinkQueue pq)       //打印出队列并释放队列占用内存空间
{
    cout << "{";
    while (!isEmptyQueue_link(pq))
    {
        deQueue_link(pq);
        cout << ",";
    }
    cout << '\b';
    cout << "}" << endl;
}

/*hiro:以上所有函数,结构体为队列数据结构及相关操作的实现,
可以跳过阅读。
完全可以用stl代替,不过也写得没什么问题*/


/*hiro:真的就只是打印,可跳过*/
void showCase(double linjiebiao[5][5])       //打印邻接矩阵函数
{
    int i,j;
    char tmp;
    cout<<"目前的邻接矩阵形式如下:"<<endl;
    cout<<"=========================================="<<endl;
    cout<<"       A      B      C      D      E     "<<endl;
    for(i=0;i<5;i++)
    {
        switch (i)
        {
        case 0: tmp='A';break;
        case 1: tmp='B';break;
        case 2: tmp='C';break;
        case 3: tmp='D';break;
        case 4: tmp='E';break;
        }
        cout<<tmp;
        for(j=0;j<5;j++)
            cout<<"      "<<linjiebiao[i][j];
        cout<<endl;
    }
    cout<<"=========================================="<<endl;
}

/*hiro:真的就只是生成了两个不想等的随机数然后赋值。。。*/
void arbStart(struct mem memlist[5])    //起初时随机确定两个为中心点
{
    int i,j;
    for(i=0;i<5;i++)
        if(memlist[i].isMedoid!=false)
            memlist[i].isMedoid=false;
    srand((unsigned)time(NULL));
    i = rand()%5;
    memlist[i].isMedoid=true;
    j= rand()%5;
    while(j==i)
    {
        j= rand()%5;
    }
    memlist[j].isMedoid=true;
}

double distance(int j, int i, int k, double linjiebiao[5][5])  //求解点ji、k两个中心点中较近的那个点的距离,参考邻接矩阵linjiebiao 
{
    if(linjiebiao[j][i]<linjiebiao[j][k])
    return linjiebiao[j][i];
    else
    return linjiebiao[j][k];
}

//求中心点i和h交换后 距离代价TC  的变化值
double TC(int index[2],int i,int h, double linjiebiao[5][5])   
{
    int j;
    double sum=0;
    int tmp;/*hiro:tmp==另一个中心点对应的下标*/
    if(i==index[0])
        tmp=index[1];
    else if(i==index[1])
        tmp=index[0];
    for(j=0;j<5;j++)
    {
        /*hiro:
        distance(j,h,tmp,linjiebiao)替换后的距离
        distance(j, index[0], index[1],linjiebiao)原本的距离
        这一条公式直接囊括了四种情况,值得研究研究的。
        */
        sum+=distance(j,h,tmp,linjiebiao)-distance(j, index[0], index[1],linjiebiao);
    }
    return sum;
}

int smallest_distance_index(int index[2],int h,double linjiebiao[5][5])  //判断点h属于那个中心点以便形成簇
{
    int i,result=index[0];
    for(i=0;i<2;i++)
        if(linjiebiao[index[i]][h]<linjiebiao[index[0]][h])
            result=index[i];
        return result;
}

void reposition(mem memlist[5], double linjiebiao[5][5])        //PAM算法关键函数,是该算法的核心体现
{
    int count,count1,h,i,k,holdi,holdh,index[2];
    double tempdif;/*hiro:记录最小总代价*/
    bool tmp;

    do
    {
//------------------------每次训话计算出更新后的两个中心点的序号
        count1=0;
        for(k=0;k<5;k++)
        {
            if(memlist[k].isMedoid==true)
            {
                index[count1]=k;
                count1++;
            }
        }
//-------------------------

        count=0;
        for(h=0;h<5;h++)
        {
            for(i=0;i<5;i++)
            {/*hiro:遍历所有Oh非代表&&Oi为代表的组合*/
                if(memlist[h].isMedoid==false&&memlist[i].isMedoid==true)
                {
                    if(count==0)
                    {
                        tempdif=TC(index,i,h,linjiebiao);
                        holdi=i;
                        holdh=h;
                        count++;
                    }
                    else if (TC(index,i,h,linjiebiao)<tempdif)
                    {/*不断比较取记录最小的TC相应的值和下标*/
                        tempdif=TC(index,i,h,linjiebiao);
                        holdi=i;
                        holdh=h;
                        count++;
                    }
                }
            }
        }

        if (tempdif<0)/*hiro:P173:当最小总代价为正,可以认为认为算法结束*/
        {
            /*hiro:交换原中心点和新中心点的信息*/
            tmp=memlist[holdi].isMedoid;
            memlist[holdi].isMedoid=memlist[holdh].isMedoid;
            memlist[holdh].isMedoid=tmp;
        }
        else if(tempdif>=0)
            break;
        //-----------------------test--------
//      if(test==1)
//          cout<<"Yes"<<endl;
        //-------------------------test----------
    }
    while(1);
}

void main()               //主函数,提供邻接矩阵,出示成员集合等PAM算法需要的输入项
{
    int i,h,count;
    int index[2];                  //用来存储为中心点的两个点的索引
    PLinkQueue pq[2];             //预备两个队列用以存储表示两个簇
    pq[0]=createEmptyQueue_link();      //队列0的创建
    pq[1]=createEmptyQueue_link();          //队列1的创建
    double linjiebiao[5][5]={{0,1,2,2,3},{1,0,2,4,3},{2,2,0,1,5},{2,4,1,0,3},{3,3,5,3,0}};  //初始化邻接矩阵
    struct mem memlist[5]={{false,'A'},{false,'B'},{false,'C'},{false,'D'},{false,'E'}};   //初始化成员集合
    showCase(linjiebiao);/*hiro:可注释*/
    cout<<"期望得到的簇的数目暂定为 2 为例。"<<endl;
    cout<<endl<<endl<<endl;
    arbStart(memlist);            //随意确定两个点作为中心点


    cout<<"初始化后的中心点分布情况:"<<endl;
    int k;
    for(k=0;k<5;k++)
        cout<<memlist[k].symbol<<"       "<<memlist[k].isMedoid<<endl;
    /*hiro:数据初始化完毕,开跑*/
    reposition(memlist,linjiebiao);           //PAM算法处理
    cout<<endl<<endl<<endl;


    cout<<"经过PAM算法处理后的中心点分布情况:"<<endl;
    for(k=0;k<5;k++)
        cout<<memlist[k].symbol<<"       "<<memlist[k].isMedoid<<endl;
    cout<<endl<<endl<<endl;


    /*hiro:得到两个中心点以后开始处理两个簇*/

    count=0;
    for(i=0;i<5;i++)
    {
        if(memlist[i].isMedoid==true)
        {
//          cout<<memlist[i].symbol<<"是最终得到的中心点"<<endl;
            enQueue_Link(pq[count], memlist[i]);
            index[count]=i;
            count++;
        }

    }

    for(h=0;h<5;h++)
    {
        if(memlist[h].isMedoid==false)
        {
            if(smallest_distance_index(index,h,linjiebiao)==index[0])
                enQueue_Link(pq[0], memlist[h]);
            else if(smallest_distance_index(index,h,linjiebiao)==index[1])
                enQueue_Link(pq[1], memlist[h]);
        }
    }


    cout<<"以以上两个中心点为中心的两个簇为:"<<endl;

    showQueue(pq[0]);
    showQueue(pq[1]);

}

感想

额虽然应该可能大概没有在等更的同学,但是最近的确精力放在了其他地方,课程也差不多跟上了我之前的进度了,于是回来把剩下的两个算法给写了。。。嗯,我才不是懒更,只是在鼓捣别的【逃】
这个算法虽然简单,但是时间复杂度好高啊。。。。。
先不说一开始就要准备N^2(无论空间上还是时间上)的距离数据,在一次迭代当中,跑的好像还是N^2啊…,不过还好可以并发解决性能问题。
这份源码怎么说呢。。。其实用STL替代自己实现的队列可以少一堆代码,,然后还有各种各样的小地方看起来都很冗余,可以再精简很多的。。
回到算法本身来讲本质上和之前的K-means思路是很像的,只是一些距离衡量和中心点的选取上决策不一样了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值