这个学期要学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思路是很像的,只是一些距离衡量和中心点的选取上决策不一样了。