常用聚类算法的比较与分析K-pototypes算法K-pototypes算法结合了K-means方法和K-means方法改进的K-modes方法。与K-means方法相比,K-pototypes算法可以处理符号属性。

CLARANS算法(划分法)CLARANS算法,即随机搜索聚类算法,是一种划分聚类方法。它首先随机选取一个点作为当前点,然后在其周围随机检查一些不超过参数Maxneighbor的相邻点。如果找到一个更好的邻点,就把它移到邻点,否则就当作局部最小值。然后,随机选取一个点寻找另一个局部最小值,直到局部最小值的个数达到用户的要求。算法要求所有聚类的对象都必须预先记忆,数据集需要多次扫描,对于大数据在时间和空间上都相当复杂。虽然引入了R- tree结构来提高其性能,使其能够处理基于磁盘的大型数据库,但R*- tree的构建和维护代价太高。该算法对脏数据和异常数据不敏感,但对数据对象和人的顺序极其敏感,只能处理凸形或球形边界聚类。

BIRCH算法(层次法)BIRCH算法,即平衡迭代约简聚类方法,其核心是用一个聚类特征3-tuple来表示一个聚类的相关信息,使一个聚类的点可以用相应的聚类特征来表示,而不是一组特定的点。它通过构造满足分枝因子和聚类直径约束的聚类特征树来发现聚类。BIRCH算法通过聚类特征可以方便地计算类内和类间的中心、半径、直径、距离。算法的聚类特征树是一棵高度平衡的树,有两个参数分支因子B和类直径t,分支指定了树中每个节点的最大子节点数,而类直径反映了对一类点的直径的限制,即这些点可以归入一类的范围,非叶节点是其子节点的最大关键字,可以根据这些关键字进行索引,它汇总了其子节点的信息。

特征树可以动态构造,所以所有数据都可以从外存中一个一个地读取,而不是从内存中读取。新数据项总是被插入到树中最近的叶子中。如果插入人后叶的直径大于类直径T,则叶节点被拆分。其他叶子节点也需要检查是否超过分支因子来判断是否分裂,直到数据插入叶子且不超过准直直径,每个非叶子节点的子节点数不大于分支因子。该算法还可以通过改变类直径来修改特征树的大小,并控制其记忆容量。

BIRCH算法通过一次扫描就能很好的聚类,所以适用于大数据。给定M兆字节的内存空间,空间复杂度为O(M),时间间复杂度为O(dNBlnB(M/P))。其中d是维度,n是节点数,p是内存页的大小,b是由p决定的分支因子,I/O成本与数据量线性相关。BIRCH算法只适用于类的凸形和球形分布,对于不可见的高维数据不可行,因为BIRCH算法需要提供正确的簇数和直径。

CURE算法(层次法)CURE算法采用代表点的聚类方法。该算法首先将每个数据点视为一个类,然后合并最近的类,直到类的数目达到要求的数目。CURE算法改进了传统的类表示方法,避免了用所有的点或圆心和半径来表示一个类。而是从每个类中提取固定数量的均匀分布的点作为代表点来描述这个类,并将这些点乘以适当的收缩因子,使其更接近类的中心。一个类用一个代表点来表示,这样类的外延就可以扩展到一个非球面的形状,并且可以调整类的形状来表示那些非球面的类。此外,收缩因子的使用降低了语音对聚类的影响。CURE算法将随机采样和分割相结合,提高了算法的空间和时间效率,并使用堆和K-d树结构提高了算法的效率。

DBSCAN算法(基于密度的方法)DBSCAN算法是基于密度的聚类算法。该算法利用类的密度连通性,可以快速找到任意形状的类。基本思想是:对于一个类中的每个对象,给定半径域中的对象数量不应小于给定的最小数量。在DBSCAN算法中,发现一个类的过程是基于这样一个事实,即一个类可以由其任何一个核心对象来确定。为了找到一个类,DBSCAN首先从对象集D中找到任意一个对象P,然后在D中找到关于临界Eps和最小对象数Minpts从P密度可以到达的所有对象。如果P是核心对象,即半径为Eps的P的邻域包含不少于Minpts,那么根据算法,可以找到一类关于参数Eps和Minpts。如果p是一个边界点,p的具有半直径Eps的邻域包含的对象比Minpts少,p被临时标记为一个噪声点。然后,DBSCAN处理D中的下一个对象.

