/*
K-Means思想: 将n个样本分成k个聚类,每个聚类里的样本关联性(或者说是相似性)比较高。
举个例子,假如有5个样本,每个样本是一个2维向量,分别记做A,B,C,D,E,我要将他们分成2个聚类,第一步是随机选2个样本(也可以是虚拟的)把它们当做中心点,然后将
A,B,C,D,E归类到距离最小的那个中心点,之后再对每个聚类进行调整中心点,假如现在A,B,C是一个聚类,D,E是一个聚类,调整的方式的话,以A,B,C这个聚类为例,
那么中心点的第一维取A,B,C的第一维的平均值,第二维也是取平均值。于是得到了新的聚类中心点,一直迭代下去,直到所有的中心点不发生变化。
*/
#include<iostream>
#include<fstream>
#include<cmath>
#include<cstdlib>
#include<ctime>
using namespace std;
struct Vector //数据向量
{
double *coords;
int size;
Vector():coords(NULL),size(0){}
Vector(int d){ create(d); }
void create(int d)
{
size=d;
coords=new double[size];
for(int i=0;i<size;i++) coords[i]=0.0;
}
void copy(const Vector& other)
{
if(size==0) create(other.size);
for(int i=0;i<size;i++) coords[i]=other.coords[i];
}
void add(const Vector& other)
{
for(int i=0;i<size;i++) coords[i]+=other.coords[i];
}
~Vector()
{
if(coords) delete[] coords;
size=0;
}
};
struct Cluster //聚类结构
{
Vector center; //中心
int *member; //各个数据的索引
int memberNum; //成员个数
};
class KMeans
{
private:
int num; //数据个数
int dimen; //维度数
int clusterNum; //聚类数
Vector *observations; //数组
Cluster *clusters; //聚类数组
int passNum; //迭代次数
public:
KMeans(int n,int d,int k,Vector *ob)
:num(n),dimen(d),clusterNum(k),observations(ob),
clusters(new Cluster[k]){
for(int x=0;x<clusterNum;x++)
clusters[x].member=new int[n];
}
~KMeans(){
for(int k=0;k<clusterNum;k++) delete []clusters[k].member;
delete []clusters;
}
void initClusters(){
for(int i=0;i<clusterNum;i++){
clusters[i].member[0]=i;
clusters[i].center.copy(observations[i]); //该数据作为数据中心
}
}
void run(){
bool converged=false; //用于判断是否收敛
passNum=0; //迭代次数
while(!converged&&passNum<999){
distribute(); //将数据分配到聚中心最近的聚类。
converged=recalculateCenters();
//计算新的聚类中心,并确认是否已经收敛
passNum++;
}
}
void distribute(){
for(int k=0;k<clusterNum;k++) getCluster(k).memberNum=0; //清零
for(int i=0;i<num;i++){
Cluster& cluster=getCluster(closestCluster(i));
//找到最接近的其中心的聚类
int memID=cluster.memberNum; //把数据i添加到该聚类中
cluster.member[memID]=i;
cluster.memberNum++;
}
}
int closestCluster(int id){
int clusterID=0; //假定索引为id的数据最接近第一个聚类
double minDist=eucNorm(id,0); //计算到第一个聚类中心的误差(用距离的平方和作为误差)
for(int k=1;k<clusterNum;k++)
{
double d=eucNorm(id,k);
if(d<minDist){ //更新最小值
minDist=d;
clusterID=k;
}
}
return clusterID;
}
double eucNorm(int id,int k){
Vector& observ=observations[id];
Vector& center=clusters[k].center;
double sumOfSquare=0;
for(int d=0;d<dimen;d++){
double dist=observ.coords[d]-center.coords[d];
sumOfSquare+=dist*dist;
}
return sumOfSquare;
}
bool recalculateCenters(){ //重新计算聚类中心
bool converged=true;
for(int k=0;k<clusterNum;k++){
Cluster& cluster=getCluster(k);
Vector average(dimen);
for(int m=0;m<cluster.memberNum;m++)
average.add(observations[cluster.member[m]]);
for(int d=0;d<dimen;d++){
average.coords[d]/=cluster.memberNum;
if(average.coords[d]!=cluster.center.coords[d]){ //如果和原来的聚类中心不同表示没有收敛
converged=false; //置为false
cluster.center.coords[d]=average.coords[d]; //用平均值作为的新的聚类中心
}
}
}
return converged;
}
//获得第id个聚类
Cluster& getCluster(int id){
return clusters[id];
}
};
//打印一个数据
void printVector(ostream& output,const Vector& v){
for(int i=0;i<v.size;i++){
if(i!=0) output<<",";
output<<v.coords[i];
}
}
void partitionObservations(istream& input){
int n,dimen,k;//n个数据,dimen维,k个簇
input>>n>>dimen>>k; //输入
Vector *obs=new Vector[n]; //n个向量点
for(int i=0;i<n;i++){
obs[i].create(dimen); //初始化
for(int d=0;d<dimen;d++) input>>obs[i].coords[d];
}
//创建KMeans实例
KMeans kmeans(n,dimen,k,obs);
kmeans.initClusters();
kmeans.run();
ostream& output=cout;
for(int c=0;c<k;c++){ //输出k个簇
Cluster& cluster=kmeans.getCluster(c);
output<<"----第"<<(c+1)<<"个聚类----
";
output<<"聚类中心:";
printVector(output,cluster.center);
for(int m=0;m<cluster.memberNum;m++){
int id=cluster.member[m]; //存储的都是id
printVector(output,obs[id]);
output<<"
";
}
output<<endl;
}
delete[] obs;
}
int main(){
/*
const char *fileName="observations.txt";
ifstream obIn(fileName);
if(obIn.is_open()) partitionObservations(obIn);
else cout<<"open"<<fileName<<" is fail!"<<endl;
*/
return 0;
}