强算KMeans聚类算法演示器

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了强算KMeans聚类算法演示器相关的知识,希望对你有一定的参考价值。

这些天做C#实验以及这个KMeans算法演示器,学了一下openGL,感觉有待加强。

//Point.h
/*
Point 结构体定义及实现
结构体重载了2个运算符:
1.==		//推断两个Point的坐标值是否相等
2.<<		//用于显示(以友元函数的方式重载)
*/
#ifndef Point_h_
#define Point_h_
#include <iostream>
#include <string>
#include <iomanip>
using namespace std;
const int mWidth=3;      //显示时每一个字符宽度 
//存放点坐标的结构 
struct Point{  
    string name;	//点名称  
    double x;      //x轴坐标    
    double y;      //y轴坐标  

	//默认的结构体构造器
    Point()
		:x(-999),y(-999){  
    }  
    Point(double xx,double yy,string n)
		:x(xx),y(yy),name(n){  
    }  
	//复制构造函数
    Point(const Point &p)
	:x(p.x),y(p.y),name(p.name){  
    }  
	//赋值复制函数
    Point operator=(const Point &p){  
        if(this==&p)  
            return *this;  
        x=p.x;  
        y=p.y;  
        name=p.name;  
        return *this;  
    }  
    //推断两个Point坐标值是否相等
    bool operator==(const Point &point)const{  
        return x==point.x&&y==point.y;  
    }  
    //重载<< 
    friend ostream& operator<<(ostream &os,const Point &p){  
        os<<setw(mWidth)<<right<<p.name<<
			"("<<setw(mWidth)<<left<<p.x
			<<","<<setw(mWidth)<<p.y<<")"<<"   ";  
		return os;
    }  
};   
#endif 

functions.h主要是一些函数

//functions.h
#ifndef functions_h_
#define functions_h_
#include <iostream>  
#include <cstdlib>  
#include <ctime>  
#include <vector>  
#include <iomanip>  
#include <string>  
#include <sstream>
#include <vector>
#include <iterator>
#include <algorithm>
#include "Point.h"
#include <ctime>
#include <windows.h>
using namespace std;  

const int MAX=20;        //聚类点数  
const int M_GROUP=3;     //簇数  
const int LIMIT=20; //同意聚类的最大次数  
const int X_LIMIT=15;    //X轴最大坐标  
const int Y_LIMIT=15;    //Y----  
//数字转字符串
string numberToString(int i){
	stringstream s;
	s<<i;
	return s.str();
};
//delay(n) 延时n秒
void delay(double sec)
{
	time_t start_time, cur_time; // 变量声明
	time(&start_time);
	do {
		time(&cur_time);
		}while((cur_time - start_time) < sec );
};


//生成随机点
//size 生成随机点的个数
bool randPoint(vector<Point> &vp,int size){
	vp.clear();
	 srand(time(0));
	int i=0;  
    //生成随机点  
    while(i<size){  
        int x=rand()%X_LIMIT;  
        int y=rand()%Y_LIMIT;
		string name="p";
		string num=numberToString(i+1);
		name+=num;
		//增加到数组中
        vp.push_back(Point(x,y,name));  
        i++;  
    }  
	if(i==size)
		return true;
	else
		return false;
};
//输出单个坐标
static int countTimes=0;//用于输出格式控制
void outPoint(Point &p){
	cout<<p;
	countTimes++;
	if(countTimes%5==0)
		cout<<endl;
};

//输出数组中全部点
//展示全部点的函数
void display(vector<Point> &vp){  
	countTimes=0;
	for_each(vp.begin(),vp.end(),outPoint);
};
//清空流内容  
void eatLine(){  
    while(cin.get()!='\n')  
        continue;  
};  
//选择起始中心点输入
//center 存储中心点的数组
//vp 全部点
bool inputCenter(vector<Point> ¢er,vector<Point> &vp){
	//可分簇的最大数目
	int vpSize=vp.size();
	//清空center中内容
	center.clear();
	cout<<"\n请输入分簇的数目:0--"<<vpSize<<endl;
	int group;
	cin>>group;
	while(group<=0||group>vpSize){
		cout<<"输入有误!"<<endl;
		cout<<"\n请输入分簇的数目:0--"<<vpSize<<endl;
		cin>>group;
	}
	//选择起始中心点
	int j=0;
    while(j<group){  
		int locate;   
		cout<<"请选择"<<group<<"个坐标点作为起始点,输入1代表p1:"<<endl;  
		cin>>locate;
		if(locate>0&&locate<=vpSize){
			Point temp=vp[locate-1]; 
			cout<<"已经成功选择了"<<j+1<<"个起始点!"<<temp;  
			center.push_back(temp);     
			if(j!=group-1)  
				cout<<"请继续完毕剩余选择:"<<endl;  
			else{  
				cout<<"\n选择完毕!选择的中心点为:"<<endl;
				display(center);
				return true;
			}
			j++;  
		}else{  
			cout<<"选择有误!"<<"请又一次输入正确的值:"
				<<1<<"--"<<vpSize<<":"<<endl;  
		}  
			eatLine();//清空流  
	  }  
	return false;
};
#endif

