Kmeans(C/C++实现,以足球队聚类为例)

在这里插入图片描述
打入预选赛十强赛赋予40,预选赛小组未出线的赋予50。对于亚洲杯,前四名取其排名,八强赋予5,十六强赋予9,预选赛没出线的赋予17。
首先需要做数据预处理:
归一化(Normalization):是为了将数据映射到0~1之间,去掉量纲的过程,让计算更加合理,不会因为量纲问题导致1米与100mm产生不同。
在这里插入图片描述
在这里插入图片描述
有了数据后,就可以开始着手写代码了:

#include<stdio.h>
#include<string.h>
#include<math.h>
#include<stdlib.h>

/*
k-means算法描述(聚类-无监督学习):
k-means算法的具体步骤:
1)给定大小为n的数据集,令I=1,选取k个初始聚类中心 

                Zj(I),j=1,2,3,…,k;
2)计算每个数据对象与聚类中心的距离D(xi,Zj(I)),
             i=1,2,3…n,j=l,2,3,…,k,
如果满足
D(xi,Zk(I)) =min{D(xi,Zj(I)),
                           i=l,2,3,…n}      
则 xi∈C k;

3) 计算k个新的聚类中心:
即取聚类中所有元素各自维度的算术平均数;

4) 判断:若Zj(I+1)≠Zj(I),
                    j=l,2,3,…,k,则I=I+1,
      返回(2);否则算法结束。
*/
//本次数据采用三维数据,可以理解为立体空间的XYZ坐标进行聚类    我们选择的数据是判断中国男足在亚洲处于几流水平
typedef struct Teams{
    double X;
    double Y;
    double Z;
}team,*Team;  //typedef毕竟是条语句,所以这里要加;
//结构体初始化好像不能使用指针去初始化(因为指针需要指向一个东西(不然叫野指针),注意下面函数必须要加引用,否则函数域并不能改变其值,因为C语言本身是值传参而不是引用传参;
void iniTeams(team& t,double x,double y,double z){
    t.X=x;
    t.Y=y;
    t.Z=z;
}
void printfTeams(Team t){
    printf("其X值为%f\n",t->X);
    printf("其Y值为%f\n",t->Y);
    printf("其Z值为%f\n",t->Z);  
}
//首先需要定义一个计算欧几里得距离的函数 参数为结构体指针
double ED(Team team1,Team team2){
        return sqrt(pow((team1->X-team2->X),2)+pow((team1->Y-team2->Y),2)+pow((team1->Z-team2->Z),2));
}

int belongs(team t,team teams[]){
    double d[3]={0};
    double min=65535; //用double接收赋值double
    int position=0;
    for(int i=0;i<3;i++){
        d[i]=ED(&t,&teams[i]); //这里写&t和参数写Team t哪个可能更好呢?
        // printf("%f\n",d[i]);
        if(d[i]<min){
            min=d[i];
            position=i; //存储下标
        }
    }
    return position;
}
team average(team a[],int numlen){
  team t;
  double averageX=0;
  double averageY=0;
  double averageZ=0;
  for(int i=0;i<numlen;i++){
      averageX+=(a[i].X/(double)numlen);
      averageY+=(a[i].Y/(double)numlen);
      averageZ+=(a[i].Z/(double)numlen);
  }
  t.X=averageX;t.Y=averageY;t.Z=averageZ;
  return t;
}
//team*不等于Team Team=&team
team* CaclulateC(team teams[],int a[],int count1,int b[],int count2,int c[],int count3){  //参数为分别在三个簇的队在teams中的下标数组
    team *newC=(team*)malloc(sizeof(team)*3);   //计算算术平均 直接搞指针写法,因为数组会随着函数的生命周期结束而结束,而指针不会 这里需要数组写法防止野指针
    team A[count1]={0}; team B[count2]={0}; team C[count3]={0}; //一定要注意变量名的命名重复问题  熟记语法很重要
    for(int i=0;i<count1;i++){
        A[i]=teams[a[i]];
    }
    newC[0]=average(A,count1);
    for(int i=0;i<count2;i++){
        B[i]=teams[b[i]];
    }
    newC[1]=average(B,count2);
    for(int i=0;i<count3;i++){
        C[i]=teams[c[i]];
    }
    newC[2]=average(C,count3);
     //分别计算三个簇中心
    // team* newc=newC; //防止warning 返回赋值指针而不是数组首地址(数组名)
    return newC;                   
}

