关于聚类算法Kmeans/K-mediods/层次聚类/OPTICS较为详细的介绍

来源:互联网 发布:aws没有centos 编辑:程序博客网 时间:2024/04/29 07:59

K-means算法

将一群物理对象或者抽象对象的划分成相似的对象类的过程。其中类簇是数据对象的集合,在类簇中所有的对象都彼此相似,而类簇与类簇之间的对象是彼此相异。

       聚类除了可以用于数据分割(data segmentation),也可以用于离群点检测(outlier detection),所谓的离群点指的是与“普通”点相对应的“异常”点,而这些“异常”点往往值的注意。

       很多人在学习聚类之初,容易将聚类和分类搞混淆。其实聚类属于无监督学习范畴(unsupervised learning),也可称作观察式学习过程,与分类不同,聚类并不依赖已有既定的先验知识。举个例子,我们成年后,很清楚世界是由男人和女人组成的,所以我们在建厕所的时候,会把厕所分为男厕所和女厕所,这就是“分类”;而当我们刚生下来,我们并不知道什么是男人,什么是女人,通过后天对生活的观察,我们发现有一类人他们有胡子,而有一类人她们头发比较长(当然,我的这个举例已经显然不符合当今世界的发展了,你们明白就行),于是我们就把有胡子的人分为一类人,把长头发的分为另一类人,然后“研究”发现,原来有胡子的叫男人,有长头发的叫女人,这个过程就是“聚类”。

        数据挖掘对聚类的典型要求如下:

1)  可伸缩性:当聚类对象由几百上升到几百万,我们希望最后的聚类结果的准确度能一致。

2)  处理不同类型属性的能力:有些聚类算法,其处理对象的属性的数据类型只能为数值类型,但是实际应用场景中,我们往往会遇到其他类型的数据,比如二元数据,分类数据等等。当然,在处理过程我们是可以将这些其他类型的数据预处理成数值型数据的,但是在聚类效率上或者聚类准确度上往往会有折损

3)  发现任意形状的类簇:因为许多聚类算法是用距离(eg:欧几里得距离或者曼哈顿距离)来量化对象之间的相似度的,基于这种方式,我们往往只能发现相似尺寸和密度的球状类簇或者成为凸形类簇。但是,类簇的形状可能是任意的。

4)  对聚类算法初始化参数的知识需求的最小化:很多算法在分析过程中需要用户提供一定的初始参数,比如期望的类簇个数,类簇初始质点的设定。聚类结果对这些参数是十分敏感的。这不仅加重了用户的负担,也非常影响聚类结果的准确性

5)  处理噪声数据的能力:所谓的噪声数据,可以理解为影响聚类结果的干扰数据,这些噪声数据的存在会造成聚类结果的畸变,最终导致低质量的聚类。

6)  增量聚类和对输入次序的不敏感:一些聚类算法不能将新加入的数据插入到已有的聚类结果;输入次序的敏感是指,对于给定的数据对象集合,以不同的次序提供输入对象时,最终产生的聚类结果的差异会比较大。

7)  高维性:有些算法只适合处理2维或者3维的数据,而对高维数据的处理能力很弱,因为在高维空间中数据分布可能十分稀疏,而且高度倾斜。

8)  基于约束的聚类:现实应用中可能需要在各种条件下进行聚类。因为同一个聚类算法,在不同的应用场景中所带来的聚类结果也是各异的,因此找到满足特定约束的具有良好聚类特性的数据分组是十分有挑战性的。

9)  可解释性和可用性:我们希望得到的聚类结果都能用特定的语义、知识进行解释,和实际的应用场景相联系。


一般情况,聚类算法可以划分为以下几类:划分方法(partitioning method)、层次方法(hierarchical methods)、基于密度的方法(density-based methods)、基于网格的方法(grid-based methods)、基于模型的方法(model-based methods.k-means算法属于划分方法中的一种。

K-means算法的整个流程:首先从聚类对象中随机选出K个对象作为类簇的质心(当然了,初始参数的K代表聚类结果的类簇数),对剩余的每个对象,根据它们分别到这个K个质心的距离,将它们指定到最相似的簇(因为K-means是利用距离来量化相似度的,所以我们这里可以理解为是“将它们指定到离最近最近距离的质心所属类簇”)。然后重新计算质心位置。以上过程不断反复,直到准则函数收敛为止。通常采用平方误差准则,定义如下:

其中,E代表的意思是所有类簇中各对象到其所属类簇质点平方误差和.

K:聚类结果类簇个数

Ci:i个类簇

P:类簇中聚类对象

mi:i个类簇的质心

K-means的优点和不足:能处理大型数据集,结果簇相当紧凑,并且簇和簇之间明显分离。计算复杂性O(tkn) t:迭代次数、K :聚类数 n:样本数;但是

1)该算法必须事先给定类簇数和质点,簇数和质点的初始值设定往往会对聚类的算法影响较大。

2 ) 通常会在获得一个局部最优值时停止,

3 ) 并且只适合对数值型数据聚类,

4) 只适用于聚类结果为凸形的数据集,K-means方法不适合发现非凸面形状的类簇,或者大小差别很大的簇。

5) 噪音和孤立点数据敏感,少量的该类数据对质点的计算会产生极大的影响。

关于K-means的代码实现网上有很多。