//kmeans.h
#ifndef kmeans_h_
#define kmeans_h_
/* 
@author:天下无双 
@date:2014-6-5 
@version:9.0 
聚类算法K-means实现:採用强算算法 
随机生成20个点,然后进行分成三个聚类 
change:
坐标点改为double类型
//已经完毕聚类算法
//弃用指针,所有使用vector<Point>取代
//界面版openGL
*/  
#include "functions.h"
#include "openglFunc.h"
#include "Point.h"
#include <vector>
#include <cmath>
//參数为一维数组,数组大小,簇大小,选择的初始点  
//返回值为聚类进行次数  
//推断两次中心是否相等
bool isEqual(vector<Point> &lhs,vector<Point> &rhs){
	int size=rhs.size();
	for(int i=0;i<size;i++){
		if(lhs[i]==rhs[i])
			continue;
		else
			return false;
	}
	return true;
};
//计算中心点
//当size为0时,返回一个(-999,-999)表示没有元素
Point calCenter(vector<Point> &arr){
	int size=arr.size();
	if(size!=0){
		double xSum=0;
		double ySum=0;
		for(int i=0;i<size;i++){
			xSum+=arr[i].x;//注意优先级
			ySum+=arr[i].y;
	}
	double x=xSum/size;
	double y=ySum/size;
	return Point(x,y,"center");
  }else
	  return Point(-999,-999,"中心点反复,该中心没有点");
};
//计算两个点之间的距离
double pointToPoint(const Point &lhs,const Point &rhs){
	double xToX=abs(lhs.x-rhs.x);
	double yToY=abs(lhs.y-rhs.y);
	double sum=pow(xToX,2)+pow(yToY,2);
	double f=sqrt(sum);
	return f;
};

//kmeans
//vp  点数组
//center 起始中心点数组
int kMeans(vector<Point> &vp,vector<Point> ¢er){  
    vector<Point> first;//   记录聚类上一次的中心  
    vector<Point> second;    //记录这一次聚类的中心      
	vector<vector<Point>> group;//存放簇
/*
center和group的关系
下标相应	0	1	2	3	4
center		0	1	2	3	4
group		00	01	02	03	04	
			10	11	12	13	14
			20	21	22	23	24
			..	..	..	..	..
*/
	int centerSize=center.size();
	int vpSize=vp.size();
	//先复制起始点到第一次聚类中心
    for(int i=0;i<centerSize;i++)
		first.push_back(center[i]);
	cout<<"\n选择的起始中心点为:"<<endl;
	display(first);
	cout<<"图中标记为红色的为中心点:"<<endl;
	//表明第一次选择的中心点
	paintCenterPoint(first);
	//number  聚类进行的次数
	int number=0;
	//color用于显示点时自己主动选择颜色
	int color=0;
	//第一次选择的中心点不应该被擦除
	bool flag=true;
	do{
	//先置group拥有相应的数组
	group.clear();
	for(int i=0;i<centerSize;i++){
		vector<Point> p;
		group.push_back(p);
	}
	//将每一个点指派到数组里面去
	  for(int i=0;i<vpSize;i++){
		  //locate 距离近期的中心点的坐标在center的下标
			int locate=0;
			double min=999;
			for(int j=0;j<centerSize;j++){
				double f=pointToPoint(vp[i],first[j]);
				//标记距离最短的那个中心点
				if(f<min){
					min=f;
					locate=j;
				}
			}
			//将点指派到相应的vector<Point>
			group[locate].push_back(vp[i]);
			//输出点指派信息
			//cout<<vp[i]<<"将被指派到簇"<<locate+1<<";"<<endl;
	  }
	  //显示簇
	  cout<<"经过聚类后的分簇情况:"<<endl;
	  for(int i=0;i<centerSize;i++){
		cout<<"\n簇"<<numberToString(i+1)<<":"<<endl;
		display(group[i]);
		cout<<endl;
	  }
	  for(int i=0;i<centerSize;i++){
		if(color==5)
			color=0;//重置color
		setColor(color++);
		paintVectorPoint(group[i]);
	  }
	//又一次计算簇中心并存放在second中
	//先清空second
	second.clear();
	for(int i=0;i<centerSize;i++){
		second.push_back(calCenter(group[i]));
	  }
	for(int i=0;i<centerSize;i++){
			if(second[i].x!=-999&&second[i].y!=-999)
				second[i].name="c"+numberToString(i+1);
		}
	cout<<"\n新的簇中心为:"<<endl;
	display(second);
	//擦除旧中心点
	if(!flag)
		RemoveCenterPoint(first);
	else{
		flag=false;
	}
	//标明每一个新中心
	paintCenterPoint(second);
	if(isEqual(first,second)){
			cout<<"\n聚类完毕!"<<endl;
			cout<<"共聚类"<<number<<"次"<<endl;
			break;
	}else if(number>LIMIT){
			cout<<"聚类次数超过了限制次数!"<<endl;
			cout<<"程序将退出"<<endl;
			break;
	}else{
		cout<<"\n未达到条件。继续聚类!"<<endl<<endl<<endl;
		//重置first中心
		first.clear();
		for(int i=0;i<centerSize;i++){
			first.push_back(second[i]);
		}
	 }
	number++;
	}while(true);
    return 0;  
};
#endif

//openglFunc.h
#ifndef opengl_kmeans_h_
#define opengl_kmeans_h_
#include <GL/glut.h>
#include <vector>
#include <iterator>
#include <windows.h>
#include <string>
#include "Point.h"
#include "functions.h"
//延时时间
#define DELAYTIME 0.2
//点的大小
#define POINTSIZE 8
//显示比例
#define BILI 10
//边
#define BIAN 1
//X,Y边
#define XLIMIT 1.5
#define YLIMIT 1.5


void drawString(const char *str);
//在屏幕上绘制单个点
void paintPoint(Point &p){
	float x=p.x*1.0/BILI;
	float y=p.y*1.0/BILI;
	glPointSize(POINTSIZE);
	glBegin(GL_POINTS);
	glVertex2f(x,y);
	glEnd();

	const char *name=p.name.c_str();
    glRasterPos2f(x+0.02f,y+0.0f);
    drawString(name);
	glFlush();
};
//绘制一个数组的点
void paintVectorPoint(vector<Point> &vp){
	int size=vp.size();
	for(int i=0;i<size;i++){
			paintPoint(vp[i]);
			delay(DELAYTIME);
	}
};
//绘制中心点,使用红颜色
//不延时
void paintCenterPoint(vector<Point> &vp){
	int size=vp.size();
	glColor3f(1.0,0.0,0.0);
	for(int i=0;i<size;i++){
		paintPoint(vp[i]);
	}
};

//将坐标绘制成网格
void paintGrid(){
	glColor3f(0.0,0.0,0.0);
	//竖向网格
	for(int i=1;i<10*XLIMIT;i++){
		float xx=i*1.0/10;
		glBegin(GL_LINES);
			glVertex2f(xx,0.0);
			glVertex2f(xx,YLIMIT);
		glEnd();
	}
	//横向网格
	for(int i=1;i<10*YLIMIT;i++){
		float yy=i*1.0/10;
		glBegin(GL_LINES);
			glVertex2f(0.0,yy);
			glVertex2f(XLIMIT,yy);
		glEnd();
	}
	
};
//显示坐标轴
// ASCII字符总共仅仅有0到127。一共128种字符
#define MAX_CHAR        128
void drawString(const char* str) {
    static int isFirstCall = 1;
    static GLuint lists;

    if( isFirstCall ) { // 假设是第一次调用,运行初始化
                         // 为每一个ASCII字符产生一个显示列表
         isFirstCall = 0;

         // 申请MAX_CHAR个连续的显示列表编号
         lists = glGenLists(MAX_CHAR);

         // 把每一个字符的绘制命令都装到相应的显示列表中
         wglUseFontBitmaps(wglGetCurrentDC(), 0, MAX_CHAR, lists);
     }
     // 调用每一个字符相应的显示列表,绘制每一个字符
    for(; *str!='\0'; ++str)
         glCallList(lists + *str);
};
//擦除旧的中心点
//使其变为白色
void RemoveCenterPoint(vector<Point> &vp){
	int size=vp.size();
	glColor3f(1.0,1.0,1.0);
	for(int i=0;i<size;i++){
		paintPoint(vp[i]);
	}
	//重绘网格
	paintGrid();
};
//绘制背景色为白色
void paintNull(){
	glColor3f(1.0,1.0,1.0);
	glBegin(GL_POLYGON);
		glVertex2f(-BIAN,-BIAN);
		glVertex2f(-BIAN,BIAN);
		glVertex2f(BIAN,BIAN);
		glVertex2f(BIAN,-BIAN);
	glEnd();
};
//绘制XY轴
void paintXY(){
	glColor3f(0.0,0.0,0.0);
	//绘制X轴
	glBegin(GL_LINES);
		glVertex2f(-0.2,0);
		glVertex2f(XLIMIT,0);
	glEnd();
	//Y
	glBegin(GL_LINES);
		glVertex2f(0.0,YLIMIT);
		glVertex2f(0.0,-0.2);
	glEnd();

	//坐标轴数字
	glColor3f(1.0f, 0.0f, 0.0f);
    glRasterPos2f(-0.05f,-0.05f);
    drawString("0");
	glRasterPos2f(0.49f,-0.05f);
    drawString("5");
	glRasterPos2f(0.99f,-0.05f);
    drawString("10");
	glRasterPos2f(-0.05f,0.5f);
    drawString("5");
	glRasterPos2f(-0.05f,0.99f);
    drawString("10");
	glRasterPos2f(1.45f,-0.05f);
    drawString("y");
	glRasterPos2f(-0.05f,1.45f);
    drawString("x");

    glutSwapBuffers();

};

