数据挖掘k-means聚类算法JAVA模拟
一个用java实现的模拟数据挖掘算法k-means的demo。因为老湿说期末如果能自己编写程序模拟课本中的任意算法,通过答辩就可以不用考试。对于我这种不规矩的学生,能不考试当然选择不考了。
进入正题:k-means聚类算法,简单点说就是给定N个坐标点,然后在其中选择k个点做为k个簇的初始中心点,然后计算所有点到各个簇的中心点的距离,将点分配到最近的簇,然后重新计算k个簇的新中心点,然后继续迭代,直到k个簇的中心点不再变化。
先来说下数据结构:
首先定义一个类用于表示坐标点,并且定义和点相关的方法:
//定义一个类用于表示每个坐标点
class Point{
String name;
double x,y;
public Point(String name,double x,double y){
this.name=name;
this.x=x;
this.y=y;
}
public Point(){
}
//计算两个坐标点之间的距离
public static double distance(Point a,Point b){
return Math.sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
//计算每个坐标点距离哪个簇最近
public static int ClusterDistance(Point p,ArrayList<Cluster> array){
int a=0;
for(int i=0;i<array.size();i++){
if(Point.distance(p,array.get(a).center)>Point.distance(p, array.get(i).center)){
a=i;
}
}
return a;
}
//将double数组中的所有坐标点存入Point数组中
public static void PointAdd(ArrayList<Point> PointArray, ArrayList<double[]> DoubleArray){
for(int i=0;i<DoubleArray.size();i++){
PointArray.add(new Point("p"+(i+1),DoubleArray.get(i)[0],DoubleArray.get(i)[1]));
}
}
}
定义一个类用于表示簇以及相关的方法:
//定义一个类用于表示簇
class Cluster{
public Cluster(double x,double y){
center.x=x;
center.y=y;
}
//计算簇的新中心点坐标
public static void NewCenter(Cluster cluster){
double sumx=0,sumy=0;
int i=0;
for(Point ex:cluster.Array){
sumx+=ex.x;
sumy+=ex.y;
i++;
}
if(cluster.center.x!=sumx/i||cluster.center.y!=sumy/i){
cluster.center.x=sumx/i;
cluster.center.y=sumy/i;
cluster.changed=true;
}
else if(cluster.center.x==sumx/i&&cluster.center.y==sumy/i){
cluster.changed=false;
}
}
//判断所有簇的中心点是否不再发生变化
public static boolean Changing(ArrayList<Cluster> array){
boolean ex=false;
for(Cluster e:array){
if(e.changed==true){
ex=true;
}
}
return ex;
}
Point center=new Point();//簇中心点
ArrayList<Point> Array=new ArrayList<Point>(); //簇中的坐标元素
boolean changed=true;//用于判断该簇的中心点坐标是否发生变化
}
因为初始的时候要从n个点里随机选择k点作为k个簇的中心点,所以定义一个随机选择函数:
//用于获取1~n之间的t个不重复的整数
class GetRandom{
@SuppressWarnings("unchecked")
static public int[] fn(int n,int t)
{
ArrayList numbers=new ArrayList();
int[] number=new int[t];
for(int i=0;i<n;i++){ //初始化数组
numbers.add(i+1);
}
for(int j=0;j<t;j++){
int raNum=(int)(Math.random()*numbers.size());
number[j]=Integer.parseInt(numbers.get(raNum).toString());
numbers.remove(raNum);
}
return number;
}
}
对于一些各个类都要用到的全局变量,我喜欢将其定义为一个类的静态成员,这样可以直接通过类名来调用。(不知道是不是一个坏习惯=.=)
//存放全局变量的类
class Pub{
static StringBuffer str=new StringBuffer();//存放textArea的内容
static StringBuffer str1=new StringBuffer();//存放textArea1的内容
static ArrayList<Point> TotalArray=new ArrayList<Point>(); //定义用于存放坐标点的泛型数组
static ArrayList<Cluster> ClusterArray=new ArrayList<Cluster>(); //定义用于存放各个簇的点的泛型数组
static DrawPanel xyPanel=new DrawPanel();//坐标轴面板
static ArrayList<Color> ColorArray=new ArrayList<Color>();//存放颜色的泛型数组
}
接下来是图形界面,话说java的图形界面编写实在是有点**,虽然可以装一些可视化的插件来编辑,但还是不习惯(其实是我不会用T.T),不过自己用代码编写也有一个好处,就是可以把握各个细节,方便修改。
程序主要有3大面板(JPanel),一个是textArea1,用于显示所有坐标点,一个是textArea,用于显示算法的结果,还有一个是xyPanel,用于显示坐标轴和点。
算法部分:
算法部分主要写在按钮的监听事件中,当按下按钮时,执行相应的算法并把结果显示在对应的面板中,每次执行完算法都刷新一下xyPanel,更新坐标轴上的点。
//例题模拟按钮监听事件
private class ExButtonListener implements ActionListener{
public void actionPerformed(ActionEvent event){
Pub.TotalArray.clear();
Pub.TotalArray.add(new Point("p1",3,4));
Pub.TotalArray.add(new Point("p2",3,6));
Pub.TotalArray.add(new Point("p3",7,3));
Pub.TotalArray.add(new Point("p4",4,7));
Pub.TotalArray.add(new Point("p5",3,8));
Pub.TotalArray.add(new Point("p6",8,5));
Pub.TotalArray.add(new Point("p7",4,5));
Pub.TotalArray.add(new Point("p8",4,1));
Pub.TotalArray.add(new Point("p9",7,4));
Pub.TotalArray.add(new Point("p10",5,5));
Pub.str1.delete(0, Pub.str1.length());
for(Point ex:Pub.TotalArray){
Pub.str1.append(" "+ex.name+" ("+ex.x+","+ex.y+")\n");
}
textArea1.setText(Pub.str1.toString());
textArea1.setFont(new Font("Serif",0,16));
textArea1.setEditable(false);
textArea.setText("");
Pub.ClusterArray.clear();
Pub.xyPanel.repaint();
int k=2;
//在所有元素的数组里随机选择k个坐标点用于代表初始簇中心
Pub.ClusterArray.clear();
Pub.ClusterArray.add(new Cluster(4,5));
Pub.ClusterArray.add(new Cluster(5,5));
Pub.str.append("初始随机选择的"+k+"个簇中心点的坐标为:\n");
for(Cluster ex:Pub.ClusterArray){
Pub.str.append("("+ex.center.x+","+ex.center.y+")"+"\n");
}
for(Point ex:Pub.TotalArray){
Pub.ClusterArray.get(Point.ClusterDistance(ex,Pub.ClusterArray)).Array.add(ex);
}
if(Pub.TotalArray.size()!=k||k==1){
for(Cluster ex:Pub.ClusterArray){
Cluster.NewCenter(ex);
}}
int m=1;
ArrayList<Point> PointArray=new ArrayList<Point>();//用于存放要删除的点的数组
while(Cluster.Changing(Pub.ClusterArray)!=false){
Pub.str.append("第"+(m++)+"次迭代的结果为:√\n");
int ii=1;
for(Cluster ex:Pub.ClusterArray){
Pub.str.append("第"+(ii++)+"个簇的所有点为:\n");
for(Point p:ex.Array){
Pub.str.append(p.name+"("+p.x+","+p.y+")"+"\n");
}
Pub.str.append("新的中心点坐标为:"+"("+String.format("%.3f",ex.center.x)+","+String.format("%.3f",ex.center.y)+")"+"\n");
Pub.str.append("---------------------------------------------------------------\n");
}
for(int j=0;j<k;j++){
for(Point t:Pub.ClusterArray.get(j).Array){
if(Point.ClusterDistance(t,Pub.ClusterArray)!=j){
Pub.ClusterArray.get(Point.ClusterDistance(t, Pub.ClusterArray)).Array.add(t);
PointArray.add(t);
}
}
Pub.ClusterArray.get(j).Array.removeAll(PointArray);
PointArray.clear();
}
for(Cluster ex:Pub.ClusterArray){
Cluster.NewCenter(ex);
}
}
textArea.setText(Pub.str.toString());
textArea.setEditable(false);
Pub.str.delete(0, Pub.str.length());
Pub.xyPanel.repaint();
}
}
总体来讲,算法部分比较简单,其实就是比较点和点之间的距离和点的存储而已。这个程序用到了增强的for循环,泛型数组以及比较细节的一些小知识点。写完这个程序,发现书中的知识如此重要,有时候虽然只是一个小小的知识点,却可能在程序中发挥巨大作用。基础还是要继续加强!!!
最后附上程序的截图和源程序网盘地址:

此demo是用Eclipse写的,导出的项目文件的网盘地址:
