用C++实现DBSCAN聚类算法

 更新时间:2013年05月24日 16:45:52   作者:  
本篇文章是对使用C++实现DBSCAN聚类算法的方法进行了详细的分析介绍,需要的朋友参考下
这几天由于工作需要,对DBSCAN聚类算法进行了C++的实现。时间复杂度O(n^2),主要花在算每个点领域内的点上。算法很简单,现共享大家参考,也希望有更多交流。
 数据点类型描述如下:
复制代码 代码如下:

#include <vector>

 using namespace std;

 const int DIME_NUM=2;        //数据维度为2,全局常量

 //数据点类型
 class DataPoint
 {
 private:
     unsigned long dpID;                //数据点ID
     double dimension[DIME_NUM];        //维度数据
     long clusterId;                    //所属聚类ID
     bool isKey;                        //是否核心对象
     bool visited;                    //是否已访问
     vector<unsigned long> arrivalPoints;    //领域数据点id列表
 public:
     DataPoint();                                                    //默认构造函数
     DataPoint(unsigned long dpID,double* dimension , bool isKey);    //构造函数

     unsigned long GetDpId();                //GetDpId方法
     void SetDpId(unsigned long dpID);        //SetDpId方法
     double* GetDimension();                    //GetDimension方法
     void SetDimension(double* dimension);    //SetDimension方法
     bool IsKey();                            //GetIsKey方法
     void SetKey(bool isKey);                //SetKey方法
     bool isVisited();                        //GetIsVisited方法
     void SetVisited(bool visited);            //SetIsVisited方法
     long GetClusterId();                    //GetClusterId方法
     void SetClusterId(long classId);        //SetClusterId方法
     vector<unsigned long>& GetArrivalPoints();    //GetArrivalPoints方法
 };

这是实现:
复制代码 代码如下:

#include "DataPoint.h"

 //默认构造函数
 DataPoint::DataPoint()
 {
 }

 //构造函数
 DataPoint::DataPoint(unsigned long dpID,double* dimension , bool isKey):isKey(isKey),dpID(dpID)
 {
     //传递每维的维度数据
     for(int i=0; i<DIME_NUM;i++)
     {
         this->dimension[i]=dimension[i];
     }
 }

 //设置维度数据
 void DataPoint::SetDimension(double* dimension)
 {
     for(int i=0; i<DIME_NUM;i++)
     {
         this->dimension[i]=dimension[i];
     }
 }

 //获取维度数据
 double* DataPoint::GetDimension()
 {
     return this->dimension;
 }

 //获取是否为核心对象
 bool DataPoint::IsKey()
 {
     return this->isKey;
 }

 //设置核心对象标志
 void DataPoint::SetKey(bool isKey)
 {
     this->isKey = isKey;
 }

 //获取DpId方法
 unsigned long DataPoint::GetDpId()
 {
     return this->dpID;
 }

 //设置DpId方法
 void DataPoint::SetDpId(unsigned long dpID)
 {
     this->dpID = dpID;
 }

 //GetIsVisited方法
 bool DataPoint::isVisited()
 {
     return this->visited;
 }

 
 //SetIsVisited方法
 void DataPoint::SetVisited( bool visited )
 {
     this->visited = visited;
 }

 //GetClusterId方法
 long DataPoint::GetClusterId()
 {
     return this->clusterId;
 }

 //GetClusterId方法
 void DataPoint::SetClusterId( long clusterId )
 {
     this->clusterId = clusterId;
 }

 //GetArrivalPoints方法
 vector<unsigned long>& DataPoint::GetArrivalPoints()
 {
     return arrivalPoints;
 }

DBSCAN算法类型描述:
复制代码 代码如下:

#include <iostream>
 #include <cmath>

 using namespace std;

 //聚类分析类型
 class ClusterAnalysis
 {
 private:
     vector<DataPoint> dadaSets;        //数据集合
     unsigned int dimNum;            //维度
     double radius;                    //半径
     unsigned int dataNum;            //数据数量
     unsigned int minPTs;            //邻域最小数据个数

     double GetDistance(DataPoint& dp1, DataPoint& dp2);                    //距离函数
     void SetArrivalPoints(DataPoint& dp);                                //设置数据点的领域点列表
     void KeyPointCluster( unsigned long i, unsigned long clusterId );    //对数据点领域内的点执行聚类操作
 public:

     ClusterAnalysis(){}                    //默认构造函数
     bool Init(char* fileName, double radius, int minPTs);    //初始化操作
     bool DoDBSCANRecursive();            //DBSCAN递归算法
     bool WriteToFile(char* fileName);    //将聚类结果写入文件
 };

 聚类实现:
复制代码 代码如下:

#include "ClusterAnalysis.h"
 #include <fstream>
 #include <iosfwd>
 #include <math.h>

 /*
 函数:聚类初始化操作
 说明:将数据文件名,半径,领域最小数据个数信息写入聚类算法类,读取文件,把数据信息读入写进算法类数据集合中
 参数:
 char* fileName;    //文件名
 double radius;    //半径
 int minPTs;        //领域最小数据个数 
 返回值: true;    */
 bool ClusterAnalysis::Init(char* fileName, double radius, int minPTs)
 {
     this->radius = radius;        //设置半径
     this->minPTs = minPTs;        //设置领域最小数据个数
     this->dimNum = DIME_NUM;    //设置数据维度
     ifstream ifs(fileName);        //打开文件
     if (! ifs.is_open())                //若文件已经被打开,报错误信息
     {
         cout << "Error opening file";    //输出错误信息
         exit (-1);                        //程序退出
     }

     unsigned long i=0;            //数据个数统计
     while (! ifs.eof() )                //从文件中读取POI信息,将POI信息写入POI列表中
     {
         DataPoint tempDP;                //临时数据点对象
         double tempDimData[DIME_NUM];    //临时数据点维度信息
         for(int j=0; j<DIME_NUM; j++)    //读文件,读取每一维数据
         {
             ifs>>tempDimData[j];
         }
         tempDP.SetDimension(tempDimData);    //将维度信息存入数据点对象内

 //char date[20]="";
 //char time[20]="";
         ////double type;    //无用信息
         //ifs >> date;
 //ifs >> time;    //无用信息读入

         tempDP.SetDpId(i);                    //将数据点对象ID设置为i
         tempDP.SetVisited(false);            //数据点对象isVisited设置为false
         tempDP.SetClusterId(-1);            //设置默认簇ID为-1
         dadaSets.push_back(tempDP);            //将对象压入数据集合容器
         i++;        //计数+1
     }
     ifs.close();        //关闭文件流
     dataNum =i;            //设置数据对象集合大小为i
     for(unsigned long i=0; i<dataNum;i++)
     {
         SetArrivalPoints(dadaSets[i]);            //计算数据点领域内对象
     }
     return true;    //返回
 }

 /*
 函数:将已经过聚类算法处理的数据集合写回文件
 说明:将已经过聚类结果写回文件
 参数:
 char* fileName;    //要写入的文件名
 返回值: true    */
 bool ClusterAnalysis::WriteToFile(char* fileName )
 {
     ofstream of1(fileName);                                //初始化文件输出流
     for(unsigned long i=0; i<dataNum;i++)                //对处理过的每个数据点写入文件
     {
         for(int d=0; d<DIME_NUM ; d++)                    //将维度信息写入文件
             of1<<dadaSets[i].GetDimension()[d]<<'\t';
         of1 << dadaSets[i].GetClusterId() <<endl;        //将所属簇ID写入文件
     }
     of1.close();    //关闭输出文件流
     return true;    //返回
 }

 /*
 函数:设置数据点的领域点列表
 说明:设置数据点的领域点列表
 参数:
 返回值: true;    */
 void ClusterAnalysis::SetArrivalPoints(DataPoint& dp)
 {
     for(unsigned long i=0; i<dataNum; i++)                //对每个数据点执行
     {
         double distance =GetDistance(dadaSets[i], dp);    //获取与特定点之间的距离
         if(distance <= radius && i!=dp.GetDpId())        //若距离小于半径,并且特定点的id与dp的id不同执行
             dp.GetArrivalPoints().push_back(i);            //将特定点id压力dp的领域列表中
     }
     if(dp.GetArrivalPoints().size() >= minPTs)            //若dp领域内数据点数据量> minPTs执行
     {
         dp.SetKey(true);    //将dp核心对象标志位设为true
         return;                //返回
     }
     dp.SetKey(false);    //若非核心对象,则将dp核心对象标志位设为false
 }

 
 /*
 函数:执行聚类操作
 说明:执行聚类操作
 参数:
 返回值: true;    */
 bool ClusterAnalysis::DoDBSCANRecursive()
 {
     unsigned long clusterId=0;                        //聚类id计数,初始化为0
     for(unsigned long i=0; i<dataNum;i++)            //对每一个数据点执行
     {
         DataPoint& dp=dadaSets[i];                    //取到第i个数据点对象
         if(!dp.isVisited() && dp.IsKey())            //若对象没被访问过,并且是核心对象执行
         {
             dp.SetClusterId(clusterId);                //设置该对象所属簇ID为clusterId
             dp.SetVisited(true);                    //设置该对象已被访问过
             KeyPointCluster(i,clusterId);            //对该对象领域内点进行聚类
             clusterId++;                            //clusterId自增1
         }
         //cout << "孤立点\T" << i << endl;
     }

     cout <<"共聚类" <<clusterId<<"个"<< endl;        //算法完成后,输出聚类个数
     return true;    //返回
 }

 /*
 函数:对数据点领域内的点执行聚类操作
 说明:采用递归的方法,深度优先聚类数据
 参数:
 unsigned long dpID;            //数据点id
 unsigned long clusterId;    //数据点所属簇id
 返回值: void;    */
 void ClusterAnalysis::KeyPointCluster(unsigned long dpID, unsigned long clusterId )
 {
     DataPoint& srcDp = dadaSets[dpID];        //获取数据点对象
     if(!srcDp.IsKey())    return;
     vector<unsigned long>& arrvalPoints = srcDp.GetArrivalPoints();        //获取对象领域内点ID列表
     for(unsigned long i=0; i<arrvalPoints.size(); i++)
     {
         DataPoint& desDp = dadaSets[arrvalPoints[i]];    //获取领域内点数据点
         if(!desDp.isVisited())                            //若该对象没有被访问过执行
         {
             //cout << "数据点\t"<< desDp.GetDpId()<<"聚类ID为\t" <<clusterId << endl;
             desDp.SetClusterId(clusterId);        //设置该对象所属簇的ID为clusterId,即将该对象吸入簇中
             desDp.SetVisited(true);                //设置该对象已被访问
             if(desDp.IsKey())                    //若该对象是核心对象
             {
                 KeyPointCluster(desDp.GetDpId(),clusterId);    //递归地对该领域点数据的领域内的点执行聚类操作,采用深度优先方法
             }
         }
     }
 }

 //两数据点之间距离
 /*
 函数:获取两数据点之间距离
 说明:获取两数据点之间的欧式距离
 参数:
 DataPoint& dp1;        //数据点1
 DataPoint& dp2;        //数据点2
 返回值: double;    //两点之间的距离        */
 double ClusterAnalysis::GetDistance(DataPoint& dp1, DataPoint& dp2)
 {
     double distance =0;        //初始化距离为0
     for(int i=0; i<DIME_NUM;i++)    //对数据每一维数据执行
     {
         distance += pow(dp1.GetDimension()[i] - dp2.GetDimension()[i],2);    //距离+每一维差的平方
     }
     return pow(distance,0.5);        //开方并返回距离
 }