K中心点算法(K-medoids

前面介绍了k-means算法,并列举了该算法的缺点。而K中心点算法(K-medoids)正好能解决k-means算法中的 “噪声”敏感这个问题。

如何解决的呢?

首先,我们得介绍下k-means算法为什么会对“噪声”敏感。还记得K-means寻找质点的过程吗?对某类簇中所有的样本点维度求平均值,即获得该类簇质点的维度。当聚类的样本点中有“噪声”(离群点)时,在计算类簇质点的过程中会受到噪声异常维度的干扰,造成所得质点和实际质点位置偏差过大,从而使类簇发生“畸变”。

Eg: 类簇C1中已经包含点A(1,1)B(2,2) C(1,2) D(2,1) 假设N(100,100)为异常点,当它纳入类簇C1时,计算质点Centroid((1+2+1+2+100)/5,(1+2+2+1+100)/5)=centroid(21,21),此时可能造成了类簇C1质点的偏移,在下一轮迭代重新划分样本点的时候,将大量不属于类簇C1的样本点纳入,因此得到不准确的聚类结果。

为了解决该问题,K中心点算法(K-medoids)提出了新的质点选取方式,而不是简单像k-means算法采用均值计算法。在K中心点算法中,每次迭代后的质点都是从聚类的样本点中选取,而选取的标准就是当该样本点成为新的质点后能提高类簇的聚类质量,使得类簇更紧凑。该算法使用绝对误差标准来定义一个类簇的紧凑程度。

聚类分析(三) <wbr>K中心点算法(k-mediods)  (p是空间中的样本点,Oj是类簇Cj的质点)

如果某样本点成为质点后,绝对误差能小于原质点所造成的绝对误差,那么K中心点算法认为该样本点是可以取代原质点的,在一次迭代重计算类簇质点的时候,我们选择绝对误差最小的那个样本点成为新的质点。

Eg:样本点A –>E1=10

样本点B –>E2=11

样本点C –>E3=12

原质点O–>E4=13,那我们选举A作为类簇的新质点。

K-means算法一样,K-medoids也是采用欧几里得距离来衡量某个样本点到底是属于哪个类簇。终止条件是,当所有的类簇的质点都不在发生变化时,即认为聚类结束。

该算法除了改善K-means的“噪声”敏感以后,其他缺点和K-means一致,并且由于采用新的质点计算规则,也使得算法的时间复杂度上升:Ok(n-k)2
 

Java实现代码如下:

package com.kmedoids;
import java.util.ArrayList;
public class Cluster {
    private String clusterName; // 类簇名
    private Medoid medoid; // 类簇的质点
    private ArrayList<DataPoint> dataPoints; // 类簇中各样本点

    public Cluster(String clusterName) {
        this.clusterName = clusterName;
        this.medoid = null; // will be set by calling setCentroid()
        dataPoints = new ArrayList<DataPoint>();
    }

    public void setMedoid(Medoid c) {
        medoid = c;
    }

    public Medoid getMedoid() {
        return medoid;
    }

   
    public void addDataPoint(DataPoint dp) { // called from CAInstance
        dp.setCluster(this);// 标注该类簇属于某点,计算欧式距离
        this.dataPoints.add(dp);
    }

    public void removeDataPoint(DataPoint dp) {
        this.dataPoints.remove(dp);
    }

    public int getNumDataPoints() {
        return this.dataPoints.size();
    }

    public DataPoint getDataPoint(int pos) {
        return (DataPoint) this.dataPoints.get(pos);
    }


    public String getName() {
        return this.clusterName;
    }

    public ArrayList<DataPoint> getDataPoints() {
        return this.dataPoints;
    }
}

------------------------------------

package com.kmedoids;
import java.util.ArrayList;

public class DataPoint {
    private double dimension[]; //样本点的维度
    private String pointName; //样本点名字
    private Cluster cluster; //类簇
    private double euDt;//样本点到质点的距离

    public DataPoint(double dimension[], String pointName) {
        this.dimension = dimension;
        this.pointName = pointName;
        this.cluster = null;
    }

    public void setCluster(Cluster cluster) {
        this.cluster = cluster;
    }


   
    public double calEuclideanDistanceSum() {
        double sum=0.0;
        Cluster cluster=this.getCluster();
        ArrayList<DataPoint> dataPoints=cluster.getDataPoints();

        for(int i=0;i<dataPoints.size();i++){
            double[] dims=dataPoints.get(i).getDimensioin();
            for(int j=0;j<dims.length;j++){
                 double temp=Math.pow((dims[j]-this.dimension[j]),2);
                 sum=sum+temp;
            }
        }

        return Math.sqrt(sum);
    }

   
    public double testEuclideanDistance(Medoid c) {
        double sum=0.0;
        double[] cDim=c.getDimensioin();

        for(int i=0;i<dimension.length;i++){
           double temp=Math.pow((dimension[i]-cDim[i]),2);
           sum=sum+temp;
        }

        return Math.sqrt(sum);
    }

    public double[] getDimensioin() {
        return this.dimension;
    }

    public Cluster getCluster() {
        return this.cluster;
    }

    public double getCurrentEuDt() {
        return this.euDt;
    }

    public String getPointName() {
        return this.pointName;
    }
}
-------------------------------

package com.kmedoids;
import java.util.ArrayList;

public class Medoid{

    private double dimension[]; // 质点的维度
    private Cluster cluster; //所属类簇
    private double etdDisSum;//Medoid到本类簇中所有的欧式距离之和


    public Medoid(double dimension[]) {
        this.dimension = dimension;
    }

    public void setCluster(Cluster c) {
        this.cluster = c;
    }

    public double[] getDimensioin() {
        return this.dimension;
    }

    public Cluster getCluster() {
        return this.cluster;
    }

    public void calcMedoid() {// 取代价最小的点
        calcEtdDisSum();
        double minEucDisSum = this.etdDisSum;
        ArrayList<DataPoint> dps = this.cluster.getDataPoints();
        for (int i = 0; i < dps.size(); i++) {
            double tempeucDisSum = dps.get(i).calEuclideanDistanceSum();
            if (tempeucDisSum < minEucDisSum) {
                dimension = dps.get(i).getDimensioin();
                minEucDisSum=tempeucDisSum;
            }
        }
    }

    // 计算该Medoid到同类簇所有样本点的欧斯距离和
    private void calcEtdDisSum() {
        double sum=0.0;
        Cluster cluster=this.getCluster();
        ArrayList<DataPoint> dataPoints=cluster.getDataPoints();

        for(int i=0;i<dataPoints.size();i++){
            double[] dims=dataPoints.get(i).getDimensioin();
            for(int j=0;j<dims.length;j++){
                 double temp=Math.abs(dims[j]-this.dimension[j]);
                 sum=sum+temp;
            }
        }
        etdDisSum= sum;
    }
}

--------------------------

package com.kmedoids;

import java.util.ArrayList;

public class ClusterAnalysis {

    private Cluster[] clusters;// 所有类簇
    private int miter;// 迭代次数
    private ArrayList<DataPoint> dataPoints = new ArrayList<DataPoint>();// 所有样本点
    private int dimNum;//维度

    public ClusterAnalysis(int k, int iter, ArrayList<DataPoint> dataPoints,int dimNum) {
        clusters = new Cluster[k];// 类簇种类数
        for (int i = 0; i < k; i++) {
            clusters[i] = new Cluster("Cluster:" + i);
        }
        this.miter = iter;
        this.dataPoints = dataPoints;
        this.dimNum=dimNum;
    }

    public int getIterations() {
        return miter;
    }

    public ArrayList<DataPoint>[] getClusterOutput() {
        ArrayList<DataPoint> v[] = new ArrayList[clusters.length];
        for (int i = 0; i < clusters.length; i++) {
            v[i] = clusters[i].getDataPoints();
        }
        return v;
    }

   
    public void startAnalysis(double[][] medoids) {

        setInitialMedoids(medoids);

        double[][] newMedoids=medoids;
        double[][] oldMedoids=new double[medoids.length][this.dimNum];

        while(!isEqual(oldMedoids,newMedoids)){
            for(int m = 0; m < clusters.length; m++){//每次迭代开始情况各类簇的点
                clusters[m].getDataPoints().clear();
            }
            for (int j = 0; j < dataPoints.size(); j++) {
                int clusterIndex=0;
                double minDistance=Double.MAX_VALUE;

                for (int k = 0; k < clusters.length; k++) {//判断样本点属于哪个类簇
                    double eucDistance=dataPoints.get(j).testEuclideanDistance(clusters[k].getMedoid());
                    if(eucDistance<minDistance){
                        minDistance=eucDistance;
                        clusterIndex=k;
                    }
                }

               //将该样本点添加到该类簇
                clusters[clusterIndex].addDataPoint(dataPoints.get(j));

            }

            for(int m = 0; m < clusters.length; m++){
                clusters[m].getMedoid().calcMedoid();//重新计算各类簇的质点
            }

            for(int i=0;i<medoids.length;i++){
                for(int j=0;j<this.dimNum;j++){
                    oldMedoids[i][j]=newMedoids[i][j];
                }
            }


            for(int n=0;n<clusters.length;n++){
                newMedoids[n]=clusters[n].getMedoid().getDimensioin();
            }

            this.miter++;
        }


    }

    private void setInitialMedoids(double[][] medoids) {
        for (int n = 0; n < clusters.length; n++) {
            Medoid medoid = new Medoid(medoids[n]);
            clusters[n].setMedoid(medoid);
            medoid.setCluster(clusters[n]);
        }
    }

   
    private boolean isEqual(double[][] oldMedoids,double[][] newMedoids){
        boolean flag=false;
        for(int i=0;i<oldMedoids.length;i++){
            for(int j=0;j<oldMedoids[i].length;j++){
                if(oldMedoids[i][j]!=newMedoids[i][j]){
                    return flag;
                }
            }
        }
        flag=true;
        return flag;
    }
}
--------------------------------------------

package com.kmedoids;

import java.util.ArrayList;
import java.util.Iterator;

public class TestMain {
    public static void main (String args[]){
        ArrayList<DataPoint> dataPoints = new ArrayList<DataPoint>();

       
        double[] a={2,3};
        double[] b={2,4};
        double[] c={1,4};
        double[] d={1,3};
        double[] e={2,2};
        double[] f={3,2};

        double[] g={8,7};
        double[] h={8,6};
        double[] i={7,7};
        double[] j={7,6};
        double[] k={8,5};

        double[] l={100,2};//孤立点

        double[] m={8,20};
        double[] n={8,19};
        double[] o={7,18};
        double[] p={7,17};
        double[] q={7,20};

        dataPoints.add(new DataPoint(a,"a"));
        dataPoints.add(new DataPoint(b,"b"));
        dataPoints.add(new DataPoint(c,"c"));
        dataPoints.add(new DataPoint(d,"d"));
        dataPoints.add(new DataPoint(e,"e"));
        dataPoints.add(new DataPoint(f,"f"));

        dataPoints.add(new DataPoint(g,"g"));
        dataPoints.add(new DataPoint(h,"h"));
        dataPoints.add(new DataPoint(i,"i"));
        dataPoints.add(new DataPoint(j,"j"));
        dataPoints.add(new DataPoint(k,"k"));

        dataPoints.add(new DataPoint(l,"l"));

        dataPoints.add(new DataPoint(m,"m"));
        dataPoints.add(new DataPoint(n,"n"));
        dataPoints.add(new DataPoint(o,"o"));
        dataPoints.add(new DataPoint(p,"p"));
        dataPoints.add(new DataPoint(q,"q"));

        ClusterAnalysis ca=new ClusterAnalysis(3,0,dataPoints,2);
       double[][] cen={{8,7},{8,6},{7,7}};
       ca.startAnalysis(cen);

       ArrayList<DataPoint>[] v = ca.getClusterOutput();
        for (int ii=0; ii<v.length; ii++){
            ArrayList tempV = v[ii];
            System.out.println("-----------Cluster"+ii+"---------");
            Iterator iter = tempV.iterator();
            while(iter.hasNext()){
                DataPoint dpTemp = (DataPoint)iter.next();
                System.out.println(dpTemp.getPointName());
            }
        }
    }

}



层次聚类算法

前面介绍的K-means算法和K中心点算法都属于划分式(partitional)聚类算法。层次聚类算法是将所有的样本点自底向上合并组成一棵树或者自顶向下分裂成一棵树的过程,这两种方式分别称为凝聚和分裂。

凝聚层次算法:

初始阶段,将每个样本点分别当做其类簇,然后合并这些原子类簇直至达到预期的类簇数或者其他终止条件。

分裂层次算法:

初始阶段,将所有的样本点当做同一类簇,然后分裂这个大类簇直至达到预期的类簇数或者其他终止条件。

两种算法的代表:

传统的凝聚层次聚类算法有AGENES,初始时,AGENES将每个样本点自为一簇,然后这些簇根据某种准则逐渐合并,例如,如果簇C1中的一个样本点和簇C2中的一个样本点之间的距离是所有不同类簇的样本点间欧几里得距离最近的,则认为簇C1和簇C2是相似可合并的。

传统的分裂层次聚类算法有DIANA,初始时DIANA将所有样本点归为同一类簇,然后根据某种准则进行逐渐分裂,例如类簇C中两个样本点AB之间的距离是类簇C中所有样本点间距离最远的一对,那么样本点AB将分裂成两个簇C1C2,并且先前类簇C中其他样本点根据与AB之间的距离,分别纳入到簇C1C2,例如,类簇C中样本点O与样本点A的欧几里得距离为2,与样本点B的欧几里得距离为4,因为Distance(AO)<Distance(B,O)那么O将纳入到类簇C1中。

如图所示:

聚类分析(四)层次聚类算法

算法:AGENES。传统凝聚层次聚类算法

输入:K:目标类簇数 D:样本点集合

输出:K个类簇集合

方法:1) D中每个样本点当做其类簇;

      2) repeat

      3)    找到分属两个不同类簇,且距离最近的样本点对;

      4)    将两个类簇合并;

      5) util 类簇数=K

 

