KMeans简单实现

此文章仅供日后参考,不做任何讲解。 

能看懂就看吧,已经是很简化的代码了。

实现一个简单的KMeans算法,将1000个二维坐标进行聚类,因为KMeans在事先知道集合的数量时是很优秀的,速度快,这次数据有两个集合。

具体步骤:

1.      将1000个数据存在一维数组里(当然是读文件)

2.      随机分配2个簇的中心坐标

3.      开始聚类,具体算法只能意会

4.      写文件,分析

结果如下:

原1000个数据:        聚类后的1000个数据:

     

(当然没显示完)

 

在Excel中模拟的效果:

 

具体怎么模拟呢?很简单:

 1.将数据导入Excel,然后按簇排序


2.插入散点图(此时图中什么也没有),然后右键-》选择数据-》添加:


X轴和Y轴都有:


然后添加一个系列二:


完成后是这样的:


确定后就生成上面的结果。


那么代码是这样的:

 

#include <iostream>
#include <cstdio>
#include <cctype>
#include <time.h>
#include <math.h>
#define DIM 2 //维数 
using namespace std;

void ReadFile(const char*filename,double *data);
void WriteToFile(const char*filename,double *data,int *label,int n);

class KM_Classify{
public:
	KM_Classify(int cluster_num=1,int dim=2,int data_num=0);
	~KM_Classify();
	void Classify(double *data,int *label);
	void InitData(double *data);
	double GetLabel(double *d,int &label);
	double GetDist(const double *d,const double *p);
private:
	int cluster_num;//簇数 
	int dim;	//维数 
	int data_num;//数据量 
	double **point_center;
	//这个二维数组代表什么呢?是每一簇的中心,第一维是簇,第二维是坐标(或维数) 
	double endcon;//结束标志 
};
//类成员函数实现
KM_Classify::KM_Classify(int cluster_num,int dim,int data_num){
	this->cluster_num = cluster_num;
	this->dim = dim;
	this->data_num = data_num; 
	endcon = 0.1;   
	
	point_center = new double*[cluster_num];
	for(int i=0;i<cluster_num;i++){
		point_center[i] = new double[dim];
		memset(point_center[i],0,sizeof(double)*dim);
	}
}
KM_Classify::~KM_Classify(){
	for(int i=0;i<cluster_num;i++){
		delete [] point_center[i];
	}
	delete [] point_center;
}
void KM_Classify::Classify(double *data,int *label){
	InitData(data);
	double *d = new double[dim];
	int lab = -1;
	double last = 0;//上一次距中心的平均值 
	double current = 0;//现在距中心的平均值 
	bool flag = true;
	int unchanged = 0;
	int *count = new int[cluster_num];//记录每一簇元素个数 
	double **mean = new double*[cluster_num];
	for(int i=0;i<cluster_num;i++){
		mean[i] = new double[dim];
	}
	while(flag){
		memset(count,0,sizeof(int)*cluster_num)	;//计数器清零 
		for(int i=0;i<cluster_num;i++){
			memset(mean[i],0,sizeof(double)*dim);
		}
		last = current;
		current = 0;
		//开始聚类
		for(int i=0;i<data_num;i++){
			for(int j=0;j<dim;j++){
				//这样相当于每次取出两个点的x,y坐标存在d数组中 
				d[j] = data[i*dim+j];//因为数据在data数组中是线性存储的 
			}
			current += GetLabel(d,lab);
			count[lab]++;
			for(int j=0;j<dim;j++){
				mean[lab][j] += d[j];
			}
		} 
		current /= data_num;//平均距离
		//重新确定中心
		for(int i=0;i<cluster_num;i++) {
			if(count[i]>0){
				for(int j=0;j<dim;j++){
					mean[i][j] /= count[i];//再次求平均值 
				}
				memcpy(point_center[i],mean[i],sizeof(double)*dim);
			}
		}
		//判断聚合成功的条件
	 	if(fabs(last - current) < endcon){
//	 		flag = false;
			unchanged++;
	 	}
	 	if(unchanged>2){
	 		flag = false;
	 	}
	}
	//输出Label 
	for(int i=0;i<data_num;i++){
		for(int j=0;j<dim;j++){
			d[j] =data[i*dim+j];
		}
		GetLabel(d,lab);
		label[i] = lab;
	}
	delete []count;
	delete []d;
	for(int i=0;i<cluster_num;i++){
		delete []mean[i];
	}
	delete []mean;
}
void KM_Classify::InitData(double *data){
	int every_num = data_num / cluster_num;
	double *temp = new double[dim];
	srand(time(NULL));
	for(int i=0;i<cluster_num;i++){
		int center = every_num*i + (every_num-1)*rand()/RAND_MAX;
		for(int j=0;j<dim;j++){
			temp[j] = data[center*dim+j];
		}
		memcpy(point_center[i],temp,sizeof(double)*dim);
	}
	delete []temp;
}
//确定元素的标签,同时返回距离 
double KM_Classify::GetLabel(double *d,int &label){
	double dist = -1;
	for(int i=0;i<cluster_num;i++){
		double temp = GetDist(d,point_center[i]);
		if(temp<dist || -1==dist){
			dist = temp;
			label = i;//确定在哪簇 
		}
	}
	return dist;
}
//计算d这个点到该簇中心的距离(欧几里得距离)
double KM_Classify::GetDist(const double *d,const double *p){
	double dist = 0.0;
	for(int i=0;i<dim;i++){
		dist += (p[i]-d[i])*(p[i]-d[i]);
	}
	return sqrt(dist);
}

int main(){
	int k = 1,n = 0;
	cout<<"输入堆数,数据量:";
	cin>>k>>n;
	double *data = new double[n*2];
	int *label = new int[n];
	KM_Classify *kmc = new KM_Classify(k,DIM,n);
	ReadFile("E://test.txt",data);	
	kmc->Classify(data,label);
/*	for(int i=0;i<n;i++){
		//cout<<data[i]<<" "<<endl; 
		cout<<label[i]<<endl; 
	}*/
	WriteToFile("E://res.txt",data,label,n);
	delete []data;
	delete []label;
	cout<<"聚类成功"<<endl; 
	return 0;
}

void ReadFile(const char*filename,double *data){
	FILE *fp;
	if((fp = fopen(filename,"r"))==NULL){
		cout<<"Cannot Open File"<<endl; 
		exit(0);
	}
	int i=0;
	double d;
	char ch;
	while(!feof(fp)){
		ch = fgetc(fp);
		if('-'==ch||isdigit(ch)){
		//	 cout<<ch<<" ";
			fseek(fp,-1,SEEK_CUR);
			fscanf(fp,"%lf",&d);
		//	cout<<data[i]<<endl;
			data[i++] = d;
		}
	}
	fclose(fp); 
}
void WriteToFile(const char*filename,double *data,int *label,int n){
	FILE *fp;
	if((fp = fopen(filename,"w"))==NULL){
		cout<<"Cannot Open File"<<endl; 
		exit(0);
	}
	for(int i=0;i<n;i++){
		for(int j=0;j<DIM;j++){
			fprintf(fp,"%lg\t",data[i*DIM+j]);
		}
		fprintf(fp,"%d\n",label[i]);
	}
}

运行结果没什么可说的,就在E盘山生成了聚好类的文件,找到了相应的标签。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值