//设置各簇的颜色
//i最大值为6
void setColor(int i){
	switch(i){
	case 2:glColor3f(1.0, 1.0, 0.0);break;  //--> 黄色 
	case 1:glColor3f(0.0, 0.0, 1.0);break;  //--> 蓝色 
	case 0:glColor3f(0.0, 1.0, 0.0);break;  //--> 绿色  
	//case 3:glColor3f(1.0, 0.0, 0.0);break;  //--> 红色   
	case 4:glColor3f(0.0, 1.0, 1.0);break;  //--> 青色 
	case 5:glColor3f(1.0, 0.0, 1.0);break;  //--> 品红色  
	case 3:glColor3f(0.0, 0.0, 0.0);break;  //--> 黑色 
	default:break;
	}
}
#endif
//tFunc.h
#ifndef tFunc_h_
#define tFunc_h_
#include <iostream>
#include "functions.h"
#include <vector>
#include "openglFunc.h"
#include "kmeans.h"
#include "functions.h"
#include <string>
using namespace std;

void yourChoice(){
	cout<<"请输入生成的随机点个数:(建议小于20点能够看得更清晰)"<<endl;
	int num;
	cin>>num;
	eatLine();
	vector<Point> vp;
	vector<Point> center;
	randPoint(vp,num);
	cout<<"随机生成的坐标点例如以下:"<<endl;
	display(vp);
	cout<<"请等待画好点后选择中心点:"<<endl;
	paintVectorPoint(vp);
	inputCenter(center,vp);
	kMeans(vp,center);
	
};

//演示书本样例
void Example(){
	Point p[10]={
		Point(3,4,"p1"),
		Point(3,6,"p2"),
		Point(7,3,"p3"),
		Point(4,7,"p4"),
		Point(3,8,"p5"),
		Point(8,5,"p6"),
		Point(4,5,"p7"),
		Point(4,1,"p8"),
		Point(7,4,"p9"),
		Point(5,5,"p10"),
	};
	vector<Point> vp;
	vector<Point> center;
	for(int i=0;i<10;i++)
		vp.push_back(p[i]);
	center.push_back(p[6]);
	center.push_back(p[9]);
	paintVectorPoint(vp);
	kMeans(vp,center);
};
#endif

//main.cpp
#include "openglFunc.h"
#include "functions.h"
#include "tFunc.h"
#include "displayFunc.h"

int main(int argc,char **argv)
{
	glutInit(&argc,argv);
	Init();
	glutMainLoop();
}


DOS界面+openGL绘图
演演示样例如以下:
技术分享
技术分享







以上是关于强算KMeans聚类算法演示器的主要内容,如果未能解决你的问题,请参考以下文章

在 Python 中为每次迭代绘制 KMeans 聚类中心

机器学习聚类算法(实战)

求MATLAB实现canopy-kmeans聚类算法的完整代码

K-means 与KNN 聚类算法

大数据实战第11期:Kmeans聚类算法做薪酬学历分析

八:聚类算法K-means(20191223-29)