算法:DIANA。传统分裂层次聚类算法

输入:K:目标类簇数 D:样本点集合

输出:K个类簇集合

方法:1) D中所有样本点归并成类簇;

      2) repeat

      3)    在同类簇中找到距离最远的样本点对;

      4)    以该样本点对为代表,将原类簇中的样本点重新分属到新类簇

      5) util 类簇数=K

缺点:

传统的层次聚类算法的效率比较低O(tn2) t:迭代次数 n:样本点数,最明显的一个缺点是不具有再分配能力,即如果样本点A在某次迭代过程中已经划分给类簇C1,那么在后面的迭代过程中A将永远属于类簇C1,这将影响聚类结果的准确性。

改进:

一般情况下,层次聚类通常和划分式聚类算法组合,这样既可以解决算法效率的问题,又能解决样本点再分配的问题,在后面将介绍BIRCH算法。首先把邻近样本点划分到微簇(microcluseters)中,然后对这些微簇使用K-means算法。

----------------贴上本人实现的AGENES算法,大家有兴趣可以把DIANA算法自己实现下---------------

package com.agenes;

public class DataPoint {
    String dataPointName; // 样本点名
    Cluster cluster; // 样本点所属类簇
    private double dimensioin[]; // 样本点的维度

    public DataPoint(){

    }

