k-Means算法主要思想:将所有特征对象划分为k个簇,每个簇至少拥有一个对象,每个对象只属于一个簇。
每个簇中的对象之间相似度最高,不同簇之间的相似度最低。
k-Means算法以对象到簇的中心点的距离作为相似度的衡量标准,以准则函数作为聚类质量的衡量标准。
算法所涉及的距离采用欧基米德距离。
算法首先给出一个初始的划分方法,以后通过反复迭代的方法改变划分,使得每一次改进之后的划分方案
都较前一次更好。好的标准就是同一簇中的对象越近越好,而不同簇中的对象越远越好。
算法的准则是最小化所有对象与其参照点(相应簇的中心点)之间的相异度(距离)之和。
// ceshi.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <iostream>
#include <math.h>
#include <vector>
#define _NUM 3 //预定义划分簇的数目
using namespace std;
/**
特征对象,表示一个元组,一个元组有两个数值属性
**/
struct Tuple
{
int attr1;
int attr2;
};
/**
获取两个特征对象之间的距离,在此以欧基米德距离作为距离度量标准
**/
double getDistXY(Tuple t1, Tuple t2)
{
return sqrt((double)(t1.attr1 - t2.attr1) * (t1.attr1 - t2.attr1) + (t1.attr2 - t2.attr2) * (t1.attr2 - t2.attr2));
}
/**
计算簇的中心点,在此以簇中所有对象的平均距离来计算中心点
**/
Tuple getMeansC(vector<Tuple> c)
{
int num = c.size();
double meansX = 0, meansY = 0;
Tuple t;
for (int i = 0; i < num; i++)
{
meansX += c[i].attr1;
meansY += c[i].attr2;
}
t.attr1 = meansX / num;
t.attr2 = meansY / num;
return t;
}
/**
获取算法的准则函数值,当准则函数收敛时算法停止
**/
double getE(vector<Tuple> classes[], Tuple means[])
{
double sum = 0;
for (int i = 0; i < _NUM; i++)
{
vector<Tuple> v = classes[i];
for (int j = 0; j< v.size(); j++)
{
sum += (v[j].attr1 - means[i].attr1) * (v[j].attr1 - means[i].attr1) + (v[j].attr2 - means[i].attr2) *(v[j].attr2 - means[i].attr2);
}
}
cout<<"sum:"<<sum<<endl;
return sum;
}
/**
对当前的特征对象,查找与其最临近的簇,最临近即到簇中心点的距离最短
**/
int searchMinC(Tuple t, Tuple means[_NUM])
{
int c = 0;
int d = (t.attr1 - means[0].attr1) * (t.attr1 - means[0].attr1) + (t.attr2 - means[0].attr2) * (t.attr2 - means[0].attr2);
for (int i = 1; i < _NUM; i++)
{
int temp = (t.attr1 - means[i].attr1) * (t.attr1 - means[i].attr1) + (t.attr2 - means[i].attr2) * (t.attr2 - means[i].attr2);
if (temp < d)
{
c = i;
d = temp;
}
}
return c;
}
/**
k-Means算法
**/
void kMeans(vector<Tuple> init)
{
vector<Tuple> classes[_NUM]; //定义簇数组,共需划分_NUM个簇
int c;
Tuple means[_NUM]; //定义中心点数组,每个簇对应一个中心点
double newE, oldE = -1; //定义准则函数值
for (int i = 0; i < _NUM; i++) //对每个簇初始赋予一个特征对象
{
cin >> c;
classes[i].push_back(init[c - 1]);
means[i] = getMeansC(classes[i]); //计算当前每个簇的中心点
cout<<"means["<<i<<"]:"<<means[i].attr1<<" "<<means[i].attr2<<endl;
}
newE = getE(classes, means); //计算当前准则函数值
cout<<"newE:"<<newE<<" oldE:"<<oldE<<endl;
for (int i = 0; i < _NUM; i++) //清空每个簇
{
classes[i].clear();
}
while(abs(newE - oldE) >= 1) //当新旧函数值相差不到1即准则函数值不发生明显变化时,算法终止
{
for (int j = 0; j < init.size(); j++) //遍历所有特征对象,将其加入到离它最近的簇
{
int toC = searchMinC(init[j], means);
classes[toC].push_back(init[j]);
}
cout<<"--------------------"<<endl;
for (int i = 0; i < _NUM; i++) //打印出当前每个簇的特征对象
{
vector<Tuple> temp = classes[i];
cout<<"类"<<i+1<<":"<<endl;
for (int j = 0; j < temp.size(); j++)
{
cout<<temp[j].attr1<<" "<<temp[j].attr2<<endl;
}
}
cout<<"--------------------"<<endl;
for (int i = 0; i < _NUM; i++) //更新每个簇的中心点
{
means[i] = getMeansC(classes[i]);
cout<<"means["<<i<<"]:"<<means[i].attr1<<" "<<means[i].attr2<<endl;
}
oldE = newE;
newE = getE(classes, means); //计算新的准则函数值
for (int i = 0; i < _NUM; i++) //清空每个簇
{
classes[i].clear();
}
}
}
/**
程序入口
**/
void main(int args, char * arg[])
{
int n1, n2;
vector<Tuple> init; //保存所有输入的特征对象
while ((cin >> n1 >> n2) && n1 != -1 && n2 != -1) //输入特征对象
{
Tuple p;
p.attr1 = n1;
p.attr2 = n2;
init.push_back(p);
}
kMeans(init); //调用k-Means算法进行聚类分析
}