密度可达对象的获取是通过连续执行区域查询来实现的。区域查询返回指定区域中的所有对象。为了有效地执行区域查询,DBSCAN算法使用了空间查询R树结构。在聚类之前,必须为所有数据建立一个R*树。另外,DBSCAN要求用户指定一个全局参数Eps(为了减少计算量,参数Minpts是预先确定的)。为了确定该值,DBSCAN计算任意对象与其第k个最近对象之间的距离。然后根据得到的距离,从小到大排序,画出排序后的图,称为k-dist图。k-dist图中的横坐标表示数据对象与其第k个最近对象之间的距离;纵坐标是对应于某个k-dist距离值的数据对象的数量。R*-树的建立和k-dist图的绘制非常耗时。此外,为了获得更好的聚类结果,用户必须根据k-dist图通过试错法选择一个合适的Eps值。DBSCAN算法直接对整个数据集进行聚类,不需要任何预处理。当数据量非常大时,必须有大量的内存来支持,I/O消耗也非常大。其时间复杂度为O(nlogn)(n为数据量),聚类过程的大部分时间花费在区域查询操作上。DBSCAN算法对参数Eps和Minpts非常敏感,这两个参数很难确定。

CLIQUE算法(基于密度和基于网格的算法的组合)CLIQUE算法是一种自动子空间聚类算法。该算法使用top-up方法寻找每个子空间的聚类单元。CLUQUE算法主要用于发现高维数据空间中的低维聚类。为了得到D维空间聚类,必须组合给出所有d-1个子空间聚类,导致算法的空间和时间效率较低,并且需要用户输入两个参数:数据值空间的等间隔距离和宽密度值。这两个参数与样本木材数据密切相关,用户通常很难确定它们。CLIQUE算法对数据输入顺序不敏感。

基于以上分析,我们得到了各种聚类算法的比较结果,结论如下:

K-means算法的详细讲解及其实现流程K-means算法应该是聚类算法中最基础但也是最重要的算法。算法流程如下:

随机抽取

计算从其他点到k个质心的距离;

如果点P更靠近第n个质心,则它属于聚类n,并且它被标记为点p.label=n,其中n =k;

计算同一簇即同一标签中的点向量的平均值作为新的质心;

迭代,直到所有质心不变,即算法结束。

当然,算法的实现方式有很多种,比如在选取初始质心时随机选取K个或K个最远点等。方法不一样。

k值的估计:对于k值,我们必须事先知道,这也是kmeans算法的一个缺点。当然,我们可以用很多方法来估计k的值。本文用平均直径法估计k。

也就是说,首先把所有的点看作一个大的整体簇,计算点之间的平均距离作为簇的平均直径。在选择初始质心时,先选择最远的两个点,然后从这两个点出发,距离这两个点较远的点(这个点到之前选择的最远的两个点的距离大于整个簇的平均直径)可以作为新发现的质心,否则不作为质心。试想一下,如果用平均半径或平均直径作为指标,如果估计的K值大于或等于真实的K值,也就是真实的团簇数,那么这个指标的上升趋势会非常缓慢,但是如果给定的K值小于真实的团簇数,这个指标肯定会大幅上升。

根据这种估计思想,可以估计出k的正确值,得到k个初始质心。然后,按照上述算法流程继续迭代,直到所有质心都不变,从而成功实现算法。如下图所示:

图一。K值的估计

我们知道K-means总是收敛的,也就是说K-means算法会达到一个稳定的状态,在这个状态下所有点不会从一个簇转移到另一个簇,所以质心不会改变。这里引入一个剪枝优化,即K-means最明显的收敛过程会发生在算法的前期,所以在某些情况下,为了增加算法的执行效率,可以替换上述算法的第五步,采用迭代直到只有1%~3%的点影响质心或者迭代直到只有1%~3%的点改变聚类。

K-means适用于大多数数据类型,简单有效。但它的缺点是需要知道确切的K值,而且它可以不处理异常团簇,如球形团簇、不同大小和密度的团簇、环形团簇等。

本文主要讲解和实现算法,所以代码实现暂不考虑面向对象的思想,采用面向过程的实现方法。如果数据是多维的,可能有必要进行数据预处理,如规范化,并修改与代码相关的方法。

算法实现清单1。Kmeans算法代码实现

导入Java . io . buffered reader;

导入Java . io . filereader;

导入Java . io . io exception;

导入Java . io . printstream;

导入Java . text . decimal format;

导入Java . util . ArrayList;

导入Java . util . comparator;

导入Java . util . priority queue;