    public DataPoint(double[] dimensioin,String dataPointName){
         this.dataPointName=dataPointName;
         this.dimensioin=dimensioin;
    }

    public double[] getDimensioin() {
        return dimensioin;
    }

    public void setDimensioin(double[] dimensioin) {
        this.dimensioin = dimensioin;
    }

    public Cluster getCluster() {
        return cluster;
    }

    public void setCluster(Cluster cluster) {
        this.cluster = cluster;
    }

    public String getDataPointName() {
        return dataPointName;
    }

    public void setDataPointName(String dataPointName) {
        this.dataPointName = dataPointName;
    }
}

package com.agenes;

import java.util.ArrayList;
import java.util.List;


public class Cluster {
    private List<DataPoint> dataPoints = new ArrayList<DataPoint>(); // 类簇中的样本点
    private String clusterName;

    public List<DataPoint> getDataPoints() {
        return dataPoints;
    }

    public void setDataPoints(List<DataPoint> dataPoints) {
        this.dataPoints = dataPoints;
    }

    public String getClusterName() {
        return clusterName;
    }

    public void setClusterName(String clusterName) {
        this.clusterName = clusterName;
    }

}

package com.agenes;

import java.util.ArrayList;
import java.util.List;


public class ClusterAnalysis {
   public List<Cluster> startAnalysis(List<DataPoint> dataPoints,int ClusterNum){
      List<Cluster> finalClusters=new ArrayList<Cluster>();
     
      List<Cluster> originalClusters=initialCluster(dataPoints);
      finalClusters=originalClusters;
      while(finalClusters.size()>ClusterNum){
          double min=Double.MAX_VALUE;
          int mergeIndexA=0;
          int mergeIndexB=0;
          for(int i=0;i<finalClusters.size();i++){
              for(int j=0;j<finalClusters.size();j++){
                  if(i!=j){
                      Cluster clusterA=finalClusters.get(i);
                      Cluster clusterB=finalClusters.get(j);

                      List<DataPoint> dataPointsA=clusterA.getDataPoints();
                      List<DataPoint> dataPointsB=clusterB.getDataPoints();

                      for(int m=0;m<dataPointsA.size();m++){
                          for(int n=0;n<dataPointsB.size();n++){
                              double tempDis=getDistance(dataPointsA.get(m),dataPointsB.get(n));
                              if(tempDis<min){
                                  min=tempDis;
                                  mergeIndexA=i;
                                  mergeIndexB=j;
                              }
                          }
                      }
                  }
              } //end for j
          }// end for i
          //合并cluster[mergeIndexA]和cluster[mergeIndexB]
          finalClusters=mergeCluster(finalClusters,mergeIndexA,mergeIndexB);
      }//end while

      return finalClusters;
   }

   private List<Cluster> mergeCluster(List<Cluster> clusters,int mergeIndexA,int mergeIndexB){
        if (mergeIndexA != mergeIndexB) {
            // 将cluster[mergeIndexB]中的DataPoint加入到 cluster[mergeIndexA]
            Cluster clusterA = clusters.get(mergeIndexA);
            Cluster clusterB = clusters.get(mergeIndexB);

            List<DataPoint> dpA = clusterA.getDataPoints();
            List<DataPoint> dpB = clusterB.getDataPoints();

            for (DataPoint dp : dpB) {
                DataPoint tempDp = new DataPoint();
                tempDp.setDataPointName(dp.getDataPointName());
                tempDp.setDimensioin(dp.getDimensioin());
                tempDp.setCluster(clusterA);
                dpA.add(tempDp);
            }

            clusterA.setDataPoints(dpA);

            // List<Cluster> clusters中移除cluster[mergeIndexB]
            clusters.remove(mergeIndexB);
        }

        return clusters;
   }

   // 初始化类簇
   private List<Cluster> initialCluster(List<DataPoint> dataPoints){
       List<Cluster> originalClusters=new ArrayList<Cluster>();
       for(int i=0;i<dataPoints.size();i++){
           DataPoint tempDataPoint=dataPoints.get(i);
           List<DataPoint> tempDataPoints=new ArrayList<DataPoint>();
           tempDataPoints.add(tempDataPoint);

           Cluster tempCluster=new Cluster();
           tempCluster.setClusterName("Cluster "+String.valueOf(i));
           tempCluster.setDataPoints(tempDataPoints);

           tempDataPoint.setCluster(tempCluster);
           originalClusters.add(tempCluster);
       }

       return originalClusters;
   }

   //计算两个样本点之间的欧几里得距离
   private double getDistance(DataPoint dpA,DataPoint dpB){
        double distance=0;
        double[] dimA = dpA.getDimensioin();
        double[] dimB = dpB.getDimensioin();

        if (dimA.length == dimB.length) {
            for (int i = 0; i < dimA.length; i++) {
                 double temp=Math.pow((dimA[i]-dimB[i]),2);
                 distance=distance+temp;
            }
            distance=Math.pow(distance, 0.5);
        }

       return distance;
   }

