DBSCAN聚集算法改进,可用于车辆GPS经纬度聚集计算

来源:互联网 发布:大数据分析工具有哪些 编辑:程序博客网 时间:2024/05/21 14:48

1、DBSCAN简介
DBSCAN(Density-Based Spatial Clustering of Applications with Noise,具有噪声的基于密度的聚类方法)是一种基于密度的空间聚类算法。该算法将具有足够密度的区域划分为簇,并在具有噪声的空间数据库中发现任意形状的簇,它将簇定义为密度相连的点的最大集合。

该算法利用基于密度的聚类的概念,即要求聚类空间中的一定区域内所包含对象(点或其他空间对象)的数目不小于某一给定阈值。

DBSCAN算法的优点:

1. 聚类速度快且能够有效处理噪声点和发现任意形状的空间聚类。
2. 与K-means方法相比,DBSCAN不需要事先知道要形成的簇类的数量。
3. 与K-means方法相比,DBSCAN可以发现任意形状的簇类。
4. 同时,DBSCAN能够识别出噪声点。
5. DBSCAN对于数据库中样本的顺序不敏感,即Pattern的输入顺序对结果的影响不大。但是,对于处于簇类之间边界样本,可能会根据哪个簇类优先被探测到而其归属有所摆动。

DBSCAN算法的弱点:
由于它直接对整个数据库进行操作且进行聚类时使用了一个全局性的表征密度的参数,因此也具有两个比较明显
1. 当数据量增大时,要求较大的内存支持I/O消耗也很大;
2. 当空间聚类的密度不均匀、聚类间距差相差很大时,聚类质量较差。
3. DBScan不能很好反映高尺寸数据。
4. DBScan不能很好反映数据集以变化的密度。


2、算法步骤

DBScan需要二个参数: 扫描半径 (eps)和最小包含点数(minPts)。 任选一个未被访问(unvisited)的点开始,找出与其距离在eps之内(包括eps)的所有附近点。
如果 附近点的数量 ≥ minPts,则当前点与其附近点形成一个簇,并且出发点被标记为已访问(visited)。 然后递归,以相同的方法处理该簇内所有未被标记为已访问(visited)的点,从而对簇进行扩展。
如果 附近点的数量 < minPts,则该点暂时被标记作为噪声点。
如果簇充分地被扩展,即簇内的所有点被标记为已访问,然后用同样的算法去处理未被访问的点。
DBScan需要二个参数: 扫描半径 (eps)和最小包含点数(minPts)。 任选一个未被访问(unvisited)的点开始,找出与其距离在eps之内(包括eps)的所有附近点。
如果 附近点的数量 ≥ minPts,则当前点与其附近点形成一个簇,并且出发点被标记为已访问(visited)。 然后递归,以相同的方法处理该簇内所有未被标记为已访问(visited)的点,从而对簇进行扩展。
如果 附近点的数量 < minPts,则该点暂时被标记作为噪声点。
如果簇充分地被扩展,即簇内的所有点被标记为已访问,然后用同样的算法去处理未被访问的点。


3、代码

在网上找到DBSCAN聚集算法源代码,运行了一下发现结果有些偏差,于是着手修复和改进,并可用于车辆GPS经纬度进行聚集,不多说直接贴代码了:

DataPoint.h文件

#ifndef DataPoint_H#define DataPoint_H#pragma once#include <vector>#include <set> 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:     CString     strDeviceID;//设备ID intnVehicleID;//车辆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方法 void SetDimension(double* dimension, const CString deviceID);    //SetDimension方法 void SetDimension(double* dimension, const int vehicleID); //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方法 };#endif

DataPoint.cpp文件

#include "stdafx.h"#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];}}//设置维度数据void DataPoint::SetDimension(double* dimension, const CString deviceID){SetDimension(dimension);this->strDeviceID = deviceID;}//设置维度数据void DataPoint::SetDimension(double* dimension, const int vehicleID){SetDimension(dimension);this->nVehicleID = vehicleID;}//获取维度数据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;}

ClusterAnalysis.h文件

