跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++AI算法

C++ 从零实现 K-Means 聚类算法详解

使用纯 C++ 从零实现 K-Means 聚类算法的过程。内容包括算法背景、需求分析、核心思想(初始化、分配、更新)、代码实现及解读。通过不依赖第三方库的方式,展示了迭代优化与距离度量的工程实践。文章提供了完整的头文件与源文件代码,并分析了算法的优缺点及扩展方向,适合机器学习初学者理解无监督学习原理及 C++ 数值计算实现。

孤勇者发布于 2026/3/23更新于 2026/10/2519K 浏览

一、项目背景详细介绍

在数据分析、机器学习、模式识别以及人工智能领域中,聚类(Clustering) 是一种极其重要的无监督学习方法。与分类(Classification)不同,聚类并不依赖任何已知标签,而是试图从数据本身的结构中,自动发现潜在的分组模式。

在所有聚类算法中,K-Means 聚类算法 几乎是:

  • 教学中的第一种聚类算法
  • 工程中最常用的基础聚类方法
  • 后续复杂聚类算法(GMM、谱聚类等)的思想基础

📌 如果说'回归'是理解监督学习的起点,那么 K-Means 就是理解无监督学习的起点。


1.1 聚类的现实意义

聚类在现实中的应用极其广泛,例如:

  • 用户画像与用户分群
  • 市场细分与推荐系统
  • 文本主题发现
  • 图像分割
  • 异常检测
  • 数据压缩(向量量化)

在这些应用中,我们通常面对的问题是:

给定一批无标签样本,如何自动将其分成若干'相似'的组?


1.2 为什么要'自己实现'聚类算法?

虽然现成库(如 scikit-learn、OpenCV、MLPack)非常成熟,但亲自实现 K-Means 具有不可替代的教学与工程价值:

  1. 理解'距离度量'的工程含义
  2. 理解'迭代优化'的数值过程
  3. 理解收敛条件与局部最优问题
  4. 为后续实现 GMM、EM 算法打基础
  5. 可作为自定义系统中的轻量级聚类模块

1.3 本项目的定位

本项目的目标是:

用纯 C++ 从零实现一个教学级但工程可用的 K-Means 聚类算法

特点包括:

  • 不依赖第三方机器学习库
  • 使用标准 C++ 数据结构
  • 代码结构清晰、注释完整
  • 易于扩展到高维数据
  • 适合作为聚类算法学习模板

二、项目需求详细介绍

2.1 功能性需求

本项目需要实现以下功能:

  1. 支持对任意维度的数值向量进行聚类
  2. 支持指定聚类数目 K
  3. 实现完整的 K-Means 迭代流程
  4. 输出:
    • 每个样本所属的簇编号
    • 每个簇的中心点(centroid)

核心接口示例:

class KMeans {
public:
    KMeans(int k, int maxIter);
    void fit(const std::vector<std::vector<double>>& data);
    ;
     std::vector<std::vector<>>& () ;
};
const std::vector<int>& labels() const
const
double
centroids
const

2.2 非功能性需求
  1. 算法逻辑清晰、可解释
  2. 数值稳定,避免无意义振荡
  3. 可设置最大迭代次数
  4. 支持欧氏距离
  5. 适合教学与博客讲解

2.3 适用数据类型
  • 数值型向量数据
  • 连续特征
  • 中小规模数据集
  • 教学与实验用途

三、相关技术详细介绍

文章配图


3.2 K-Means 的核心思想

K-Means 的思想极其简单,但非常优雅:

  1. 初始化 K 个中心点
  2. 分配步骤(Assignment Step)
    • 将每个样本分配给最近的中心
  3. 更新步骤(Update Step)
    • 重新计算每个簇的中心
  4. 重复 2–3,直到收敛

3.3 算法特点与局限性

优点:

  • 实现简单
  • 计算效率高
  • 易于理解和实现

缺点:

  • 需要预先指定 K
  • 对初始值敏感
  • 只能发现'凸形'簇
  • 容易陷入局部最优

四、实现思路详细介绍

4.1 整体算法流程
输入数据 X,聚类数 K
随机选择 K 个样本作为初始中心
repeat:
    对每个样本: 计算其到各中心的距离
    分配给最近的中心
    对每个簇: 重新计算中心点(均值)