   public static void main(String[] args){
       ArrayList<DataPoint> dpoints = new ArrayList<DataPoint>();
      
       double[] a={2,3};
       double[] b={2,4};
       double[] c={1,4};
       double[] d={1,3};
       double[] e={2,2};
       double[] f={3,2};

       double[] g={8,7};
       double[] h={8,6};
       double[] i={7,7};
       double[] j={7,6};
       double[] k={8,5};

//       double[] l={100,2};//孤立点


       double[] m={8,20};
       double[] n={8,19};
       double[] o={7,18};
       double[] p={7,17};
       double[] q={8,20};

       dpoints.add(new DataPoint(a,"a"));
       dpoints.add(new DataPoint(b,"b"));
       dpoints.add(new DataPoint(c,"c"));
       dpoints.add(new DataPoint(d,"d"));
       dpoints.add(new DataPoint(e,"e"));
       dpoints.add(new DataPoint(f,"f"));

       dpoints.add(new DataPoint(g,"g"));
       dpoints.add(new DataPoint(h,"h"));
       dpoints.add(new DataPoint(i,"i"));
       dpoints.add(new DataPoint(j,"j"));
       dpoints.add(new DataPoint(k,"k"));

//       dataPoints.add(new DataPoint(l,"l"));

       dpoints.add(new DataPoint(m,"m"));
       dpoints.add(new DataPoint(n,"n"));
       dpoints.add(new DataPoint(o,"o"));
       dpoints.add(new DataPoint(p,"p"));
       dpoints.add(new DataPoint(q,"q"));

       int clusterNum=3; //类簇数

       ClusterAnalysis ca=new ClusterAnalysis();
       List<Cluster> clusters=ca.startAnalysis(dpoints, clusterNum);

       for(Cluster cl:clusters){
           System.out.println("------"+cl.getClusterName()+"------");
           List<DataPoint> tempDps=cl.getDataPoints();
           for(DataPoint tempdp:tempDps){
               System.out.println(tempdp.getDataPointName());
           }
       }

   }
    



基于密度的聚类算法OPTICS

 什么是OPTICS算法

在前面介绍的DBSCAN算法中,有两个初始参数E(邻域半径)和minPts(E邻域最小点数)需要用户手动设置输入,并且聚类的类簇结果对这两个参数的取值非常敏感,不同的取值将产生不同的聚类结果,其实这也是大多数其他需要初始化参数聚类算法的弊端。

为了克服DBSCAN算法这一缺点,提出了OPTICS算法(Ordering Points to identify the clustering structure)。OPTICS并不显示的产生结果类簇,而是为聚类分析生成一个增广的簇排序(比如,以可达距离为纵轴,样本点输出次序为横轴的坐标图),这个排序代表了各样本点基于密度的聚类结构。它包含的信息等价于从一个广泛的参数设置所获得的基于密度的聚类,换句话说,从这个排序中可以得到基于任何参数EminPtsDBSCAN算法的聚类结果。     

 OPTICS两个概念

核心距离

对象p的核心距离是指是p成为核心对象的最小E’。如果p不是核心对象,那么p的核心距离没有任何意义。

可达距离

对象q到对象p的可达距离是指p的核心距离和pq之间欧几里得距离之间的较大值。如果p不是核心对象,pq之间的可达距离没有意义。

例如:假设邻域半径E=2, minPts=3,存在点A(2,3),B(2,4),C(1,4),D(1,3),E(2,2),F(3,2)

A为核心对象,在AE领域中有点{A,B,C,D,E,F},其中A的核心距离为E’=1,因为在点AE’邻域中有点{A,B,D,E}>3;

F到核心对象点A的可达距离为聚类分析(六)基于密度的聚类算法 <wbr>鈥 <wbr>OPTICS,因为AF的欧几里得距离聚类分析(六)基于密度的聚类算法 <wbr>鈥 <wbr>OPTICS,大于点A的核心距离1.

3 算法描述

OPTICS算法额外存储了每个对象的核心距离和可达距离。基于OPTICS产生的排序信息来提取类簇。

算法描述如下:

算法:OPTICS

输入:样本集D, 邻域半径E, 给定点在E领域内成为核心对象的最小领域点数MinPts

输出:具有可达距离信息的样本点输出排序

方法:1 创建两个队列,有序队列和结果队列。(有序队列用来存储核心对象及其该核心对

         象的直接可达对象,并按可达距离升序排列;结果队列用来存储样本点的输出次

         序);

       2 如果所有样本集D中所有点都处理完毕,则算法结束。否则,选择一个未处理(即

不在结果队列中)且为核心对象的样本点,找到其所有直接密度可达样本点,如

过该样本点不存在于结果队列中,则将其放入有序队列中,并按可达距离排序;

      3 如果有序队列为空,则跳至步骤2,否则,从有序队列中取出第一个样本点(即可

达距离最小的样本点)进行拓展,并将取出的样本点保存至结果队列中,如果它不

存在结果队列当中的话。

 3.1 判断该拓展点是否是核心对象,如果不是,回到步骤3,否则找到该拓展点所

有的直接密度可达点;

3.2 判断该直接密度可达样本点是否已经存在结果队列,是则不处理,否则下一

步;

3.2 如果有序队列中已经存在该直接密度可达点,如果此时新的可达距离小于旧

的可达距离,则用新可达距离取代旧可达距离,有序队列重新排序;

3.3 如果有序队列中不存在该直接密度可达样本点,则插入该点,并对有序队列

   重新排序;

