k-Means算法主要思想:将所有特征对象划分为k个簇,每个簇至少拥有一个对象,每个对象只属于一个簇。
每个簇中的对象之间相似度最高,不同簇之间的相似度最低。
k-Means算法以对象到簇的中心点的距离作为相似度的衡量标准,以准则函数作为聚类质量的衡量标准。
算法所涉及的距离采用欧基米德距离。
算法首先给出一个初始的划分方法,以后通过反复迭代的方法改变划分,使得每一次改进之后的划分方案
都较前一次更好。好的标准就是同一簇中的对象越近越好,而不同簇中的对象越远越好。
算法的准则是最小化所有对象与其参照点(相应簇的中心点)之间的相异度(距离)之和。
#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((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 (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 (i = 0; i < _NUM; i++) //打印出当前每个簇的特征对象 { vector<Tuple> temp = classes[i]; cout<<"类"<<i+1<<":"<<endl; for (j = 0; j < temp.size(); j++) { cout<<temp[j].attr1<<" "<<temp[j].attr2<<endl; } } cout<<"--------------------"<<endl; for (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 (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算法进行聚类分析 }
注:由于能力有限,算法实现可能不尽如人意,另外正确性也有等考量!