這個學期要學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思路是很像的,只是一些距離衡量和中心點的選取上決策不一樣了。