       4 算法结束,输出结果队列中的有序样本点。

大家或许会很疑惑,这里不也有输入参数EMinPts吗?其实这里的EMinPts只是起到算法辅助作用,也就是说EMinPts的细微变化并不会影响到样本点的相对输出顺序,这对我们分析聚类结果是没有任何影响。

我们采用与先前DBSCAN相同的样本点集合,

对于样本点

a={2,3};b={2,4};c={1,4};d={1,3};e={2,2};f={3,2};

g={8,7};h={8,6};i={7,7};j={7,6};k={8,5};

l={100,2};//孤立点

m={8,20};n={8,19};o={7,18};p={7,17};q={8,21};

并且使用相同的E=2 MinPts=4时,输出序列为

1->a:1.0

2->e:1.0

3->b:1.0

4->d:1.0

5->c:1.4142135623730951

6->f:1.4142135623730951

------

7->g:1.4142135623730951

8->j:1.4142135623730951

9->k:1.4142135623730951

10->i:1.4142135623730951

11->h:1.4142135623730951

------

12->n:2.0

13->q:2.0

14->o:2.0

15->m:2.0

聚类分析(六)基于密度的聚类算法 <wbr>鈥 <wbr>OPTICS

如图,按照算法,分三个阶段输出了三波值

{a,e,b,d,c,f} ,{g,j,k,I,h},{n,q,o,m}

这和DBSCAN的类簇结果是一样的。不仅如此,我们通过分析有序图还能直接得到当参数E=1.5,minPts=4DBSCAN的类簇结果,只要在坐标图中找到Y值小于1.5的样本点即可,只有两类{a,e,b,d,c,f} ,{g,j,k,I,h},其他点被认为是孤立点,和DBSCAN聚类算法取E=1.5,minPts=4时的结果一致。

所以说,这个OPTICS聚类算法所得的簇排序信息等价于一个广泛的参数设置所获得的基于密度的聚类结果。

具体实现算法如下:

package com.optics;

public class DataPoint {
    private String name; // 样本点名
    private double dimensioin[]; // 样本点的维度
    private double coreDistance; //核心距离,如果该点不是核心对象,则距离为-1
    private double reachableDistance; //可达距离

    public DataPoint(){
    }

    public DataPoint(DataPoint e){
        this.name=e.name;
        this.dimensioin=e.dimensioin;
        this.coreDistance=e.coreDistance;
        this.reachableDistance=e.reachableDistance;
    }

