更多数据挖掘算法:https://github.com/linyiqun/DataMiningAlgorithm
算法介绍
K-Means又名为K均值算法,他是一个聚类算法,这里的K就是聚簇中心的个数,代表数据中存在多少数据簇。K-Means在聚类算法中算是非常简单的一个算法了。有点类似于KNN算法,都用到了距离矢量度量,用欧式距离作为小分类的标准。
算法步骤
(1)、设定数字k,从n个初始数据中随机的设置k个点为聚类中心点。
(2)、针对n个点的每个数据点,遍历计算到k个聚类中心点的距离,最后按照离哪个中心点最近,就划分到那个类别中。
(3)、对每个已经划分好类别的n个点,对同个类别的点求均值,作为此类别新的中心点。
(4)、循环(2),(3)直到最终中心点收敛。
以上的计算过程将会在下面我的程序实现中有所体现。
算法的代码实现
输入数据:
- 33
- 410
- 96
- 148
- 1811
- 217
- packageDataMining_KMeans;
- importjava.io.BufferedReader;
- importjava.io.File;
- importjava.io.FileReader;
- importjava.io.IOException;
- importjava.text.MessageFormat;
- importjava.util.ArrayList;
- importjava.util.Collections;
- /**
- *k均值算法工具类
- *
- *@authorlyq
- *
- */
- publicclassKMeansTool{
- //输入数据文件地址
- privateStringfilePath;
- //分类类别个数
- privateintclassNum;
- //类名称
- privateArrayList<String>classNames;
- //聚类坐标点
- privateArrayList<Point>classPoints;
- //所有的数据左边点
- privateArrayList<Point>totalPoints;
- publicKMeansTool(StringfilePath,intclassNum){
- this.filePath=filePath;
- this.classNum=classNum;
- readDataFile();
- }
- /**
- *从文件中读取数据
- */
- privatevoidreadDataFile(){
- Filefile=newFile(filePath);
- ArrayList<String[]>dataArray=newArrayList<String[]>();
- try{
- BufferedReaderin=newBufferedReader(newFileReader(file));
- Stringstr;
- String[]tempArray;
- while((str=in.readLine())!=null){
- tempArray=str.split("");
- dataArray.add(tempArray);
- }
- in.close();
- }catch(IOExceptione){
- e.getStackTrace();
- }
- classPoints=newArrayList<>();
- totalPoints=newArrayList<>();
- classNames=newArrayList<>();
- for(inti=0,j=1;i<dataArray.size();i++){
- if(j<=classNum){
- classPoints.add(newPoint(dataArray.get(i)[0],
- dataArray.get(i)[1],j+""));
- classNames.add(i+"");
- j++;
- }
- totalPoints
- .add(newPoint(dataArray.get(i)[0],dataArray.get(i)[1]));
- }
- }
- /**
- *K均值聚类算法实现
- */
- publicvoidkMeansClustering(){
- doubletempX=0;
- doubletempY=0;
- intcount=0;
- doubleerror=Integer.MAX_VALUE;
- Pointtemp;
- while(error>0.01*classNum){
- for(Pointp1:totalPoints){
- //将所有的测试坐标点就近分类
- for(Pointp2:classPoints){
- p2.computerDistance(p1);
- }
- Collections.sort(classPoints);
- //取出p1离类坐标点最近的那个点
- p1.setClassName(classPoints.get(0).getClassName());
- }
- error=0;
- //按照均值重新划分聚类中心点
- for(Pointp1:classPoints){
- count=0;
- tempX=0;
- tempY=0;
- for(Pointp:totalPoints){
- if(p.getClassName().equals(p1.getClassName())){
- count++;
- tempX+=p.getX();
- tempY+=p.getY();
- }
- }
- tempX/=count;
- tempY/=count;
- error+=Math.abs((tempX-p1.getX()));
- error+=Math.abs((tempY-p1.getY()));
- //计算均值
- p1.setX(tempX);
- p1.setY(tempY);
- }
- for(inti=0;i<classPoints.size();i++){
- temp=classPoints.get(i);
- System.out.println(MessageFormat.format("聚类中心点{0},x={1},y={2}",
- (i+1),temp.getX(),temp.getY()));
- }
- System.out.println("----------");
- }
- System.out.println("结果值收敛");
- for(inti=0;i<classPoints.size();i++){
- temp=classPoints.get(i);
- System.out.println(MessageFormat.format("聚类中心点{0},x={1},y={2}",
- (i+1),temp.getX(),temp.getY()));
- }
- }
- }
- packageDataMining_KMeans;
- /**
- *坐标点类
- *
- *@authorlyq
- *
- */
- publicclassPointimplementsComparable<Point>{
- //坐标点横坐标
- privatedoublex;
- //坐标点纵坐标
- privatedoubley;
- //以此点作为聚类中心的类的类名称
- privateStringclassName;
- //坐标点之间的欧式距离
- privateDoubledistance;
- publicPoint(doublex,doubley){
- this.x=x;
- this.y=y;
- }
- publicPoint(Stringx,Stringy){
- this.x=Double.parseDouble(x);
- this.y=Double.parseDouble(y);
- }
- publicPoint(Stringx,Stringy,StringclassName){
- this.x=Double.parseDouble(x);
- this.y=Double.parseDouble(y);
- this.className=className;
- }
- /**
- *距离目标点p的欧几里得距离
- *
- *@paramp
- */
- publicvoidcomputerDistance(Pointp){
- if(p==null){
- return;
- }
- this.distance=(this.x-p.x)*(this.x-p.x)+(this.y-p.y)
- *(this.y-p.y);
- }
- publicdoublegetX(){
- returnx;
- }
- publicvoidsetX(doublex){
- this.x=x;
- }
- publicdoublegetY(){
- returny;
- }
- publicvoidsetY(doubley){
- this.y=y;
- }
- publicStringgetClassName(){
- returnclassName;
- }
- publicvoidsetClassName(StringclassName){
- this.className=className;
- }
- publicdoublegetDistance(){
- returndistance;
- }
- publicvoidsetDistance(doubledistance){
- this.distance=distance;
- }
- @Override
- publicintcompareTo(Pointo){
- //TODOAuto-generatedmethodstub
- returnthis.distance.compareTo(o.distance);
- }
- }
- /**
- *K-means(K均值)算法调用类
- *@authorlyq
- *
- */
- publicclassClient{
- publicstaticvoidmain(String[]args){
- StringfilePath="C:\\Users\\lyq\\Desktop\\icon\\input.txt";
- //聚类中心数量设定
- intclassNum=3;
- KMeansTooltool=newKMeansTool(filePath,classNum);
- tool.kMeansClustering();
- }
- }
测试输出结果:
- 聚类中心点1,x=15.5,y=8
- 聚类中心点2,x=4,y=10
- 聚类中心点3,x=3,y=3
- ----------
- 聚类中心点1,x=17.667,y=8.667
- 聚类中心点2,x=6.5,y=8
- 聚类中心点3,x=3,y=3
- ----------
- 聚类中心点1,x=17.667,y=8.667
- 聚类中心点2,x=6.5,y=8
- 聚类中心点3,x=3,y=3
- ----------
- 结果值收敛
- 聚类中心点1,x=17.667,y=8.667
- 聚类中心点2,x=6.5,y=8
- 聚类中心点3,x=3,y=3
K-Means算法的优缺点
1、首先优点当然是算法简单,快速,易懂,没有涉及到特别复杂的数据结构。
2、缺点1是最开始K的数量值以及K个聚类中心点的设置不好定,往往开始时不同的k个中心点的设置对后面迭代计算的走势会有比较大的影响,这时候可以考虑根据类的自动合并和分裂来确定这个k。
3、缺点2由于计算是迭代式的,而且计算距离的时候需要完全遍历一遍中心点,当数据规模比较大的时候,开销就显得比较大了。