导入Java . util . queue;

公共类Kmeans {

私有类节点{

int标签;//label用于记录该点属于哪个簇。

double[]属性;

公共节点(){

属性=new double[100];

}

}

私有类节点比较器{

Node nodeOne

节点nodeTwo

双倍距离;

public void compute() {

double val=0;

for(int I=0;I《维度;i) {

val=(this . node one . attributes[I]-this . node two . attributes[I])*

(this . node one . attributes[I]-this . node two . attributes[I]);

}

this.distance=val

}

}

私有数组列表《Node》数组列表;

私有数组列表《Node》 centroidList;

私人双倍平均工资;

private int维度;

专用队列《NodeComparator》 FsQueue=

New PriorityQueue 《NodeComparator》 (150,//用于对任意两点之间的距离进行排序,从最大到最小。

新的比较仪《NodeComparator》 () {

public int compare(node comparator 1,node comparator 2){

如果(一。距离《两。距离》

返回1;

else if(一。距离& gt两个。距离)

return-1;

其他

返回0;

}

});

公共void setKmeansInput(字符串路径){

尝试{

缓冲读取器br=新缓冲读取器(新文件读取器(路径));

字符串潜艇用热中子反应堆(submarine thermal reactor的缩写)

string[]strArray;

ArrayList=new ArrayList 《Node》();

while ((str=br.readLine())!=null) {

strArray=str . split();

dimension=strArray.length

节点node=新节点();

for(int I=0;我维度;i) {

节点。attributes[I]=double。parse double(strArray[I]);

}

arraylist.add(节点);

}

br。close();

} catch (IOException e) {

e。printstacktrace();

}

}

public void computeTheK() {

int CNT tuple=0;

for(int I=0;我《数组列表。size()-1;i) {

for(int j=I 1;j《数组列表。size();j) {

节点比较器node comp=新节点比较器();

节点组件。节点一=新节点();

节点组件。节点二=新节点();

for(int k=0;k”维度;k) {

节点组件。节点一。attributes[k]=数组列表。得到(I).属性[k];

节点组件。节点二。attributes[k]=数组列表。得到(j).属性[k];

}

节点组件。compute();

平均is=节点组件。距离;

fs队列。add(节点组件);

cntTuple

}

}

平均为/=cntTuple;//计算平均距离

选择质心(fs队列);

}

公共double getDistance(节点一,节点二){//计算两点间的欧氏距离

double val=0;

for(int I=0;我维度;i) {

val=(一。属性[I]-二。属性[I])*(一。属性[I]-二。属性[I]);

}

返回英国压力单位

}

公共void chooseCentroid(队列《NodeComparator》队列){

centroidList=new ArrayList 《Node》();

布尔标志=假;

而(!queue.isEmpty()) {

布尔判断=假

布尔判断wo=假;

节点比较器NC=fs队列。poll();

如果(数控距离"平均是"

打破;//如果接下来的元组,两节点间距离小于平均距离,则不继续迭代

如果(!标志){

质心列表。添加(NC。节点一);//先加入所有点中距离最远的两个点

质心列表。添加(NC。节点二);

标志=真

} else {//之后从之前已加入的最远的两个点开始,找离这两个点最远的点,

//如果距离大于所有点的平均距离,则认为找到了新的质心,否则不认定为质心

for(int I=0;我《质心表。size();i) {

节点testnode=centroidlist。get(I);

if(centroidlist。包含(NC。nodeone)| | get distance(testnode,nc.nodeOne) 《averageDis) {

judgeOne=真

}

if(centroidlist。包含(NC。node two)| | get distance(test node,nc.nodeTwo) 《averageDis) {

judgeTwo=true

}

}

如果(!judgeOne) {

质心列表。添加(NC。节点一);

}

如果(!judgeTwo) {

质心列表。添加(NC。节点二);

}

}

}

}

公共void文件(数组列表《Node》质心){

int CNT=1;

int cntEnd=0;

int num label=质心。size();

while (true) {//迭代,直到所有的质心都不变化为止

布尔标志=假;

for(int I=0;我《数组列表。size();i) {

double dis=0x7fffffff

CNT=1;

for(int j=0;形心。size();j) {

节点node=质心。get(j);

if (getDistance(arraylist.get(i),node) 《dis) {

dis=get distance(ArrayList。get(I),node);

arraylist.get(i).标签=计数

}

(cannot)不能

}

}

int j=0;

数字标签-=1;

虽然(j数字标签){

int c=0;

节点node=新节点();

for(int I=0;我《数组列表。size();i) {

if (arraylist.get(i).label==j 1) {

for(int k=0;k”维度;k) {

节点。attributes[k]=数组列表。得到(I).属性[k];

}

c;

}

}

十进制格式df=新的十进制格式(#。# # # );//保留小数点后三位

double[]属性列表=new double[100];

for(int I=0;我维度;i) {

属性列表[I]=double。解析double(df。格式(节点。属性[I]/c));

if (attributelist[i]!=centroid.get(j)。属性[i]) {

centroid.get(j)。attributes[I]=attribute list[I];

flag=true

}

}

如果(!标志){

cntEnd

If (cntEnd==numLabel) {//如果所有质心都相同,则跳出循环。

打破;

}

}

j;

}

If (cntEnd==numLabel) {//如果所有质心都相同,则成功

System.out.println("成功运行kmeans。");

打破;

}

}

}

public void printKmeansResults(字符串路径){

尝试{

print stream out=new print stream(path);

computeTheK();

doIteration(质心列表);

out.println("有" centroidList.size()"簇!");

for(int I=0;I《ArrayList . size();i) {

out . print("(");

for(int j=0;j《维度-1;j) {

out.print(arraylist.get(i))。属性[j]""

}

out.print(arraylist.get(i))。属性[dimension-1]"));

out.println("属于集群" arraylist.get(i)。标签);

}

out . close();

System.out.println("请检查结果in:" path ");

} catch (IOException e) {

e . printstacktrace();

}

}

公共静态void main(String[] args) {

k means k means=new k means();

k means . setkmeansinput(" c:/k means . txt ");

k means . printkmeans results(" c:/kmeans results . txt ");

}

}

测试数据给出了一组简单的二维测试数据:

清单2。Kmeans算法测试数据

1,1

2,1

1,2

2,2

6,1

6,2

7,1

7,2

1,5

1,6

2,5

2,6

6,5

6,6

7,5

7,6

运行结果清单3。Kmeans算法运行结果

有4个集群!

(1.0,1.0)属于群集1

(2.0,1.0)属于群集1

(1.0,2.0)属于群集1

(2.0,2.0)属于群集1

(6.0,1.0)属于群集3

(6.0,2.0)属于群集3

(7.0,1.0)属于群集3

(7.0,2.0)属于群集3

(1.0,5.0)属于群集4

(1.0,6.0)属于群集4

(2.0,5.0)属于群集4

(2.0,6.0)属于群集4

(6.0,5.0)属于群集2

(6.0,6.0)属于群集2

(7.0,5.0)属于群集2

(7.0,6.0)属于群集2

层次聚类算法的详细说明和层次聚类的介绍。层次聚类分为内聚层次聚类和分裂层次聚类。

内聚层次聚类是指初始阶段将每个点视为一个簇,然后每次合并两个最接近的簇。当然,邻近性的定义需要指定簇的邻近性标准。

分裂层次聚类是在初始阶段把所有点看成一个簇,然后一次分裂一个簇,直到最后只剩下一个点簇。

本文将详细介绍内聚层次聚类算法。

对于内聚层次聚类,指定聚类的邻域准则是一个非常重要的环节。在这里,我们介绍三个常用的标准,即最大值、最小值和组平均值。如下图所示:

图二。分层聚类计算标准

内聚层次聚类算法也是一个迭代过程。算法流程如下:

每次最接近的两个簇合并,我们就把这两个合并的簇叫做合并簇。

如果采用最大值准则,则选择其他类与合并类中两个最远点之间的距离作为类之间的接近度。如果采用最小准则,则其他聚类与合并后的聚类中最近的两个点之间的距离作为聚类之间的接近度。如果采用组平均准则,则将其他类的所有点与合并类之间的平均距离作为类之间的接近度。

重复步骤1和2,直到只剩下一个簇。

算法的例子让让我们看下面的例子:

下图是一个包含五个点的坐标系:

图3。分层聚类的示例

下表是这五个点的欧几里德距离矩阵:

表1。欧氏距离原始矩阵

根据算法流程,我们首先找出最近的两个聚类,P3和P4。

P3和P4合并为{P3和P4},矩阵根据最小原则更新如下:

最小距离({P3,P4},P1)=1.32;

最小距离({P3,P4},P2)=1.56;

最小距离({P3,P4},P5)=0.70;

表2.欧式距离更新矩阵一

接着继续找出距离最近的两个簇,{P3,P4},P5。

合并{ P4P3 },P5为{P3,P4,P5},根据部原则继续更新矩阵:

MIN.distance(P1,{P3,P4,P5 })=1.32;

MIN.distance(P2,{P3,P4,P5 })=1.56;

表3.欧式距离更新矩阵2

接着继续找出距离最近的两个簇P2P1。

合并P2P1为{ P2P1 },根据部原则继续更新矩阵:

最小距离({P1,P2},{P3,P4,P5})=1.32

表4.欧式距离更新矩阵3

最终合并剩下的这两个簇即可获得最终结果,如下图:

图4.层次聚类举例结果

麦克斯,组平均算法流程同理,只是在更新矩阵时将上述计算簇间距离变为簇间两点最大欧式距离,和簇间所有点平均欧式距离即可。

算法实现清单4.层次聚类算法代码实现

导入Java。io。缓冲阅读器;

导入Java。io。filereader

导入Java。io。io异常;

导入Java。io。printstream

导入Java。文字。十进制格式;

导入Java。util。ArrayList

公共类层次{

私有double[][]矩阵;

私有int维度;//数据维度

私有类节点{

double[]属性;

公共节点(){

属性=new double[100];

}

}

私有数组列表《Node》数组列表;

私有班级模型{

int x=0;

int y=0;

两倍值=0;

}

私有模型最小模型=新模型();

私有double getDistance(节点一,节点二){//计算两点间的欧氏距离

double val=0;

for(int I=0;我维度;i) {

val=(一。属性[I]-二。属性[I])*(一。属性[I]-二。属性[I]);

}

返回数学。sqrt(val);

}

私有void loadMatrix() {//将输入数据转化为矩阵

for(int I=0;我矩阵。长度;i) {

for(int j=I 1;j矩阵。长度;j) {

double distance=get distance(ArrayList。get(I),数组列表。get(j));

矩阵[i][j]=距离;

}

}

}

私有模型findMinValueOfMatrix(double[][]matrix){//找出矩阵中距离最近的两个簇

模型模型=新模型();

double min=0x7fffffff

for(int I=0;我矩阵。长度;i) {

for(int j=I 1;j矩阵。长度;j) {

if(min)矩阵[I][j]矩阵[I][j]!=0) {

最小值=矩阵[I][j];

模型。x=I;

模型。y=j;

模型。值=矩阵[I][j];

}

}

}

退货模式;

}

私有void进程分层(字符串路径){

尝试{

打印流输出=新打印流(路径);

while (true) {//凝聚层次聚类迭代

out . println(矩阵更新如下:);

for(int I=0;我矩阵。长度;i) {//输出每次迭代更新的矩阵

for(int j=0;j矩阵。长度-1;j) {

输出打印(新的十进制格式(# .00 )。format(matrix[I][j])");

}

out.println(新的十进制格式(# .00 )。格式(矩阵[I][矩阵。长度-1]);

}

出去。println();

minModel=findMinValueOfMatrix(矩阵);

if (minModel.value==0) {//当找不出距离最近的两个簇时,迭代结束

打破;

}

出去。println(合并(最小型号。x1)(最小型号。y 1));

out . println(距离为:最小模型。价值);

矩阵[最小模型。x][最小模型。y]=0;//更新矩阵

for(int I=0;我矩阵。长度;i) {//如果合并了点第一亲代与p2,则只保留p1,p2其中之一与其他点的距离,取较小值

if(矩阵[I][最小模型。x]》=矩阵[I][最小模型。y){

矩阵[I][最小模型。y]=0;

}否则{

矩阵[I][最小模型。x]=0;

}

if(矩阵[最小模型。x][I]=矩阵[最小模型。y][I]){

矩阵[最小模型。y][I]=0;

}否则{

矩阵[最小模型。x][I]=0;

}

}

}

出去。close();

system . out . println(请检查结果在:路径);

} catch(异常e) {

e。printstacktrace();

}

}

公共void setInput(字符串路径){

尝试{

buffered reader br=new buffered reader(new file reader(path));

字符串str

string[]strArray;

ArrayList=new ArrayList 《Node》();

while ((str=br.readLine())!=null) {

strArray=str.split("");

dimension=strArray.length

Node node=新节点();

for(int I=0;I《维度;i) {

node . attributes[I]=double . parse double(strArray[I]);

}

arraylist.add(节点);

}

matrix=new double[ArrayList . size()][ArrayList . size()];

load matrix();

br . close();

} catch (IOException e) {

e . printstacktrace();

}

}

公共void printOutput(字符串路径){

processHierarchical(路径);

}

公共静态void main(String[] args) {

hierarchy hi=new Hierarchical();

hi . set input(" c:/hierarchical . txt ");

hi . print output(" c:/hierarchical _ results . txt ");

}

}

测试数据给出一组简单的二维测试数据。

5.层次聚类算法的测试数据

0.7,1.2

0.8,2

2,1

2.6,0.8

2.5,1.5

运行结果清单6。层次聚类算法的运行结果

矩阵更新如下:

.00 .81 1.32 1.94 1.82

.00 .00 1.56 2.16 1.77

.00 .00 .00 .63 .71

.00 .00 .00 .00 .71

.00 .00 .00 .00 .00

联合收割机3 4

这个距离是:0。36860 . 68686868661

矩阵更新如下:

.00 .81 1.32 .00 1.82

.00 .00 1.56 .00 1.77

.00 .00 .00 .00 .00

.00 .00 .00 .00 .71

.00 .00 .00 .00 .00

组合4 5

距离是:0。36860 . 66868686861

矩阵更新如下:

.00 .81 1.32 .00 .00

.00 .00 1.56 .00 .00

.00 .00 .00 .00 .00

.00 .00 .00 .00 .00

.00 .00 .00 .00 .00

组合1 2

这个距离是:0。38860 . 68686868686

矩阵更新如下:

.00 .00 1.32 .00 .00

.00 .00 .00 .00 .00

.00 .00 .00 .00 .00

.00 .00 .00 .00 .00

.00 .00 .00 .00 .00

联合收割机1 3

距离是:1。36860 . 66868686861

矩阵更新如下:

.00 .00 .00 .00 .00

.00 .00 .00 .00 .00

.00 .00 .00 .00 .00

.00 .00 .00 .00 .00

.00 .00 .00 .00 .00

DBSCAN算法的详细说明和实现考虑了一种情况,当点的分布不均匀,形状不规则时,Kmeans算法和层次聚类算法将面临失败的风险。

下列坐标系:

图5。DBSCAN算法示例

我们可以看到上面的点的密度是不均匀的,所以我们考虑使用基于密度的聚类算法:DBSCAN。

该算法设置扫描半径Eps,并指定扫描半径内的密度值。如果当前点半径内的密度大于或等于设定的密度值,则将当前点设定为核心点;如果一个点正好在一个核心点的半径边上,则将该点设置为边界点;如果一个点既不是核心点也不是边界点,那么这个点就是噪声点。

删除噪点。

距离在扫描半径内的所有核心点都被赋予连接性的边。

每组连接的核心点被标记为一个簇。

将所有边界点分配给相应核心点的聚类总数。

上图的坐标系中显示了算法过程的一个示例。我们将扫描半径Eps设置为1.5,密度阈值设置为3。然后,通过上面的算法过程,我们可以得到如下图:

图6。dbscan算法的示例结果

通过计算扫描半径内各点之间的欧氏距离及其密度值,可以判断这些点属于核心点、边界点还是噪声点。因为我们将扫描半径设置为1.5,密度阈值设置为3,所以:

点P0为边界点,因为以其为中心的扫描半径内只有P0和P1两点;

P1是核心点,因为以它为中心的扫描半径内有P0、P1、P2、P4四个点;

P8是噪声点,因为它既不是核心点,也不是边界点;

其他点等等。

算法实现清单7。dbscan算法的代码实现

导入Java . io . buffered reader;

导入Java . io . filereader;

导入Java . io . io exception;

导入Java . io . printstream;

导入Java . util . ArrayList;

导入Java . util . hashmap;

导入Java . util . iterator;

导入Java . util . map;

公共类DBSCAN {

private int维度;//数据维度

私双eps=1.5

private int阈值=3;

私有双距离[][];

私有映射《Integer, Integer》 id=new HashMap 《Integer, Integer》();

private int count clusters=0;

私有数组列表《Integer》关键点列表=新数组列表《Integer》();//

私有int[]标志;//标记边缘点

私有类边缘{

int p,q;

双倍重量;

}

私有类节点{

double[]属性;

公共节点(){

属性=new double[100];

}

}

二等兵ArrayL

private ArrayList《Edge》 edgeList;

private double getDistance(Node one, Node two) {// 计算两点间的欧氏距离

double val = 0;

for (int i = 0; i 《 dimension; ++i) {

val += (one.attributes[i] - two.attributes[i]) * (one.attributes[i] - two.attributes[i]);

}

return Math.sqrt(val);

}

public void loadEdges() {// 给所有在扫描半径内的核心点之间加边,标记边界点并且自动忽略噪声点

edgeList = new ArrayList《Edge》();

flags = new int[nodeList.size()];

int[] countPoint = new int[nodeList.size()];

for (int i = 0; i 《 countPoint.length; ++i) {

countPoint[i] = 1;// 每一个点一开始都是核心点

}

for (int i = 0; i 《 nodeList.size(); ++i) {

for (int j = i + 1; j 《 nodeList.size(); ++j) {

distance[i][j] = getDistance(nodeList.get(i), nodeList.get(j));

if (distance[i][j] 《= eps) {// 两点间距离小于扫描半径

countPoint[i]++;

if (countPoint[i] 》 0 && countPoint[i] 《 threshold) {

flags[i] = j;// 记录边界点

}

if (countPoint[i] 》= threshold) {// 如果记录当前点的扫描半径内密度值大于或等于给定阈值

flags[i] = 0;

if (!keyPointList.contains(i)) {

keyPointList.add(i);

}

}

countPoint[j]++;

if (countPoint[j] 》 0 && countPoint[j] 《 threshold) {

flags[j] = i;// 记录边界点

}

if (countPoint[j] 》= threshold) {// 如果记录当前点的扫描半径内密度值大于或等于给定阈值

flags[j] = 0;

if (!keyPointList.contains(j)) {

keyPointList.add(j);

}

}

}

}

}

for (int i = 0; i 《 keyPointList.size(); ++i) {

for (int j = i + 1; j 《 keyPointList.size(); ++j) {

Edge edge = new Edge();

edge.p = keyPointList.get(i);

edge.q = keyPointList.get(j);

edge.weight = distance[edge.p][edge.q];

if (edge.weight 《= eps) {

if (!id.containsKey(edge.p)) {// 为后期使用并查集求连通分量做准备

id.put(edge.p, edge.p);

}

if (!id.containsKey(edge.q)) {

id.put(edge.q, edge.q);

}

edgeList.add(edge);

}

}

}

}

public void setInput(String path) {

try {

BufferedReader br = new BufferedReader(new FileReader(path));

String str;

String[] strArray;

nodeList = new ArrayList《Node》();

while ((str = br.readLine()) != null) {

strArray = str.split(“,”);

dimension = strArray.length;

Node node = new Node();

for (int i = 0; i 《 dimension; ++i) {

node.attributes[i] = Double.parseDouble(strArray[i]);

}

nodeList.add(node);

}

distance = new double[nodeList.size()][nodeList.size()];

loadEdges();

br.close();

} catch (IOException e) {

e.printStackTrace();

}

}

public void union(int p, int q) {// 并操作

int a = find(p);

int b = find(q);

if (a != b) {

id.put(a, b);

}

}

public int find(int p) {// 查操作

if (p != id.get(p)) {

id.put(p, find(id.get(p)));

}

return id.get(p);

}

public void processDBSCAN(String path) {

try {

PrintStream out = new PrintStream(path);

out.println(“核心点为: ” + keyPointList);

out.println();

for (int i = 0; i 《 edgeList.size(); ++i) {

out.println(“核心点 (” + edgeList.get(i).p + “ ” +

edgeList.get(i).q + “) 之间的距离为: ” + edgeList.get(i).weight);

}

for (int i = 0; i 《 edgeList.size(); ++i) {

union(edgeList.get(i).p, edgeList.get(i).q);// 利用并查集将点集变为连通分量

}

Iterator《Integer》 it = id.keySet().iterator();

while (it.hasNext()) {

int key = it.next();

if (id.get(key) == key) {// 利用并查集得到强连通分量个数

++countClusters;

}

}

out.println();

for (int i = 0; i 《 flags.length; ++i) {

if (flags[i] != 0) {

out.println(“点” + i + “属于点” + flags[i] + “所在的簇”);

}

}

out.println();

out.println(“由核心点连通分量数量得知共有: ” + countClusters + “个簇”);

out.close();

System.out.println(“Please check results in: ” + path);

} catch (Exception e) {

e.printStackTrace();

}

}

public void printOutput(String path) {

processDBSCAN(path);

}

public static void main(String[] args) {

DBSCAN dbscan = new DBSCAN();

dbscan.setInput(“c:/dbscan.txt”);

dbscan.printOutput(“c:/dbscan_results.txt”);

}

}

清单 8. DBSCAN 算法测试数据

2,1

2,2

2,3

3,3

3,4.5

4,1

4,2

4,3

6,2

8,1

8,2

8,3

9,1

10,1

10,2

10,3

清单 9. DBSCAN 算法运行结果

核心点为: [1, 2, 3, 6, 7, 9, 10, 12, 13, 14]

核心点 (1 2) 之间的距离为: 1.0

核心点 (1 3) 之间的距离为: 1.4142135623730951

核心点 (2 3) 之间的距离为: 1.0

核心点 (3 6) 之间的距离为: 1.4142135623730951

核心点 (3 7) 之间的距离为: 1.0

核心点 (6 7) 之间的距离为: 1.0

核心点 (9 10) 之间的距离为: 1.0

核心点 (9 12) 之间的距离为: 1.0

核心点 (10 12) 之间的距离为: 1.4142135623730951

核心点 (12 13) 之间的距离为: 1.0

核心点 (12 14) 之间的距离为: 1.4142135623730951

核心点 (13 14) 之间的距离为: 1.0

连通点 1 和点 2

连通点 1 和点 3

连通点 3 和点 6

连通点 3 和点 7

连通点 9 和点 10

连通点 9 和点 12

连通点 12 和点 13

连通点 12 和点 14

点 1、点 2、点 3、点 6、点 7 同属于簇 1

点 9、点 10、点 12、点 13、点 14 同属于簇 2

点 0 属于点 1 所在的簇

点 4 属于点 3 所在的簇

点 5 属于点 6 所在的簇

点 11 属于点 10 所在的簇

点 15 属于点 14 所在的簇

由核心点连通分量数量得知共有: 2 个簇

Birch 是一种能够高效处理大数据聚类的基于树的层次聚类算法。设想这样一种情况,一个拥有大规模数据的数据库,当这些数据被放入主存进行聚类处理时,一般的聚类算法则没有对应的高效处理能力,这时 Birch 算法是最佳的选择。

Birth 不仅能够高效地处理大数据聚类,并且能最小化 IO 花销。它不需要扫描全局数据已经现有的簇。

聚类特征 CF=(N,LS,SS),其中 N 代表簇中点的个数,LS 代表簇中代表簇中各点线性和,SS 代表簇中各点的平方和距离。聚类特征被应用于 CF 树中,CF 树是一种高度平衡树,它具有两个参数:平衡因子 B 和簇半径阈值 T。其中平衡因子 B 代表每一个非叶子节点最多能够引入 B 个实体条目。

叶子节点最多只能包含 L 个实体条目,并且它们具有前向后向指针,这样可以彼此链接起来。

树的大小取决于簇半径阈值 T 的大小。

从根节点开始,递归查找与要插入的数据点距离最近的叶子节点中的实体条目,递归过程选择最短路径。

比较上述计算出的数据点与叶子节点中实体条目间的最近距离是否小叶簇半径阈值 T,小于则吸收该数据点。否则执行下一步。

判断当前条目所在的叶节点个数是否小于 L,若小于则直接将该数据点插入当前点。否则分裂叶子节点,分裂过程是将叶子节点中距离最远的两个实体条目变为新的两个叶子节点,其他条目则根据距离最近原则重新分配到这两个新的叶子节点中。删除原来的叶子节点并更新 CF 树。

若不能将所有数据点加入 CF 树中,则考虑增加簇半径阈值 T,并重新更新 CF 树直至所有的数据点被加入 CF 树为止。

在数据集中选择样本数据。

将上述样本数据划分为 P 个同样大小的划分。

将每个划分中的点聚成 m/pq 个簇,共得到 m/q 个簇。过程中需删除噪声点。

对上述 m/q 个簇进行聚类直至剩下 k 个簇。

继续删除离群点。

将剩下的点指派到最近的簇完成聚类过程。

聚类算法是数据挖掘算法中最为重要的部分之一,算法种类繁多,应用场景也各有不同,本文章提到的聚类算法为常见常用的一些较为基本的算法,对于其他的聚类算法,如最小生成树聚类,CLIQUE,DENCLUE,SOM 等等如有兴趣,读者可以自行查找相关资料进行学习。本文旨在提高读者对算法本身的理解,代码实现过程及结果打印能够更好的帮助读者剖析算法,使读者能够更快的入门并掌握基本的聚类算法。