k近邻算法的c++实现,我的github地址:https://github.com/halooooJeffrey/machine-learning/blob/master/kNN/kNN.h
#ifndef _KNN_H_
#define _KNN_H_
#include <vector>
#include <cmath>
#include <algorithm>
#include <map>
#include <iterator>
#include <iostream>
using namespace std;
/************************************************************************/
/* 定义一个结构体,里面是存储的是一个vector里面元素的值和它对应所在的位置,将会在
/* sortDistancesIndices函数中使用
/************************************************************************/
struct valueAndIndices{
double value;
int index;
};
/************************************************************************/
/* 是一个比较函数,比较valueAndIndices中的元素哪个最小,用在sort函数的第三个参数
/************************************************************************/
int cmp(const valueAndIndices& a, const valueAndIndices& b){
if (a.value < b.value)
return 1;
else
return 0;
}
/************************************************************************/
/* 是一份数据的类,这个类里面包含有数据和所属的分类,因为维度不确定使用vector来储存
/************************************************************************/
class data{
public:
vector<double> _data;
char _label;
data();
data(const data&);
};
/************************************************************************/
/* data的默认构造函数,是取得一个样本,这里写的是手工输入,维度可以较小,在实际情况中
/* 读入数据方式会不同,这里仅作用作验证kNN算法只用,
/************************************************************************/
data::data(){
//copy(istream_iterator<double>(cin), istream_iterator<double>(), back_inserter(_data));
double tmp;
cin >> _label;
cout << "有多少维数据: ";
int num;
cin >> num;
while (num--){
cin >> tmp;
_data.push_back(tmp);
}
}
/************************************************************************/
/* data的拷贝构造函数
/************************************************************************/
data::data(const data& d){
_label = d._label;
_data = d._data;
}
/************************************************************************/
/* 定义一个训练数据集,里面有数据的集合和一个标签的集合,一份data对应一份label,在vector
/* 中的位置也是对应的。
/************************************************************************/
class trainingDataSet{
public:
vector<data> _dataSet;
vector<char> _labelSet;
trainingDataSet();
};
/************************************************************************/
/* 同样这里只是一个简单的获取一个样本数据集合的方法
/************************************************************************/
trainingDataSet::trainingDataSet(){
cout << "有多少组数据: ";
int num;
cin >> num;
while (num--){
data tempData;
_dataSet.push_back(tempData);
_labelSet.push_back(tempData._label);
}
}
/************************************************************************/
/* 定义一个kNN类,数据成员有一个测试样本,一个样本数据集合
/* 成员函数有计算距离的函数,将距离排序返回下标的函数,计算各标签的个数的函数以及最终分类函数
/* 这里没有写测试函数用来检测样本数据集中的错误率,我们当做是可以满足条件的
/************************************************************************/
class kNN{
data _testData;
trainingDataSet _trainingDataSet; //为什么无法访问_trainingDataSet里的_dataSet(为私有成员)
public:
kNN();
kNN(data aTestData, trainingDataSet aTrainingDataSet) : _testData(aTestData), _trainingDataSet(aTrainingDataSet){}
vector<double> distances();
vector<int> sortDistancesIndices(vector<double>);
map<char, int> classCount(vector<int>, int);
char classify(const map<char, int>&);
};
/************************************************************************/
/* 计算测试样本与样本数据集合中各样本点的距离,外层的for循环是指向样本数据集合中的单个
/* 样本,即data类型,内存for循环是将测试样本和数据集中的样本对应位上求平方和(这里计算距离
/* 公式是简单的欧氏距离),开根号求得距离后压入vector中,vector中的距离元素所在位置是与
/* 数据集合中标签vector中标签的位置一一对应,这会用在sortDistancesIndices函数中
/************************************************************************/
vector<double> kNN::distances(){
vector<double> retVec;
double sum = 0, aDistance;
for (vector<data>::const_iterator it1 = _trainingDataSet._dataSet.begin(); it1 != _trainingDataSet._dataSet.end(); ++it1){
for (vector<double>::const_iterator it2 = _testData._data.begin(), it3 = it1->_data.begin(); it2 != _testData._data.end(); ++it2){
sum += (*it2 - *it3) * (*it2 - *it3);
++it3;
}
aDistance = sqrt(sum);
retVec.push_back(aDistance);
sum = 0;
}
return retVec;
}
/************************************************************************/
/* 将距离vector中的元素从小到大排序,返回的vector是排序后的元素在原矩阵中的位置。
/* 例如[3,5,2,9]运用这个函数后返回[2,0,1,3]
/************************************************************************/
vector<int> kNN::sortDistancesIndices(vector<double> vec){
vector<int> retVec;
vector<valueAndIndices> vecTemp;
for (vector<double>::size_type st = 0; st != vec.size(); ++st){
valueAndIndices v;
v.value = vec[st];
v.index = st;
vecTemp.push_back(v);
}
sort(vecTemp.begin(), vecTemp.end(), cmp);
for (vector<valueAndIndices>::const_iterator it = vecTemp.begin(); it != vecTemp.end(); ++it)
retVec.push_back(it->index);
return retVec;
}
/************************************************************************/
/* 计算前k个最近距离做属于标签的统计 */
/************************************************************************/
map<char, int> kNN::classCount(vector<int> vec, int k){
map<char, int> retMap;
for (vector<int>::size_type st = 0; st != k; ++st)
++retMap[_trainingDataSet._labelSet[vec[st]]];
return retMap;
}
/************************************************************************/
/* 分类函数返回标个数统计中有最大个数的标签,即为测试样本的标签
/************************************************************************/
char kNN::classify(const map<char, int>& m){
char labelResult;
int num;
map<char, int>::const_iterator baseIt = m.begin();
num = baseIt->second;
labelResult = baseIt->first;
for (map<char, int>::const_iterator it = m.begin(); it != m.end(); ++it){
if (it->second > num){
num = it->second;
labelResult = it->first;
}
}
return labelResult;
}
#endif // !_KNN_H_