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,获取对应数据,并返回其中一个核心对象 };#endifClusterAnalysis.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车辆的聚集,可能会形成如下图形:
- DBSCAN聚集算法改进,可用于车辆GPS经纬度聚集计算
- 两点GPS经纬度计算
- 对数复杂度的聚集算法
- K-Means 数据聚集算法
- java根据GPS经纬度坐标计算两点的距离算法
- java根据GPS经纬度坐标计算两点的距离算法
- 根据GPS经纬度坐标计算两点的距离算法
- 聚集函数
- 聚集函数
- 聚集索引
- Oracle 聚集
- Oracle 聚集
- 聚集索引
- 聚集索引
- 增量聚集
- 聚集索引
- 聚集国际
- 聚集索引
- 一些标签及小技巧的积累和使用
- [MES]欣技9700的重置方法
- 自己的问题所在
- iwebshop二次开发
- Unable to start Virtual device问题及解决办法Android模拟器Genymotion
- DBSCAN聚集算法改进,可用于车辆GPS经纬度聚集计算
- 一个基于BaseAdapter适配ListView的完整示例
- 基于Davenport风速谱单点时程模拟
- 数据结构实验之链表四:有序链表的归并
- python安装及配置
- 用js编写生成指定范围内的随机数
- 一次写代码活动
- 静态库与动态库
- 静态变量