#ifndef ClusterAnalysis_H#define ClusterAnalysis_H#include <iostream>#include <cmath>#include "DataPoint.h"  using namespace std;  //聚类分析类型 class ClusterAnalysis { private:     vector<DataPoint> dadaSets;        //数据集合     unsigned int dimNum;            //维度     double radius;                    //半径     unsigned int dataNum;            //数据数量     unsigned int minPTs;            //邻域最小数据个数 unsigned long m_MaxclusterId;    //最大簇ID;           void SetArrivalPoints(DataPoint& dp);                                //设置数据点的领域点列表     void KeyPointCluster( unsigned long i, unsigned long clusterId );    //对数据点领域内的点执行聚类操作 public:      ClusterAnalysis(){}                    //默认构造函数     bool Init(double radius, int minPTs);    //初始化操作 bool Init(char* fileName, double radius, int minPTs); //从文件初始化 bool AddData(DataPoint &DP) ;       //加载数据     bool DoDBSCANRecursive();             //DBSCAN递归算法     bool WriteToFile(char* fileName);    //将聚类结果写入文件 double GetDistance(DataPoint dp1, DataPoint dp2, bool isGPS = true);                    //距离函数 unsigned long GetMaxClusterId(); //获取最大簇ID     DataPoint GetDataPoint(unsigned long clusterId, vector<DataPoint> &DpSets);//根据点簇ID,获取对应数据,并返回其中一个核心对象 };#endif
ClusterAnalysis.cpp文件

#include "stdafx.h"#include "ClusterAnalysis.h"#include <fstream>#include <iosfwd>#include <math.h> const double PI = 3.1415926535897932384626433;const double R = 6.378137*1e6; /* 函数:聚类初始化操作 说明:将半径,领域最小数据个数信息写入聚类算法类 参数: double radius;    //半径 int minPTs;        //领域最小数据个数   返回值: true;    */ bool ClusterAnalysis::Init(double radius, int minPTs) {     this->radius = radius;        //设置半径     this->minPTs = minPTs;        //设置领域最小数据个数     this->dimNum = DIME_NUM;    //设置数据维度 dataNum = 0;     return true;    //返回 } /*  函数:聚类初始化操作  说明:将数据文件名,半径,领域最小数据个数信息写入聚类算法类,读取文件,把数据信息读入写进算法类数据集合中  参数:  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   cout<<i<<endl;   }   ifs.close();        //关闭文件流   dataNum =i;            //设置数据对象集合大小为i   return true;    //返回   }   /* 函数:聚类加载数据 说明:点数据写入聚类算法类 参数: DataPoint &pdata    //数据点 返回值: true;    */ bool ClusterAnalysis::AddData(DataPoint &DP)  { DP.SetDpId(dataNum);                    //将数据点对象ID设置为i DP.SetVisited(false);            //数据点对象isVisited设置为false DP.SetClusterId(-1);            //设置默认簇ID为-1 dadaSets.push_back(DP);            //将对象压入数据集合容器 dataNum++;            //设置数据对象集合大小 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;    //返回 } /* 函数:获取最大簇ID 说明:获取最大簇ID 参数: 返回值: 最大簇ID    */ unsigned long  ClusterAnalysis::GetMaxClusterId() { return m_MaxclusterId; } /* 函数:根据点簇ID,获取对应数据,并返回其中一个核心对象 说明:获取核心点列表 参数: clusterId:所属簇ID vector<DataPoint> &DpSets;    //要输出的数据 返回值: 核心对象    */DataPoint ClusterAnalysis::GetDataPoint(unsigned long clusterId, vector<DataPoint> &DpSets) { DataPoint KeyDP; for(unsigned long i=0; i<dataNum; i++)                //对每个数据点执行 { if(clusterId != dadaSets[i].GetClusterId()) { continue; } DpSets.push_back(dadaSets[i]);          if (dadaSets[i].IsKey()) { KeyDP = dadaSets[i]; }} return KeyDP; } /* 函数:设置数据点的领域点列表 说明:设置数据点的领域点列表 参数: 返回值: 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()+1 >= minPTs)            //若dp领域内数据点数据量> minPTs执行.包括该点     {         dp.SetKey(true);    //将dp核心对象标志位设为true         return;                //返回     }     dp.SetKey(false);    //若非核心对象,则将dp核心对象标志位设为false }   /* 函数:执行聚类操作 说明:执行聚类操作 参数: 返回值: true;    */ bool ClusterAnalysis::DoDBSCANRecursive() {     for(unsigned long i=0; i<dataNum;i++) { SetArrivalPoints(dadaSets[i]);            //计算数据点领域内对象 } m_MaxclusterId = 0; //聚类id计数,初始化为01     for(unsigned long i=0; i<dataNum;i++)            //对每一个数据点执行     {         DataPoint& dp=dadaSets[i];                    //取到第i个数据点对象         if(!dp.isVisited() && dp.IsKey())            //若对象没被访问过,并且是核心对象执行         {              dp.SetClusterId(m_MaxclusterId);                //设置该对象所属簇ID为clusterId             dp.SetVisited(true);                    //设置该对象已被访问过             KeyPointCluster(i,m_MaxclusterId);            //对该对象领域内点进行聚类             m_MaxclusterId++;                          //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 ){ if (dpID >= dataNum)       //防止访问出错 return;     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);    //递归地对该领域点数据的领域内的点执行聚类操作,采用深度优先方法             }         }     } }  //两数据点之间距离 /* 函数:获取两数据点之间距离 说明:获取两数据点之间的欧式距离和GPS距离 参数: DataPoint& dp1;        //数据点1 DataPoint& dp2;        //数据点2 bool isGPS;//是否GPS经纬度 返回值: double;    //两点之间的距离,GPS距离单位米      */ double ClusterAnalysis::GetDistance(DataPoint dp1, DataPoint dp2) {double distance =0;        //初始化距离为0if(!isGPS){for(int i=0; i<DIME_NUM;i++)    //对数据每一维数据执行{distance += pow(dp1.GetDimension()[i] - dp2.GetDimension()[i],2);    //距离+每一维差的平方}return pow(distance,0.5);        //开方并返回距离 } else//经纬度计算距离  { double x,y; x=(dp2.GetDimension()[0]-dp1.GetDimension()[0])*PI*R*cos( ((dp1.GetDimension()[1] + dp2.GetDimension()[1])/2) *PI/180)/180; y=(dp2.GetDimension()[1]-dp1.GetDimension()[1])*PI*R/180; return hypot(x,y); } }
使用方法:

//直接输入输出数据ClusterAnalysis cs;cs.Init(2.5, 3, false);//点之间距离设定为2.5,最小聚集数量为3,false为不使用GPS计算距离DataPoint point;double tempPoint[19][2] ={2,2,3,1,3,4,5,3,3,14,8,3,8,6,9,8,10,4,10,7,10,10,10,14,11,13,12,8,12,15,14,7,14,9,14,15,15,8};for(int i =0 ;i <19; i++){point.SetDimension(tempPoint[i]);cs.AddData(point);}cs.DoDBSCANRecursive();//执行聚类计算std::vector<DataPoint> dp;DataPoint dp2;//返回其中一个点(最后一个点)unsigned long nClusterCount = cs.GetMaxClusterId();//获取聚簇ID最大值(数量)for(int i = 0; i < nClusterCount; i++){dp2 = cs.GetDataPoint(i, dp);//获取聚集簇for(auto itr = dp.begin(); itr != dp.end(); itr++){TRACE("聚集簇ID:%d,坐标:%f,%f\r\n", itr->GetClusterId() , itr->GetDimension()[0], itr->GetDimension()[1]);}dp.clear();}cs.GetDataPoint(-1, dp);//获取噪声簇for(auto itr = dp.begin(); itr != dp.end(); itr++){TRACE("噪声点ID:%d,坐标:%f,%f\r\n", itr->GetClusterId() , itr->GetDimension()[0], itr->GetDimension()[1]);}//使用文件输入输出数据ClusterAnalysis csFile;csFile.Init("d:\\In.txt", 2.5, 3, false);//点之间距离设定为2.5,最小聚集数量为3,false为不使用GPS计算距离csFile.DoDBSCANRecursive();//执行聚类计算csFile.WriteToFile("d:\\Out.txt");//输出到文件


结果:

聚集簇ID:0,坐标:2.000000,2.000000聚集簇ID:0,坐标:3.000000,1.000000聚集簇ID:0,坐标:3.000000,4.000000聚集簇ID:0,坐标:5.000000,3.000000聚集簇ID:1,坐标:8.000000,6.000000聚集簇ID:1,坐标:9.000000,8.000000聚集簇ID:1,坐标:10.000000,7.000000聚集簇ID:1,坐标:10.000000,10.000000聚集簇ID:1,坐标:12.000000,8.000000聚集簇ID:1,坐标:14.000000,7.000000聚集簇ID:1,坐标:14.000000,9.000000聚集簇ID:1,坐标:15.000000,8.000000聚集簇ID:2,坐标:10.000000,14.000000聚集簇ID:2,坐标:11.000000,13.000000聚集簇ID:2,坐标:12.000000,15.000000聚集簇ID:2,坐标:14.000000,15.000000噪声点ID:-1,坐标:3.000000,14.000000噪声点ID:-1,坐标:8.000000,3.000000噪声点ID:-1,坐标:10.000000,4.000000



输入输出文件:

In.txt文件

2231345331483869810410710101014111312812151471491415158

输出结果Out.txt

220310340530314-183-1861981104-1107110101101421113212811215214711491141521581

附图:



如果用于GPS车辆的聚集,可能会形成如下图形:



                                             
0 0
原创粉丝点击