    public DataPoint(double dimensioin[],String name){
        this.name=name;
        this.dimensioin=dimensioin;
        this.coreDistance=-1;
        this.reachableDistance=-1;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public double[] getDimensioin() {
        return dimensioin;
    }
    public void setDimensioin(double[] dimensioin) {
        this.dimensioin = dimensioin;
    }
    public double getCoreDistance() {
        return coreDistance;
    }
    public void setCoreDistance(double coreDistance) {
        this.coreDistance = coreDistance;
    }
    public double getReachableDistance() {
        return reachableDistance;
    }
    public void setReachableDistance(double reachableDistance) {
        this.reachableDistance = reachableDistance;
    }
}
package com.optics;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class ClusterAnalysis {
    class ComparatorDp implements Comparator<DataPoint>{
        public int compare(DataPoint arg0, DataPoint arg1) {
            double temp=arg0.getReachableDistance()-arg1.getReachableDistance();
            int a = 0;
            if (temp < 0) {
                a = -1;
            } else {
                a = 1;
            }
            return a;
        }
    }

   
    public List<DataPoint> startAnalysis(List<DataPoint> dataPoints,
            double radius, int ObjectNum) {
        List<DataPoint> dpList = new ArrayList<DataPoint>();
        List<DataPoint> dpQue = new ArrayList<DataPoint>();

        int total = 0;
        while (total < dataPoints.size()) {
            if (isContainedInList(dataPoints.get(total), dpList) == -1 ) {
                List<DataPoint> tmpDpList = isKeyAndReturnObjects(dataPoints.get(total),
                        dataPoints, radius, ObjectNum);
                if(tmpDpList != null && tmpDpList.size() > 0){
                    DataPoint newDataPoint=new DataPoint(dataPoints.get(total));
                   dpQue.add(newDataPoint);
                }
            }
            while (!dpQue.isEmpty()) {
                DataPoint tempDpfromQ = dpQue.remove(0);
                DataPoint newDataPoint=new DataPoint(tempDpfromQ);
                dpList.add(newDataPoint);
                List<DataPoint> tempDpList = isKeyAndReturnObjects(tempDpfromQ,
                        dataPoints, radius, ObjectNum);
               System.out.println(newDataPoint.getName()+":"+newDataPoint.getReachableDistance());
                if (tempDpList != null && tempDpList.size() > 0) {
                    for (int i = 0; i < tempDpList.size(); i++) {
                        DataPoint tempDpfromList = tempDpList.get(i);
                        int indexInList = isContainedInList(tempDpfromList,
                                dpList);
                        int indexInQ = isContainedInList(tempDpfromList, dpQue);
                        if (indexInList == -1) {
                            if (indexInQ > -1) {
                                int index = -1;
                                for (DataPoint dataPoint : dpQue) {
                                    index++;
                                    if (index == indexInQ) {
                                        if (dataPoint.getReachableDistance() > tempDpfromList
                                                .getReachableDistance()) {
                                            dataPoint
                                                    .setReachableDistance(tempDpfromList
                                                            .getReachableDistance());
                                        }
                                    }
                                }
                            } else {
                                dpQue.add(new DataPoint(tempDpfromList));
                            }
                        }
                    }

                    // TODO:对Q进行重新排序
                    Collections.sort(dpQue, new ComparatorDp());
                }
            }
            System.out.println("------");
            total++;

        }

        return dpList;
    }

   
    public void displayDataPoints(List<DataPoint> dps){
        for(DataPoint dp: dps){
            System.out.println(dp.getName()+":"+dp.getReachableDistance());
        }
    }

   
    private int isContainedInList(DataPoint dp, List<DataPoint> dpList) {
        int index = -1;
        for (DataPoint dataPoint : dpList) {
            index++;
            if (dataPoint.getName().equals(dp.getName())) {
                return index;
            }
        }
        return -1;
    }

   
   private List<DataPoint> isKeyAndReturnObjects(DataPoint dataPoint,List<DataPoint> dataPoints,double radius,int ObjectNum){
       List<DataPoint> arrivableObjects=new ArrayList<DataPoint>(); //用来存储所有直接密度可达对象
       List<Double> distances=new ArrayList<Double>(); //欧几里得距离
       double coreDistance; //核心距离

        for (int i = 0; i < dataPoints.size(); i++) {
            DataPoint dp = dataPoints.get(i);
            double distance = getDistance(dataPoint, dp);
            if (distance <= radius) {
                distances.add(distance);
                arrivableObjects.add(dp);
            }
        }

       if(arrivableObjects.size()>=ObjectNum){
           List<Double> newDistances=new ArrayList<Double>(distances);
           Collections.sort(distances);
           coreDistance=distances.get(ObjectNum-1);
           for(int j=0;j<arrivableObjects.size();j++){
                if (coreDistance > newDistances.get(j)) {
                    if(newDistances.get(j)==0){
                        dataPoint.setReachableDistance(coreDistance);
                    }
                    arrivableObjects.get(j).setReachableDistance(coreDistance);
                }else{
                    arrivableObjects.get(j).setReachableDistance(newDistances.get(j));
                }
           }
           return arrivableObjects;
       }

       return null;
   }

   
    private double getDistance(DataPoint dp1,DataPoint dp2){
        double distance=0.0;
        double[] dim1=dp1.getDimensioin();
        double[] dim2=dp2.getDimensioin();
        if(dim1.length==dim2.length){
            for(int i=0;i<dim1.length;i++){
                double temp=Math.pow((dim1[i]-dim2[i]), 2);
                distance=distance+temp;
            }
            distance=Math.pow(distance, 0.5);
            return distance;
        }
        return distance;
    }

    public static void main(String[] args){
         ArrayList<DataPoint> dpoints = new ArrayList<DataPoint>();
        
         double[] a={2,3};
         double[] b={2,4};
         double[] c={1,4};
         double[] d={1,3};
         double[] e={2,2};
         double[] f={3,2};

         double[] g={8,7};
         double[] h={8,6};
         double[] i={7,7};
         double[] j={7,6};
         double[] k={8,5};

         double[] l={100,2};//孤立点


         double[] m={8,20};
         double[] n={8,19};
         double[] o={7,18};
         double[] p={7,17};
         double[] q={8,21};

         dpoints.add(new DataPoint(a,"a"));
         dpoints.add(new DataPoint(b,"b"));
         dpoints.add(new DataPoint(c,"c"));
         dpoints.add(new DataPoint(d,"d"));
         dpoints.add(new DataPoint(e,"e"));
         dpoints.add(new DataPoint(f,"f"));

         dpoints.add(new DataPoint(g,"g"));
         dpoints.add(new DataPoint(h,"h"));
         dpoints.add(new DataPoint(i,"i"));
         dpoints.add(new DataPoint(j,"j"));
         dpoints.add(new DataPoint(k,"k"));

         dpoints.add(new DataPoint(l,"l"));

         dpoints.add(new DataPoint(m,"m"));
         dpoints.add(new DataPoint(n,"n"));
         dpoints.add(new DataPoint(o,"o"));
         dpoints.add(new DataPoint(p,"p"));
         dpoints.add(new DataPoint(q,"q"));

         ClusterAnalysis ca=new ClusterAnalysis();
         List<DataPoint> dps=ca.startAnalysis(dpoints, 2, 4);
         ca.displayDataPoints(dps);
    }

}



DBSCAN

一  什么是基于密度的聚类算法

由于层次聚类算法和划分式聚类算往往只能发现凸形的聚类簇。为了弥补这一缺陷,发现各种任意形状的聚类簇,开发出基于密度的聚类算法。这类算法认为,在整个样本空间点中,各目标类簇是由一群的稠密样本点组成的,而这些稠密样本点被低密度区域(噪声)分割,而算法的目的就是要过滤低密度区域,发现稠密样本点。

二  DBSCANDensity-based Spatial Clustering of Applications with Noise

是一种基于高密度联通区域的聚类算法,它将类簇定义为高密度相连点的最大集合。它本身对噪声不敏感,并且能发现任意形状的类簇。

DBSCAN中的的几个定义:

Ε领域:给定对象半径为Ε内的区域称为该对象的Ε领域

核心对象:如果给定对象Ε领域内的样本点数大于等于MinPts,则称该对象为核心对象

直接密度可达:对于样本集合D,如果样本点qpΕ领域内,并且p为核心对象,那么对象q从对象p直接密度可达

密度可达:对于样本集合D,给定一串样本点p1,p2….pnp= p1,q= pn,假如对象pipi-1直接密度可达,那么对象q从对象p密度可达

密度相连:对于样本集合D中的任意一点O,如果存在对象p到对象o密度可达,并且对象q到对象o密度可达,那么对象q到对象p密度相连

可以发现,密度可达是直接密度可达的传递闭包,并且这种关系是非对称的。密度相连是对称关系。DBSCAN目的是找到密度相连对象的最大集合。

Eg: 假设半径Ε=3MinPts=3,点pE领域中有点{m,p,p1,p2,o}, mE领域中有点{m,q,p,m1,m2},qE领域中有点{q,m},oE领域中有点{o,p,s},sE领域中有点{o,s,s1}.

那么核心对象有p,m,o,s(q不是核心对象,因为它对应的E领域中点数量等于2,小于MinPts=3)

m从点p直接密度可达,因为mpE领域内,并且p为核心对象;

q从点p密度可达,因为点q从点m直接密度可达,并且点m从点p直接密度可达;

q到点s密度相连,因为点q从点p密度可达,并且s从点p密度可达。

三  算法描述

算法:DBSCAN

输入:E — 半径

      MinPts — 给定点在E领域内成为核心对象的最小领域点数

      D — 集合

输出:目标类簇集合

方法:repeat

1)       判断输入点是否为核心对象

2)       找出核心对象的E领域中的所有直接密度可达点

      util 所有输入点都判断完毕

      repeat

         针对所有核心对象的E领域所有直接密度可达点找到最大密度相连对象集合,

         中间涉及到一些密度可达对象的合并。

      Util 所有核心对象的E领域都遍历完毕



算法:DBSCAN

输入:E  半径

      MinPts  给定点在E领域内成为核心对象的最小领域点数

      D  集合

输出:目标类簇集合

方法:repeat

1)       判断输入点是否为核心对象

2)       找出核心对象的E领域中的所有直接密度可达点

      util 所有输入点都判断完毕

      repeat

         针对所有核心对象的E领域所有直接密度可达点找到最大密度相连对象集合,

         中间涉及到一些密度可达对象的合并。

      Util 所有核心对象的E领域都遍历完毕

四  算法实现

package com.dbscan;

public class DataPoint {
    private String dataPointName; // 样本点名
    private double dimensioin[]; // 样本点的维度
    private boolean isKey; //是否是核心对象

    public DataPoint(){

    }

    public DataPoint(double[] dimensioin,String dataPointName,boolean isKey){
         this.dataPointName=dataPointName;
         this.dimensioin=dimensioin;
         this.isKey=isKey;
    }

}

------------

package com.dbscan;