直到中心点变化小于阈值 或达到最大迭代次数

4.2 距离度量的选择

文章配图

原因:

  • 避免开平方,性能更好
  • 与 K-Means 目标函数一致

4.3 收敛条件设计

采用以下任一条件停止:

  1. 达到最大迭代次数
  2. 所有中心点变化小于阈值
  3. 样本标签不再变化(可选)

五、完整实现代码

/******************************************************
 * File: kmeans.h
 ******************************************************/
#ifndef KMEANS_H
#define KMEANS_H
#include <vector>

class KMeans {
public:
    KMeans(int k, int maxIterations = 100);
    void fit(const std::vector<std::vector<double>>& data);
    const std::vector<int>& labels() const;
    const std::vector<std::vector<double>>& centroids() const;
private:
    int K; // 聚类数
    int maxIter; // 最大迭代次数
    int dim; // 数据维度
    std::vector<int> labelVec; // 每个样本的簇标签
    std::vector<std::vector<double>> centerVec; // 聚类中心
    double distanceSquared(const std::vector<double>& a, const std::vector<double>& b);
};
#endif

/******************************************************
 * File: kmeans.cpp
 ******************************************************/
#include "kmeans.h"
#include <random>
#include <limits>
#include <stdexcept>
#include <cmath>

KMeans::KMeans(int k, int maxIterations) : K(k), maxIter(maxIterations), dim(0) {
    if (K <= 0) throw std::invalid_argument("K must be positive");
}

double KMeans::distanceSquared(const std::vector<double>& a, const std::vector<double>& b) {
    double sum = 0.0;
    for (size_t i = 0; i < a.size(); ++i) {
        double diff = a[i] - b[i];
        sum += diff * diff;
    }
    return sum;
}

void KMeans::fit(const std::vector<std::vector<double>>& data) {
    if (data.empty()) throw std::invalid_argument("Empty dataset");
    int n = static_cast<int>(data.size());
    dim = static_cast<int>(data[0].size());
    labelVec.assign(n, 0);
    centerVec.assign(K, std::vector<double>(dim, 0.0));

    /* 随机初始化聚类中心 */
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> dist(0, n - 1);
    for (int k = 0; k < K; ++k) centerVec[k] = data[dist(gen)];

    /* 迭代优化 */
    for (int iter = 0; iter < maxIter; ++iter) {
        bool changed = false;
        /* 分配步骤 */
        for (int i = 0; i < n; ++i) {
            double minDist = std::numeric_limits<double>::max();
            int bestCluster = 0;
            for (int k = 0; k < K; ++k) {
                double d = distanceSquared(data[i], centerVec[k]);
                if (d < minDist) {
                    minDist = d;
                    bestCluster = k;
                }
            }
            if (labelVec[i] != bestCluster) {
                labelVec[i] = bestCluster;
                changed = true;
            }
        }
        /* 若标签不再变化,提前收敛 */
        if (!changed) break;

        /* 更新步骤 */
        std::vector<std::vector<double>> newCenters(
            K, std::vector<double>(dim, 0.0));
        std::vector<int> counts(K, 0);
        for (int i = 0; i < n; ++i) {
            int k = labelVec[i];
            for (int j = 0; j < dim; ++j) newCenters[k][j] += data[i][j];
            counts[k]++;
        }
        for (int k = 0; k < K; ++k) {
            if (counts[k] == 0) continue;
            for (int j = 0; j < dim; ++j) newCenters[k][j] /= counts[k];
        }
        centerVec = newCenters;
    }
}

const std::vector<int>& KMeans::labels() const {
    return labelVec;
}

const std::vector<std::vector<double>>& KMeans::centroids() const {
    return centerVec;
}

/******************************************************
 * File: main.cpp
 ******************************************************/
#include <iostream>
#include "kmeans.h"

int main() {
    std::vector<std::vector<double>> data = {
        {1.0, 2.0},
        {1.5, 1.8},
        {5.0, 8.0},
        {8.0, 8.0},
        {1.0, 0.6},
        {9.0, 11.0}
    };
    KMeans kmeans(2, 100);
    kmeans.fit(data);
    std::cout << "Cluster labels:\n";
    for (int label : kmeans.labels()) std::cout << label << " ";
    std::cout << "\n";
    std::cout << "Centroids:\n";
    for (const auto& c : kmeans.centroids()) {
        for (double v : c) std::cout << v << " ";
        std::cout << "\n";
    }
    return 0;
}

