- 源代码链接:
- 模式识别课程设计1源代码资源-CSDN文库
- 问题描述
根据题意,使用上课所教的K——均值法实现对给定数据的聚类分析。
- 算法介绍
根据题目要求,限定算法使用为K——均值法。
预先的条件及约定:
设待分类的模式特征矢量集为{x1,x2,…,xN};
类的数目K是事先取定的。
算法基本思想:
首先任意选取K个聚类中心,按最小距离原则将各模式分配到K类的某一类;
不断计算聚类中心和调整各模式的类别,最终使各模式到其判属类别中心的距离平方之和最小。
基于使聚类准则函数最小化
准则函数:聚类集中每一样本点到该类中心的距离平方和。
对于第j个聚类集,准则函数定义为
Sj:第j个聚类集(域),聚类中心为Zj ;
Nj:第j个聚类集Sj中所包含的样本个数。
对所有K个模式类有
K-均值算法的聚类准则:聚类中心的选择应使准则函数J极小,即使Jj的值极小。
对于某一个聚类 j,
即
上式表明,Sj类的聚类中心应选为该类样本的均值。
算法步骤:
(1)任选K个模式特征矢量作为初始聚类中心:z1(1) ,z2(1) ,…zK(1)。括号内的序号表示迭代次数。
(2)将待分类的模式特征矢量集{x}中的模式逐个按最小距离原则分划给K类中的某一类。
如果 Dj(k) =min{||x-zi(k)||}, i=1,2,…,K
则判 x∈Sj(k)
(4)如果zj(k+1)=zj(k)(j=1,2,…K),则结束;否则,k=k+1,转(2)
- 计算过程
因为题目没有提前预设K值,所以我采用了让用户输入自己所需K值的方式。
为了是归类能够更加完善,所以我每一次的过程所提前选取的Zi都是不一样的,这样的话可以使结果更加完善。
为了便于计算,我设立了一个结构体xly。
具体代码如下:
#include<iostream>
#include<cmath>
using namespace std;
struct xly
{
double a;
double b;
int s;
double* d;
};
int Ksuanfa(int n, xly x[], xly Z[], xly ZX[], int k)
{
if (n <= 0)
return -1;
int i, j, t;
int* NN = new int[k + 1];
for (i = 1; i <= k; i++)
NN[i] = 0;
double min[11];
for (i = 1; i <= 10; i++)
{
for (j = 1; j <= k; j++)
{
x[i].d[j] = sqrt((x[i].a - Z[j].a) * (x[i].a - Z[j].a) + (x[i].b - Z[j].b) * (x[i].b - Z[j].b));
}
}
for (i = 1; i <= 10; i++)
{
min[i] = x[i].d[1];
x[i].s = 1;
for (j = 1; j <= k; j++)
{
if (min[i] > x[i].d[j])
{
min[j] = x[i].d[j];
x[i].s = j;
}
}
}
for (i = 1; i <= 10; i++)
{
NN[x[i].s]++;
}
for (i = 1; i <= k; i++)
{
ZX[i].a = 0;
ZX[i].b = 0;
}
for (i = 1; i <= 10; i++)
{
ZX[x[i].s].a += x[i].a;
ZX[x[i].s].b += x[i].b;
}
for (i = 1; i <= k; i++)
{
ZX[i].a = ZX[i].a / NN[i];
ZX[i].b = ZX[i].b / NN[i];
}
for (t = 1; t <= k; t++)
{
if (Z[t].a != ZX[t].a)
break;
else
{
if (Z[t].b != ZX[t].b)
break;
}
}
if (t > k)
return 1;
else
{
for (i = 1; i <= k; i++)
{
Z[i].a = ZX[i].a;
Z[i].b = ZX[i].b;
}
return Ksuanfa(n - 1, x, Z, ZX, k);
}
}
int main()
{
zcy x[11], Z[11], ZX[11];
x[1].a = 0;
x[1].b = 0;
x[2].a = 3;
x[2].b = 8;
x[3].a = 2;
x[3].b = 2;
x[4].a = 1;
x[4].b = 1;
x[5].a = 5;
x[5].b = 3;
x[6].a = 4;
x[6].b = 8;
x[7].a = 6;
x[7].b = 3;
x[8].a = 5;
x[8].b = 4;
x[9].a = 6;
x[9].b = 4;
x[10].a = 7;
x[10].b = 5;
int k, i, j,m=0;
char w;
do {
cout << "请输入你所预先设置的K值。(注意,K应该是不大于10的正整数)" << endl;
cin >> k;
if (k > 10 || k < 1)
cout << "请正确输入K值。" << endl;
for (i = 1; i <= 10; i++)
x[i].d = new double[k + 1];
int n = 150;
for (i = 1; i <= k; i++)
{
Z[i].a = x[(i+m)%10+1].a;
Z[i].b = x[(i+m)%10+1].b;
Z[i].s = i;
}
if (Ksuanfa(n, x, Z, ZX, k) > 0)
{
cout << "最后的聚类中心分别为:" << endl;
for (i = 1; i <= k; i++)
cout << '(' << Z[i].a << ',' << Z[i].b << ')' << endl;
cout << "每一个类的点为:" << endl;
for (i = 1; i <= k; i++)
{
cout << "第" << i << "类:";
for (j = 1; j <= 10; j++)
{
if (x[j].s == i)
cout << j << '\t';
}
cout << endl;
}
}
else
{
cout << "该结果不收敛,无法分类!" << endl;
}
cout << "如果你不满意分组数或者不满意分组,请您输入y重新开始,如果您满意这一次的分组情况,请您输入n结束程序。";
cin >> w;
m++;
} while (w == 'y');
return 0;
}
- 结果分析讨论
首先证明我的结果可以使用于任意K值,以下选取K为2,4,6,8正常值以及19,-1两个异常值的结果:
接着分析在K值为2的时候,预选的Zi值不同,得到的结果也不同:
这也证明了K——均值法的局限性:聚类结果依赖于预选值。
- 实验感悟
在最开始的时候,因为所给数据的缘由,所以我给自己设立的结构体zcy中的两个坐标设置的类型均为int,但是在这样的情况下,得到的结果十分不精确,如图所示:
仅仅能够大概反映聚类情况,但是有关聚类中心的求解就十分的不精确了,为了更加精确求解,因此将变量类型由int改为了double。
为了更加的方便用户分析我加入了循环,使得用户可以按照自己的意愿决定是否终结程序,实现了交互功能,如图所示:
通过本次课程设计,我对K——均值法有了更深刻的理解,同时也进一步增强了我的C++编程能力,收获满满。