//自定义team元素等价函数
bool teamequal(team t1,team t2){
    if(t1.X==t2.X and t1.Y==t2.Y and t1.Z==t2.Z){
        return true;
    }
    return false;
}
int main(){
    const char* name[15]={"中国","日本","韩国","伊朗","沙特","伊拉克","卡塔尔","阿联酋","乌兹别克斯坦","泰国","越南","阿曼","巴林","朝鲜","印尼"};
    double data[15][3]={
        1,1,0.5,
        0.3,0,0.19,
        0,0.15,0.13,
        0.24,0.76,0.25,
        0.3,0.76,0.06,
        1,1,0,
        1,0.76,0.5,
        1,0.76,0.5,
        0.7,0.76,0.25,
        1,1,0.5,
        1,1,0.25,
        1,1,0.5,
        0.7,0.76,0.5,
        0.7,0.68,1,
        1,1,0.5
    };
    //初始化15个队伍
    team teams[15]={0};//用数组来创建多个变量
    // Team Teams[15]={0}; //定义指针可以略过,反正C语言值传参,直接传&teams[i]即可
    for(int i=0;i<15;i++){ //每一个单独循环一直用i是可以的,但是多个循环嵌套就不能一直用i,因为内层循环依然处于外层循环的函数域内
        iniTeams(teams[i],data[i][0],data[i][1],data[i][2]);
    }
    // for(int j=0;j<15;j++){
    //     Teams[j]=&teams[j];
    // }
    //选择k=3个聚类中心,这里选择和PPT相同,日本、巴林和泰国的值:A:{0.3, 0, 0.19},B:{0.7, 0.76, 0.5}和C:{1, 1, 0.5}。  C语言没有%C
    // team C[3]={0};
    // iniTeams(C[0],0.3,0,0.19);iniTeams(C[1],0.7,0.76,0.5);iniTeams(C[2],1,1,0.5);
    // printf("中国属于%c类",'A'+belongs(&teams[0],C));
    // exit(0);
    team C[3]={0};
    //初始化聚类中心
    iniTeams(C[0],0.3,0,0.19);iniTeams(C[1],0.7,0.76,0.5);iniTeams(C[2],1,1,0.5);
    // for(int i=0;i<15;i++){
    //     printf("%d",belongs(teams[i],C));
    // }
    int Count1=0;int Count2=0;int Count3=0;
    int *a=nullptr; int *b=nullptr; int *c=nullptr;
    // int A[15]={0}; int B[15]={0}; int C[15]={0};
    while(true){
        //建立k=3个簇-用数组(取maxsize=15)表示存储它们在teams中的下标 很多东西用malloc申请空间的机制都更加灵活 数组没有指针灵活对于C语言来说
        a=(int*)malloc(sizeof(int)*15);  b=(int*)malloc(sizeof(int)*15);  c=(int*)malloc(sizeof(int)*15); int j=0,k=0,l=0;
        int count1=0,count2=0,count3=0;//存储每个簇中目前元素的数量,方便计算 注意连续定义多个变量不能写int count1,count2,count3=0;
        //划分15个队所属的簇
        for(int i=0;i<15;i++){
                if(belongs(teams[i],C)==0){
                    a[j++]=i;
                    count1++;
                }
                if(belongs(teams[i],C)==1){
                    b[k++]=i;
                    count2++;
                }
                if(belongs(teams[i],C)==2){
                    c[l++]=i;
                    count3++;
                }
        }
        //重新计算聚类中心
        team* newC=CaclulateC(teams,a,count1,b,count2,c,count3);
        //保存聚类结果供打印
        Count1=count1;Count2=count2;Count3=count3;
        
        //判断结束条件
        if(teamequal(newC[0],C[0]) and teamequal(newC[1],C[1]) and teamequal(newC[2],C[2])){
            break;
        }
        //更新聚类中心
        for(int i=0;i<3;i++){
            C[i].X=newC[i].X;
            C[i].Y=newC[i].Y;
            C[i].Z=newC[i].Z;
        }
        free(a); free(b); free(c);
    }
    printf("经过聚类后:\n");
    //输出各个球队所属于的类
    printf("亚洲一流队伍有%d支分别是 ",Count1);
    for(int i=0;i<Count1;i++){
        printf("%s ",name[a[i]]);
    } 
    printf("\n");
    printf("亚洲二流队伍有%d支分别是 ",Count2);
    for(int i=0;i<Count2;i++){
        printf("%s ",name[b[i]]);
    }
    printf("\n");
    printf("亚洲三流队伍有%d支分别是 ",Count3);
     for(int i=0;i<Count3;i++){
        printf("%s ",name[c[i]]);
    }
    printf("\n");
    // printf("%f",C[0].X);
    // // printfTeams(Teams[0]);
    // // printf("中国与某某队的欧式距离为:%f",ED(Teams[0],Teams[1]));
    return 0;
}


  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值