1. 程式人生 > >KMeans演算法的實現

KMeans演算法的實現

//咱走的不是流量,走的是心


#include<iostream>
#include<cstdlib>
#include<cmath>
#include<ctime>
#include<fstream>
#include<cstdio>
using namespace std;


#define Length  5    //資料維數(每個資料集,5維向量)
#define K       5     //類別數(分為5類)
#define Psize  50    //資料集總量
#define T      100   //根據實驗要求選擇迭代次數
#define End    0.00001 //結束條件




typedef struct
{
    double p[Length];//儲存資料向量
    double distance[K];//儲存距離不同類中心的距離
}Point;


typedef struct
{
    Point clu_cent[K];//簇類中心
    int cluster[K][Psize];//記錄屬於K簇的樣本編號
    int cluster_num[K];//記錄屬於K簇的樣個個數
////data與old_data為了計算聚類之前和聚類之後的變化,如果變化小,則停止聚類,說明比較收斂
    double data;
    double old_data;
}Cluster_Center;




Point all_data[Psize];//資料大小
Cluster_Center sample;




//自動生成樣本資料
void rand_data()
{
    int sum=50;
    srand(time(0));
    ofstream outfile,fout;
    outfile.open("G:\\test.data");
    int s[5];
    while(sum)
    {
      for(int i=0;i<5;i++)
      s[i]=rand()%100;


      outfile<<s[0]<<" "<<s[1]<<" "<<s[2]<<" "<<s[3]<<" "<<s[4]<<endl;//將輸出寫入到檔案中,所有cout的函式都可以用
      sum--;
    }
    outfile.close();
}




//將樣本資料讀入記憶體
void input_data()
{
    FILE *infile;
    int i,j;
    double data;
    if((infile=fopen("G:\\test.data","r"))==NULL)
    {
        cout<<"沒有test.data這個檔案,無法匯入資料"<<endl;
        exit(1);
    }
    for(i=0;i<Psize;i++)//資料集的大小
    {
         for(j=0;j<Length;j++)//每條資料集中的個數
        {
            fscanf(infile,"%lf",&data);
            all_data[i].p[j]=data;          //存入到all_data中去。
           // cout<<data<<" ";
        }
           // cout<<endl;
    }


    fclose(infile);//關閉檔案描述符
}


//檢查資料是否相等
int Equal(int a[],int n,int b)
{


    for(int i=0;i<n;i++)
    if(a[i]==b) return 1;


    return 0;
}


//隨機初始化聚類質心(要檢測聚類中心不能相同)
void Init_center()
{
    int sum=0;
    int rand_num;
    int center[K];
    //隨機產生三個0~Psize的數
    while(sum<K)
    {
        rand_num=rand()%Psize;//設定中心標號
        if(!Equal(center,sum,rand_num))
        {
            center[sum++]=rand_num;
        }
    }
    for(int i=0;i<K;i++)//中心資料放到center中
    {
        for(int j=0;j<Length;j++)
        {
            sample.clu_cent[i].p[j]=all_data[center[i]].p[j];//重新賦值
        }
    }
}


//歐幾里得距離公式
double Euclidean_Distance(int a,int b)//a代表的是樣本資料,b代表的是簇中心資料
{
    double square=0;
    for(int i=0;i<Length;i++)
    {
        square+=pow((all_data[a].p[i]-sample.clu_cent[b].p[i]),2);
    }
    /*
    for(int i=0;i<n;i++)//n維向量
    {
        square+=(a[i]-b[i])*(a[i]-b[i]);
    }
    */
    return sqrt(square);
}


//計算Psize組資料到K個質心的歐幾里得距離
void claculate_distance()
{
    int i,j;
    for(i=0;i<Psize;i++)
    for(j=0;j<K;j++)
    {
        all_data[i].distance[j]=Euclidean_Distance(i,j);//傳遞的是向量的標號
    }
}




//將資料進行聚類
void cluster()
{


    double minV;
    for(int i=0;i<K;i++)  //初始化每個簇中心中的資料為0
    sample.cluster_num[i]=0;


    for(int i=0;i<Psize;i++)
    {
        int index=0;
        minV=all_data[i].distance[0];
        for(int j=1;j<K;j++)//篩選到簇心歐幾里得最小的
        {
            if(all_data[i].distance[j]<minV)
            {
                minV=all_data[i].distance[j];
                index=j;
            }
        }
        //劃分簇集:記錄每個簇中樣本的總個數
        //sample.cluster_num[index]儲存的是該類總的樣本的當前總數
        sample.cluster[index][sample.cluster_num[index]++]=i;
    }
    double tem=0;
     //計算樣本誤差和
    for(int i = 0; i < K; i++)
        for(int j = 0; j < sample.cluster_num[i]; j++){
            tem +=pow(all_data[sample.cluster[i][j]].distance[i],2);
        }
    sample.old_data = sample.data;
    sample.data =tem;
}
//重新計算類中心,按照列進行計算
void new_center()
{
    int i, j, n;
    double tmp_sum;


    for(i = 0; i < K; i++)//簇中心
        for(j = 0; j < Length; j++)//向量維數
        {
            tmp_sum = 0;
            for(n = 0; n < sample.cluster_num[i]; n++) //第i個簇的第j維數的所有資料和&&求平均值
            {
                //sample.cluster[i][n]儲存的是樣本的編號
                tmp_sum += all_data[sample.cluster[i][n]].p[j];
            }
            //取平均數得到新的簇中心
            sample.clu_cent[i].p[j] = tmp_sum / sample.cluster_num[i];
        }
}


//輸出實驗結果
void output()
{
    for(int i=0;i<K;i++)
    {
        cout<<"第"<<i<<"類為:"<<endl;
      for(int k=0;k<sample.cluster_num[i];k++)//sample.cluster_num[i]記錄的是該類中,樣本量的個數
      {
          for(int j=0;j<Length;j++)//Length每條樣本長度
          {
            //sample.cluster[i][k]記錄樣本標號
            cout<<all_data[sample.cluster[i][k]].p[j]<<" ";
          }


        cout<<endl;
      }
    }
}
int main()
{
    int iteration;
    double differ = 1;
    int flag = 0;
    rand_data();//生成樣本資料
    input_data();//將樣本輸入讀入記憶體
    Init_center();//初始化類中心


    iteration=0;//迭代標記,T=100,即迭代次數為100,可自己設定
    while(1)
    {
        claculate_distance();    //計算歐幾里德距離
        cluster();               //根據距離聚類


        differ = sample.old_data - sample.data; /* 判斷條件 */
        differ = fabs(differ);
        if(differ<=End||iteration>T)
             break;
        new_center();//重新計算類中心
    }


    output();  /* 聚類後顯示結果 */
    return 0;
}