DM&ML_note.6-K-中心點聚類算法

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章