算法调用就简单了:
复制代码 代码如下:

#include "ClusterAnalysis.h"
 #include <cstdio>

 using namespace std;

 int main()
 {
     ClusterAnalysis myClusterAnalysis;                        //聚类算法对象声明
     myClusterAnalysis.Init("D:\\1108\\XY.txt",500,9);        //算法初始化操作,指定半径为15,领域内最小数据点个数为3,(在程序中已指定数据维度为2)
     myClusterAnalysis.DoDBSCANRecursive();                    //执行聚类算法
     myClusterAnalysis.WriteToFile("D:\\1108\\XYResult.txt");//写执行后的结果写入文件

     system("pause");    //显示结果
     return 0;            //返回
 }
您可能感兴趣的文章:

相关文章

  • C++利用用埃式筛法求解素数

    C++利用用埃式筛法求解素数

    埃拉托斯特尼筛法,简称埃氏筛或爱氏筛,是一种由希腊数学家埃拉托斯特尼所提出的一种简单检定素数的算法。本文将利用这一算法实现求解素数,感兴趣的可以了解一下
    2023-01-01
  • C++的optional用法实例详解

    C++的optional用法实例详解

    编程中我们可能会遇到要处理可能为空的变量,比如说容器,基本类型,或者说对象实例,下面通过实例代码介绍C++的optional用法,感兴趣的朋友一起看看吧
    2024-02-02
  • VC++开发中完美解决头文件相互包含问题的方法解析

    VC++开发中完美解决头文件相互包含问题的方法解析

    本文中,为了叙述方便,把class AClass;语句成为类AClass的声明,把class AClass开始的对AClass的类成员变量、成员函数原型等的说明称为类的定义,而把在CPP中的部分称为类的定义
    2013-09-09
  • C++面试题之数a、b的值互换(不使用中间变量)

    C++面试题之数a、b的值互换(不使用中间变量)

    这篇文章主要介绍了不使用中间变量,C++实现数a、b的值互相转换操作,感兴趣的小伙伴们可以参考一下
    2016-07-07
  • C 语言进制之间的转换

    C 语言进制之间的转换

    本篇文章主要介绍了C语言进制之间的转换,举例说明并附图片,帮助大家理解,希望对大家有所帮助
    2016-07-07
  • 基于QT设计一个春联自动生成器

    基于QT设计一个春联自动生成器

    春节是中国最隆重的传统节日,一到过年家家户户肯定是要贴春联;在春节前夕,会用大红纸张,加上浓墨书写祝福词语。本文将利用Qt框架设计一个春联自动生成器,需要的可以参考一下
    2022-01-01
  • 深入讲解C++数据类型转换的相关函数的知识

    深入讲解C++数据类型转换的相关函数的知识

    这篇文章主要介绍了深入讲解C++数据类型转换的相关函数的知识,包括类型转换运算符函数等内容,需要的朋友可以参考下
    2015-09-09
  • C++ QT实现获取本机网卡信息

    C++ QT实现获取本机网卡信息

    这篇文章主要为大家详细介绍了如何利用qt自带的QNetworkInterface接口以及c++Iphlpapi库,获取当前windows电脑下的网卡信息,需要的可以参考下
    2024-01-01
  • C++实例分析讲解临时对象与右值引用的用法

    C++实例分析讲解临时对象与右值引用的用法

    对性能来说,许多的问题都需要和出现频率及本身执行一次的开销挂钩,有些问题虽然看似比较开销较大,但是很少会执行到,那也不会对程序有大的影响;同样一个很小开销的函数执行很频繁,同样会对程序的执行效率有很大影响。本章中作者主要根据临时对象来阐述这样一个观点
    2022-08-08
  • C++类的继承和派生及指针安全引用

    C++类的继承和派生及指针安全引用

    这篇文章主要介绍了C++类的继承和派生及指针安全引用,继承指从现有类获得其特性,派生指从已有类产生新的类,指针和引用并存,二者似乎有很多相同点,但是又不完全相同,下面关于两者的相关资料,需要的小伙伴可以参考一下
    2022-03-03

最新评论