K-means
算法的工作原理:
算法首先随机从数据集中选取 K个点作为初始聚类中心,然后计算各个样本到聚类中的距离,把样本归到离它最近的那个聚类中心所在的类。计算新形成的每一个聚类的数据对象的平均值来得到新的聚类中心,如果相邻两次的聚类中心没有任何变化,说明样本调整结束,聚类准则函数已经收敛。本算法的一个特点是在每次迭代中都要考察每个样本的分类是否正确。若不正确,就要调整,在全部样本调整完后,再修改聚类中心,进入下一次迭代。如果在一次迭代算法中,所有的样本被正确分类,则不会有调整,聚类中心也不会有任何变化,这标志着已经收敛,因此算法结束。
算法描述如下:
算法:K-means。划分的K-means 算法基于类中对象的平均值。
输入:类的数目K和包含N个对象的数据库。
方法:
① 对于数据对象集,任意选取K个对象作为初始的类中心;
② 根据类中对象的平均值,将每个对象重新赋给最相似的类;
③ 更新类的平均值,即计算每个类中对象的平均值;
④ Repeat ②③;
⑤ 直到不再发生变化。
代码如下:
#include<iostream>
#include<fstream>
#include<cmath>
#include<vector>
using namespace std;
struct Mode
{
float x1;
float x2;
};
typedef vector<vector<Mode> > ModeVec;
#define N 4
int Minindex(vector<float> d);
float GetDistance(Mode d1,Mode d2);
Mode Core(vector<Mode> p);
bool IsEqual(Mode p[],Mode q[],int n);
float Clustering(Mode p[],int Number);
//聚类函数
float Clustering(Mode p[],int Number)
{
int i;
ModeVec groupM;
vector<float> Dis;
for(i=0;i<Number;i++)
{
vector<Mode>p1;
groupM.push_back(p1);
}
Mode c[N];
Mode nc[N];
for(i=0;i<Number;i++)
{
c[i]=p[i];
nc[i]=p[i];
}
for(i=0;i<20;i++)
{
int j;
Dis.clear();
for(j=0;j<Number;j++)
Dis.push_back(GetDistance(p[i],c[j]));
int k=Minindex(Dis);
groupM.at(k).push_back(p[i]);
}
for(i=0;i<Number;i++)
{
c[i]=Core(groupM.at(i));
}
int flag=1;
while(flag)
{
for(i=0;i<Number;i++)
{
groupM.at(i).clear();
}
for(i=0;i<20;i++)
{
int j;
Dis.clear();
for(j=0;j<Number;j++)
Dis.push_back(GetDistance(p[i],c[j]));
int k=Minindex(Dis);
groupM.at(k).push_back(p[i]);
}
for(i=0;i<Number;i++)
{
nc[i]=Core(groupM.at(i));
}
if(IsEqual(c,nc,Number))
{
flag=0;
}
else
{
for(i=0;i<Number;i++)
{
c[i]=nc[i];
}
}
}
vector<Mode>::iterator it;
float sum=0;
cout<<"*******************分类数为"<<Number<<"******************:"<<endl;
for(i=0;i<Number;i++)
{
cout<<"中心点"<<i+1<<":"<<c[i].x1<<" "<<c[i].x2<<endl;
cout<<i+1<<"类:"<<endl;
for (it = groupM.at(i).begin(); it != groupM.at(i).end(); ++it )
{
cout<<(*it).x1<<" "<<(*it).x2<<endl;
sum+=GetDistance((*it),c[i]);
}
}
return sum;
}
//返回欧式距离
float GetDistance(Mode m1,Mode m2)
{
return sqrt((m1.x1-m2.x1)*(m1.x1-m2.x1)+(m1.x2-m2.x2)*(m1.x2-m2.x2));
}
//返回每个聚类的中心
Mode Core(vector<Mode> m)
{
Mode core;
float sum1=0,sum2=0;
vector<Mode>::iterator it;
for ( it = m.begin(); it != m.end(); ++it )
{
sum1+=(*it).x1;
sum2+=(*it).x2;
}
core.x1=sum1/m.size();
core.x2=sum2/m.size();
return core;
}
//返回属于哪个分类的索引
int Minindex(vector<float> d)
{
float min=d.at(0);
int i;
int index=0;
for(i=0;i<d.size();i++)
{
if(d.at(i)<min)
{
min=d.at(i);
index=i;
}
}
return index;
}
//判断p,q前n个元素是否相等
bool IsEqual(Mode p[],Mode q[],int n)
{
int i;
for(i=0;i<n;i++)
{
if(p[i].x1!=q[i].x1||p[i].x2!=q[i].x2)
return false;
}
return true;
}
//主函数
void main()
{
Mode p[24];
ifstream fin("in.txt");
ofstream fout("out.txt");
int i;
for(i=0;i<24;i++)
{
fin>>p[i].x1>>p[i].x2;
}
for(i=1;i<N;i++)
{
fout<<i<<" "<<Clustering(p,i)<<endl;
}
}
其中in.txt是输入的样本数据,示例
0.537667139546100 -0.433592022305684
1.833885014595087 0.342624466538650
-2.258846861003648 3.578396939725761
0.862173320368121 2.769437029884877
0.318765239858981 -1.349886940156521
-1.307688296305273 3.034923466331855
4.725404224946106 1.409034489800479
3.936945126810344 1.417192413429614
4.714742903826096 0.671497133608081
3.795033941700226 -1.207486922685038
3.875855651783688 0.717238651328838
5.489697607785465 1.630235289164729
0.488893770311789 4.888395631757642
1.034693009917860 2.852929893030850
0.726885133383238 2.931129541831968
-0.303440924786016 3.190501305575125
0.293871467096658 1.055715838005104
-0.787282803758638 5.438380292815099
5.093265669039484 3.993150671896652
5.109273297614397 5.532630308284750
3.136347178011286 3.230334086246318
4.077359091130425 4.371378812760058
2.785882956384591 3.774415597728748
2.886499258513235 5.117356138814467
out.txt则是不同的分类数目对应着J值(即在每个分类中,每个分类的点与这个类的类心得距离的和),可用于绘制JC曲线。
对应着上面的示例:
1 58.2769
2 37.5225
3 28.7359
可以看出,分类数目越多,J值越小,J-C曲线是个递减曲线。