import java.util.ArrayList;
import java.util.List;



public class Cluster {
    private List<DataPoint> dataPoints = new ArrayList<DataPoint>(); // 类簇中的样本点
    private String clusterName; //簇名

    public List<DataPoint> getDataPoints() {
        return dataPoints;
    }

    public void setDataPoints(List<DataPoint> dataPoints) {
        this.dataPoints = dataPoints;
    }

    public String getClusterName() {
        return clusterName;
    }

    public void setClusterName(String clusterName) {
        this.clusterName = clusterName;
    }

}

------------

package com.dbscan;

import java.util.ArrayList;
import java.util.List;

public class ClusterAnalysis {

   
    public List<Cluster> doDbscanAnalysis(List<DataPoint> dataPoints,
            double radius, int ObjectNum) {
         List<Cluster> clusterList=new ArrayList<Cluster>();
         for(int i=0; i<dataPoints.size();i++){
             DataPoint dp=dataPoints.get(i);
             List<DataPoint> arrivableObjects=isKeyAndReturnObjects(dp,dataPoints,radius,ObjectNum);
             if(arrivableObjects!=null){
                  Cluster tempCluster=new Cluster();
                  tempCluster.setClusterName("Cluster "+i);
                  tempCluster.setDataPoints(arrivableObjects);
                  clusterList.add(tempCluster);
             }
         }

         for(int i=0;i<clusterList.size();i++){
             for(int j=0;j<clusterList.size();j++){
                  if(i!=j){
                      Cluster clusterA=clusterList.get(i);
                      Cluster clusterB=clusterList.get(j);

                      List<DataPoint> dpsA=clusterA.getDataPoints();
                      List<DataPoint> dpsB=clusterB.getDataPoints();

                      boolean flag=mergeList(dpsA,dpsB);
                      if(flag){
                          clusterList.set(j, new Cluster());
                      }
                  }
             }
         }

         return clusterList;
    }

   

    public void displayCluster(List<Cluster> clusterList){
        if(clusterList!=null){
            for(Cluster tempCluster:clusterList){
               if(tempCluster.getDataPoints()!=null&&tempCluster.getDataPoints().size()>0){
                    System.out.println("----------"+tempCluster.getClusterName()+"----------");
                    for(DataPoint dp:tempCluster.getDataPoints()){
                       System.out.println(dp.getDataPointName());
                    }
                }
            }
        }
    }

   
    private double getDistance(DataPoint dp1,DataPoint dp2){
        double distance=0.0;
        double[] dim1=dp1.getDimensioin();
        double[] dim2=dp2.getDimensioin();
        if(dim1.length==dim2.length){
            for(int i=0;i<dim1.length;i++){
                double temp=Math.pow((dim1[i]-dim2[i]), 2);
                distance=distance+temp;
            }
            distance=Math.pow(distance, 0.5);
            return distance;
        }
        return distance;
    }

   
   private List<DataPoint> isKeyAndReturnObjects(DataPoint dataPoint,List<DataPoint> dataPoints,double radius,int ObjectNum){
       List<DataPoint> arrivableObjects=new ArrayList<DataPoint>(); //用来存储所有直接密度可达对象

       for(DataPoint dp:dataPoints){
          double distance=getDistance(dataPoint,dp);
          if(distance<=radius){
              arrivableObjects.add(dp);
          }
       }

       if(arrivableObjects.size()>=ObjectNum){
           dataPoint.setKey(true);
           return arrivableObjects;
       }

       return null;
   }

  
   private boolean isContain(DataPoint dp,List<DataPoint> dps){
      boolean flag=false;
      String name=dp.getDataPointName().trim();
      for(DataPoint tempDp:dps){
         String tempName=tempDp.getDataPointName().trim();
         if(name.equals(tempName)){
             flag=true;
             break;
         }
      }

      return flag;
   }

  
   private boolean mergeList(List<DataPoint> dps1,List<DataPoint> dps2){
       boolean flag=false;

       if(dps1==null||dps2==null||dps1.size()==0||dps2.size()==0){
           return flag;
       }

       for(DataPoint dp:dps2){
          if(dp.isKey()&&isContain(dp,dps1)){
             flag=true;
             break;
          }
       }

       if(flag){
           for(DataPoint dp:dps2){
              if(!isContain(dp,dps1)){
                  DataPoint tempDp=new DataPoint(dp.getDimensioin(),dp.getDataPointName(),dp.isKey());
                  dps1.add(tempDp);
              }
           }
       }


       return flag;
   }

   public static void main(String[] args){
       ArrayList<DataPoint> dpoints = new ArrayList<DataPoint>();
      
       double[] a={2,3};
       double[] b={2,4};
       double[] c={1,4};
       double[] d={1,3};
       double[] e={2,2};
       double[] f={3,2};

       double[] g={8,7};
       double[] h={8,6};
       double[] i={7,7};
       double[] j={7,6};
       double[] k={8,5};

       double[] l={100,2};//孤立点


       double[] m={8,20};
       double[] n={8,19};
       double[] o={7,18};
       double[] p={7,17};
       double[] q={8,21};

       dpoints.add(new DataPoint(a,"a",false));
       dpoints.add(new DataPoint(b,"b",false));
       dpoints.add(new DataPoint(c,"c",false));
       dpoints.add(new DataPoint(d,"d",false));
       dpoints.add(new DataPoint(e,"e",false));
       dpoints.add(new DataPoint(f,"f",false));

       dpoints.add(new DataPoint(g,"g",false));
       dpoints.add(new DataPoint(h,"h",false));
       dpoints.add(new DataPoint(i,"i",false));
       dpoints.add(new DataPoint(j,"j",false));
       dpoints.add(new DataPoint(k,"k",false));

       dpoints.add(new DataPoint(l,"l",false));

       dpoints.add(new DataPoint(m,"m",false));
       dpoints.add(new DataPoint(n,"n",false));
       dpoints.add(new DataPoint(o,"o",false));
       dpoints.add(new DataPoint(p,"p",false));
       dpoints.add(new DataPoint(q,"q",false));

       ClusterAnalysis ca=new ClusterAnalysis();
       List<Cluster> clusterList=ca.doDbscanAnalysis(dpoints, 2, 4);
       ca.displayCluster(clusterList);

   }
}

   
}



所有均转载自:http://www.360doc.com/userhome.aspx?userid=7000788&cid=9

1 0
原创粉丝点击