六、代码详细解读(仅解读方法作用)

6.1 fit
  • 实现 K-Means 的完整训练流程
  • 包括初始化、分配、更新三个阶段
  • 支持提前收敛
6.2 distanceSquared
  • 计算两个向量之间的欧氏距离平方
  • 是 K-Means 目标函数的核心
6.3 labels 与 centroids
  • 提供聚类结果访问接口
  • 便于后续分析与可视化

七、项目详细总结

通过本项目,我们:

  1. 从数学模型出发理解 K-Means
  2. 将无监督学习转化为工程实现
  3. 掌握了迭代优化的数值过程
  4. 构建了一个可复用的聚类模块

该实现:

  • 教学友好
  • 工程可用
  • 结构清晰
  • 易于扩展

八、项目常见问题及解答

Q1:K-Means 一定能找到最优解吗?

A:不能,只能保证局部最优。


Q2:如何改进初始化?

A:可以使用 K-Means++ 初始化策略。


Q3:是否支持高维数据?

A:支持,但距离计算成本会上升。


九、扩展方向与性能优化

  1. K-Means++ 初始化
  2. Mini-batch K-Means
  3. 支持不同距离度量
  4. 并行化距离计算
  5. 扩展到 GMM / EM 算法

目录

  1. 一、项目背景详细介绍
  2. 1.1 聚类的现实意义
  3. 1.2 为什么要“自己实现”聚类算法?
  4. 1.3 本项目的定位
  5. 二、项目需求详细介绍
  6. 2.1 功能性需求
  7. 2.2 非功能性需求
  8. 2.3 适用数据类型
  9. 三、相关技术详细介绍
  10. 3.2 K-Means 的核心思想
  11. 3.3 算法特点与局限性
  12. 四、实现思路详细介绍
  13. 4.1 整体算法流程
  14. 4.2 距离度量的选择
  15. 4.3 收敛条件设计
  16. 五、完整实现代码
  17. 六、代码详细解读(仅解读方法作用)
  18. 6.1 fit
  19. 6.2 distanceSquared
  20. 6.3 labels 与 centroids
  21. 七、项目详细总结
  22. 八、项目常见问题及解答
  23. Q1:K-Means 一定能找到最优解吗?
  24. Q2:如何改进初始化?
  25. Q3:是否支持高维数据?
  26. 九、扩展方向与性能优化

更多推荐文章

查看全部
  • n8n 开源自动化工作流工具部署与使用指南
  • Seedream 4.0 企业级图像 AIGC 能力解析与应用场景
  • 自然语言处理基础与文本分析系统开发
  • Java 25 JDK 安装与配置指南
  • Flutter inappwebview_cookie_manager 鸿蒙适配:Web 容器 Cookie 隔离方案
  • C++高性能事件循环库libev封装实战
  • 红黑树封装 map 和 set 的实现原理与代码
  • 本地电脑搭建 PyTorch 深度学习环境指南
  • RAG 场景中复杂 PDF 解析工具对比:PyMuPDF、Nougat 与 TextIn
  • 多模态与大语言模型经典论文解读目录
  • 国产开源时序数据库 IoTDB 选型指南与核心功能解析
  • CLI-Anything:让所有软件都能被 AI Agent 原生调用
  • 智能家居电平转换电路实战:从理论到PCB制作
  • Python Flask 企业合同管理系统技术要点与选型对比
  • 基于 YOLOv8 的无人机枸杞树病害检测数据集与训练方案
  • Ubuntu 22.04 安装与配置 ROS2 Humble 详解
  • 前端 Word 文档生成实战:DOCX.js 完整使用指南
  • Flutter 开发入门与原理详解
  • Whisper v0.2 本地语音转文字工具安装与使用指南
  • AIGC 内容安全:cv_resnet50 人脸重建嵌入 Deepfake 检测预处理

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • RSA密钥对生成器

    生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online

  • Mermaid 预览与可视化编辑

    基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online

  • 随机西班牙地址生成器

    随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online