QT+VS实现Kmeans聚类算法

1、Kmeans的定义

聚类是一个将数据集中在某些方面相似的数据成员进行分类组织的过程,聚类就是一种发现这种内在结构的技术,聚类技术经常被称为无监督学习k均值聚类是最著名的划分聚类算法,由于简洁和效率使得他成为所有聚类算法中最广泛使用的。

无监督学习通常用于聚类,通过样本件的相似性对数据集进行聚类,使类内差距最小化,类间差距最大化。

2、原理

首先需要弄清楚两个概念:簇和质心

簇: 直观上来看,簇是一组聚在一起的数据,在一个簇中的数据就认为是同一类。

质心: 簇中所有数据的均值通常被称为这个簇的质心。

如何求取质心:

 在一个二维平面中,一簇数据点的质心的横坐标就是这一簇数据点的横坐标的均值,质心的纵坐标就是这一簇数据点的纵坐标的均值。同理可推广至高维空间。

欧式距离计算公式:

二维平面上的欧式距离:

假设待求两点的二维平面坐标为a(,)和b(,),则其距离公式为:

==

3、实现的流程步骤 

  1. 首先随机选取样本中的K个点作为初始聚类中心(质心);
  2. 分别算出样本中其他数据点距离这K个聚类中心的距离,以最近距离的质心缩在的簇作为该数据点分类后的簇;
  3. 对上述分类完的样本再进行每个簇求平均值,求解出新的聚类质心;
  4. 与前一次计算得到的K个聚类质心比较,如果聚类质心发生变化,转过程b,否则转过程e;
  5. 当质心不再发生变化时,停止并输出聚类结果。

 4、实现结果

5、部分代码解析

(1)首先,为了提高分类精度,K个质心初始值的选取,采用人工确定的方法。先人为的选取K个初值,并写成txt格式,如下:

 格式:点号-X坐标-Y坐标

读取K值数据的函数如下:

void Kmeans::onBtReadK()
{
    QString fileName = QFileDialog::getOpenFileName(this, tr("打开"));
    QFile file(fileName);
    bool isOpen = 1;
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
    {
        isOpen = 0;
        QMessageBox::StandardButton btnValue = QMessageBox::information(this, tr("提示"), tr("打开失败!"));
    }
    QTextStream stream(&file);
    while (!stream.atEnd())
    {
        QString str = stream.readLine();
        QStringList list = str.split(",");
        Pointp k1;
        k1.no = list.at(0);
        k1.x = list.at(1).toDouble();
        k1.y = list.at(2).toDouble();
        k.push_back(k1);
    }

    //判断是否读取完毕
    if (stream.atEnd() && isOpen)
    {
        QMessageBox box;
        box.setText("数据读取完毕");
        box.exec();
    }
    dd = readK;
}

 (2)读取K个初始值之后,需要读取整个样本的数据(样本数据格式同K值格式一致),读取函数如下:

void Kmeans::onBtReadData()
{
    K = ui.lineEdit->text().toInt();
    p.clear();

    //打开文件对话框
    QString fileName = QFileDialog::getOpenFileName(this, tr("打开"));
    QFile file(fileName);
    bool isOpen = 1;
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
    {
        isOpen = 0;
        QMessageBox::StandardButton btnValue = QMessageBox::information(this, tr("提示"), tr("打开失败!"));
    }

    //逐行读取文本文件
    QTextStream stream(&file);
    while (!stream.atEnd())
    {
        Pointp pt;
        QString str = stream.readLine();
        QStringList list = str.split(",");
        pt.no = list.at(0);
        pt.x = list.at(1).toDouble();
        pt.y = list.at(2).toDouble();
        p.push_back(pt);
    }

    file.close();

    //判断是否读取完毕
    if (stream.atEnd()&&isOpen)
    {
        QMessageBox box;
        box.setText("数据读取完毕");
        box.exec();
    }
}

(3)在对话框中输入簇个数,然后点击“开始聚类”按钮,开始进行聚类。首先是计算每个样本到K个聚类中心的距离,并找出最小值,作为该样本点的聚类结果。代码如下:

//计算每个对象至聚类中心的距离
void Kmeans::CalDis()
{
    for (int i = 0; i < p.size(); i++)
    {
        double s0 = 0; QString no; Dis ss; int t = 0;
        for (int j = 0; j < K; j++)
        {
            double x1 = p.at(i).x;
            double y1 = p.at(i).y;
            double x2 = k.at(j).x;
            double y2 = k.at(j).y;
            double s1 = sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
            t++;
            if (t == 1)
            {
                s0 = s1;
                no = k.at(j).no;
            }
            if (s1 < s0)
            {
                s0 = s1;
                no = k.at(j).no;
            }
        }
        ss.s = s0;
        ss.no = p.at(i).no;
        ss.x = p.at(i).x;
        ss.y = p.at(i).y;
        ss.noK = no;
        S.push_back(ss);
    }
}

(4)根据分类后的样本计算新的质心,如下:

//计算质心
void Kmeans::Calcentroid()
{
    centroid s;
    for (int i = 0; i < k.size(); i++)
    {
        s.sx = 0; s.sy = 0; int iCt = 0;
        for (int j = 0; j < S.size(); j++)
        {
            if (k.at(i).no == S.at(j).noK)
            {
                s.sx = s.sx + S.at(j).x;
                s.sy = s.sy + S.at(j).y;
                iCt++;
            }
        }
        s.noK = k.at(i).no;
        s.sx = s.sx / iCt;
        s.sy = s.sy / iCt;
        dis.push_back(s);
    }
}

(5)然后判断新质心与旧质心之间的距离,若为0,则停止重新计算。

6、整体代码如下(输入的数据中不能包含负数,因为控件范围是从0开始的)

//Kmeans.cpp文件
#include "Kmeans.h"

Kmeans::Kmeans(QWidget *parent)
    : QWidget(parent)
{
    start = false;
    dd = to2K;
    ui.setupUi(this);
    connect(ui.pushButton, SIGNAL(clicked()), this, SLOT(onBtReadData()));
    connect(ui.pushButton_2, SIGNAL(clicked()), this, SLOT(onBtCalKmeans()));
    connect(ui.pushButton_3, SIGNAL(clicked()), this, SLOT(onBtReadK()));
}

void Kmeans::onBtReadData()
{
    K = ui.lineEdit->text().toInt();
    p.clear();

    //打开文件对话框
    QString fileName = QFileDialog::getOpenFileName(this, tr("打开"));
    QFile file(fileName);
    bool isOpen = 1;
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
    {
        isOpen = 0;
        QMessageBox::StandardButton btnValue = QMessageBox::information(this, tr("提示"), tr("打开失败!"));
    }

    //逐行读取文本文件
    QTextStream stream(&file);
    while (!stream.atEnd())
    {
        Pointp pt;
        QString str = stream.readLine();
        QStringList list = str.split(",");
        pt.no = list.at(0);
        pt.x = list.at(1).toDouble();
        pt.y = list.at(2).toDouble();
        p.push_back(pt);
    }

    file.close();

    //判断是否读取完毕
    if (stream.atEnd()&&isOpen)
    {
        QMessageBox box;
        box.setText("数据读取完毕");
        box.exec();
    }
}

void Kmeans::onBtReadK()
{
    QString fileName = QFileDialog::getOpenFileName(this, tr("打开"));
    QFile file(fileName);
    bool isOpen = 1;
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
    {
        isOpen = 0;
        QMessageBox::StandardButton btnValue = QMessageBox::information(this, tr("提示"), tr("打开失败!"));
    }
    QTextStream stream(&file);
    while (!stream.atEnd())
    {
        QString str = stream.readLine();
        QStringList list = str.split(",");
        Pointp k1;
        k1.no = list.at(0);
        k1.x = list.at(1).toDouble();
        k1.y = list.at(2).toDouble();
        k.push_back(k1);
    }

    //判断是否读取完毕
    if (stream.atEnd() && isOpen)
    {
        QMessageBox box;
        box.setText("数据读取完毕");
        box.exec();
    }
    dd = readK;
}

void Kmeans::toK()
{
    //随机选取k个初始聚类中心
    for (int i = 0; i < K; i++)
    {
        Pointp k1;
        k1.no = i + 1;
        k1.x = p.at(i).x;
        k1.y = p.at(i).y;
        k.push_back(k1);
    }
}

int Kmeans::onBtCalKmeans()
{
    K = ui.lineEdit->text().toInt();
    if (S.size()&&p.size()==S.size())
    {
        QMessageBox box;
        box.setText("已经计算完成");
        box.exec();
        return 0;
    }

    if (dd == to2K)
    {
        toK();
    }

    CalDis();//S
    Calcentroid();//用到S,得dis
    //CKmeans();//用到dis,得new k.

    int iCount = 0;
    while (iCount < K)
    {
        if (dis.size())
        {
            for (int i = 0; i < k.size(); i++)
            {
                for (int j = 0; j < dis.size(); j++)
                {
                    if (k.at(i).no == dis.at(j).noK)
                    {
                        //qDebug() <<"k:" <<k.at(i).no<< k.at(i).x << k.at(i).y;
                        //qDebug() <<"dis:" <<dis.at(i).noK.toInt()<< dis.at(j).sx << dis.at(j).sy<<endl;
                        double detaX = k.at(i).x - dis.at(j).sx;
                        double detaY = k.at(i).y - dis.at(j).sy;
                        double sk = sqrt(detaX * detaX + detaY * detaY);
                        //qDebug() << sk;
                        if (sk == 0)
                        {
                            iCount++;
                        }
                        else
                        {
                            CKmeans();
                        }
                    }
                }
            }
        }

        dis.clear();
        S.clear();
        CalDis();
        Calcentroid();
    }

    start = true;
    qDebug() << "S" << S.size();
    drawPoint();
    QMessageBox box;
    box.setText("计算完成");
    box.exec();
    return 1;
}

Kmeans::~Kmeans()
{}

//计算质心
void Kmeans::Calcentroid()
{
    centroid s;
    for (int i = 0; i < k.size(); i++)
    {
        s.sx = 0; s.sy = 0; int iCt = 0;
        for (int j = 0; j < S.size(); j++)
        {
            if (k.at(i).no == S.at(j).noK)
            {
                s.sx = s.sx + S.at(j).x;
                s.sy = s.sy + S.at(j).y;
                iCt++;
            }
        }
        s.noK = k.at(i).no;
        s.sx = s.sx / iCt;
        s.sy = s.sy / iCt;
        dis.push_back(s);
    }
}

//计算每个对象至聚类中心的距离
void Kmeans::CalDis()
{
    for (int i = 0; i < p.size(); i++)
    {
        double s0 = 0; QString no; Dis ss; int t = 0;
        for (int j = 0; j < K; j++)
        {
            double x1 = p.at(i).x;
            double y1 = p.at(i).y;
            double x2 = k.at(j).x;
            double y2 = k.at(j).y;
            double s1 = sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
            t++;
            if (t == 1)
            {
                s0 = s1;
                no = k.at(j).no;
            }
            if (s1 < s0)
            {
                s0 = s1;
                no = k.at(j).no;
            }
        }
        ss.s = s0;
        ss.no = p.at(i).no;
        ss.x = p.at(i).x;
        ss.y = p.at(i).y;
        ss.noK = no;
        S.push_back(ss);
    }
}

//将新的质心坐标赋值给k
void Kmeans::CKmeans()
{
    for (int i = 0; i < k.size(); i++)
    {
        for (int j = 0; j < dis.size(); j++)
        {
            if (k.at(i).no == dis.at(j).noK)
            {
                k.at(i).x = dis.at(j).sx;
                k.at(i).y = dis.at(j).sy;
            }
        }
    }
}

//绘图函数
void Kmeans::drawPoint()
{
    QPicture pp;
    pp.setBoundingRect(ui.label_2->rect());


    QPainter painterP(&pp);
    QPen pen;
    painterP.setRenderHint(QPainter::Antialiasing, true);

    Pointp p1;
    p1.no = p.at(0).no;
    p1.x = p.at(0).x;
    p1.y = p.at(0).y;
    for (int i = 1; i < p.size(); i++)
    {
        if (p1.x > p.at(i).x)
        {
            p1.x = p.at(i).x;
        }
        if (p1.y > p.at(i).y)
        {
            p1.y = p.at(i).y;
        }
    }
    double xmin = p1.x;
    double ymin = p1.y;

    for (int i = 1; i < p.size(); i++)
    {
        if (p1.x < p.at(i).x)
        {
            p1.x = p.at(i).x;
        }
        if (p1.y < p.at(i).y)
        {
            p1.y = p.at(i).y;
        }
    }
    double xmax = p1.x;
    double ymax = p1.y;

    int w=ui.label_2->width();
    int h=ui.label_2->height();

    double a = w/(xmax -xmin);
    double b1 = h/(ymax -ymin);

    for (int i = 0; i < k.size(); i++)
    {
        int r = qrand() % 256;
        int g = qrand() % 256;
        int b = qrand() % 256;
        QColor color = QColor(r, g, b);

        for (int j = 0; j < S.size(); j++)
        {
            if (k.at(i).no == S.at(j).noK)
            {
                pen.setColor(color);
                painterP.setPen(pen);
                int radius = 5;
                double x = S.at(j).x;
                double y = S.at(j).y;
                x = (x - xmin)*a;
                y = (y - ymin)*b1;
                painterP.drawEllipse(x - radius, y - radius, radius * 2, radius * 2);
            }
        }
    }

    ui.label_2->setPicture(pp);
}
//Kmeans.h文件
#pragma once

#include <QtWidgets/QWidget>
#include "ui_Kmeans.h"
#include<QFileDialog>
#include<QFile>
#include<QMessageBox>
#include<QTextStream>
#include<vector>
#pragma execution_character_set("UTF-8")
#include<qDebug>
#include<QPainter>
#include<QColor>
#include<QColorDialog>
#include<QPicture>

struct Pointp
{
    double x;
    double y;
    QString no;
};

struct Dis
{
    double x;
    double y;
    QString no;
    QString noK;
    double s;
};

struct centroid
{
    QString noK;
    double sx;
    double sy;
};

enum Pd
{
    readK,
    to2K,
    blank
};

class Kmeans : public QWidget
{
    Q_OBJECT

public:
    Kmeans(QWidget *parent = nullptr);
    ~Kmeans();

public slots:
    void onBtReadData();
    int onBtCalKmeans();
    void onBtReadK();
    void toK();

public:
    std::vector<Pointp> p;//原始数据点
    std::vector<Pointp> k;//各簇质心坐标
    int K;
    std::vector<Dis> S;
    std::vector<centroid> dis;
    bool start;
    Pd dd;

public:
    void Calcentroid();
    void CKmeans();
    void CalDis();
    void drawPoint();

private:
    Ui::KmeansClass ui;
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/351437.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java 基础知识-IO流

大家好我是苏麟 , 今天聊聊IO流 . 资料来源黑马程序员 . IO概述 生活中&#xff0c;你肯定经历过这样的场景。当你编辑一个文本文件&#xff0c;忘记了ctrls &#xff0c;可能文件就白白编辑了。当你电脑上插入一个U盘&#xff0c;可以把一个视频&#xff0c;拷贝到你的电脑硬…

ajax点击搜索返回所需数据

html 中body设置&#xff08;css设置跟进自身需求&#xff09; <p idsearch_head>学生信息查询表</p> <div id"div_1"> <div class"search_div"> <div class"search_div_item"> …

day22 事件委托

目录 事件委托 事件委托 事件委托是利用事件流的特征解决一些开发需求的知识技巧 优点&#xff1a;减少注册次数&#xff0c;提高程序性能原理&#xff1a;事件委托其实是利用事件冒泡的特点 给父元素注册事件&#xff0c;当触发子元素时&#xff0c;会冒泡到父元素上&#x…

automa插件使用的一些实战经验3

1 子流程的变量怎么传回父流程 主流程向子流程传参很容易 在子流程可以看到&#xff0c;父流程定义的表格&#xff0c;在子流程中是看不到的&#xff0c;那么子流程定义的变量如何传回父流程呢&#xff1f;另外在子流程再添加执行工作流&#xff0c;是无法选择父流程本身&…

C++:vector容器(memcpy浅拷贝问题、迭代器失效问题)

文章目录 一. vector 的介绍二. vector 的使用1. string 和 vector<char> 的区别2. 为什么 vector 没有 find() 接口 三. vector 的模拟实现1. vector 的基本框架2. memcpy 和 memmove 的浅拷贝问题3. vector 迭代器失效问题4. 模拟代码 一. vector 的介绍 vector 的文档…

二次元动漫卡通手机APP应用下载页HTML源码

HTML源码&#xff0c;记事本修改里面的内容即可&#xff0c;本地双击index.html即可运行 蓝奏云&#xff1a;https://wfr.lanzout.com/itZRg1mf3b9c

华为HCIP Datacom H12-831 卷18

判断题 1、对于同一个MAC地址,手工配置的MAC表项优先级高于动态的表项,某二层报文的源MAC地址已经绑定在了交换机的GEO/0/1接口,当交换机从GEO/0/2收到该报文时,会丢弃该报文 A 对 B 错 正确答案 A 解析:为了提高接口安全性,网络管理员可手工在MAC地址表中加入特定M…

Mac电脑如何安装幻兽帕鲁游戏,Mac虚拟机CrossOver如何安装幻兽帕鲁

《幻兽帕鲁》能在哪些平台玩&#xff1f; | 《幻兽帕鲁》是一款动作冒险生存游戏&#xff0c;自1月中旬发布以来&#xff0c;在Steam榜单上迅速攀升。目前该游戏可在Xbox Series X/S、Xbox One和PC上运行&#xff0c;并可通过Xbox Game Pass在Xbox和PC平台上使用。虽然游戏还未…

taro3 + vue3 + ts 跨平台体验记录

taro3 vue3 ts 跨平台体验记录&#xff0c;根据进度不定期更新。 目标平台包含&#xff1a;H5、微信小程序、APP。开发环境&#xff1a;windows 安装cli【官方安装文档】 npm install -g tarojs/cli常用命令 // 查看taro版本 npm info tarojs/cli创建demo项目 taro init…

【Unity3D日常开发】Unity3D中设置Text行首不出现标点符号

推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址我的个人博客 大家好&#xff0c;我是佛系工程师☆恬静的小魔龙☆&#xff0c;不定时更新Unity开发技巧&#xff0c;觉得有用记得一键三连哦。 一、前言 在开发中会遇到Text的文本内容行首出现标点符号的情况&#xf…

Ultraleap 3Di新建项目之给所有的Joint挂载物体

工程文件 Ultraleap 3Di给所有的Joint挂载物体 前期准备 参考上一期文章&#xff0c;进行正确配置 Ultraleap 3Di配置以及在 Unity 中使用 Ultraleap 3Di手部跟踪 新建项目 初始项目如下&#xff1a; 新建Create Empty 将新建的Create Empty&#xff0c;重命名为LeapPro…

第四篇【传奇开心果系列】beeware的Toga开发移动应用示例:健身追踪应用

传奇开心果博文系列 系列博文目录beeware的Toga开发移动应用示例系列 博文目录前言一、记录存储运动活动数据二、添加添加统计显示功能三、添加图表显示数据分析功能四、增加健身计划管理五、增加备份数据恢复数据功能六、初步整合代码示例七、增加登录验证功能八、完成最终整合…

利用STM32CubeMX和Keil模拟器,3天入门FreeRTOS(5.1) —— 队列集

前言 &#xff08;1&#xff09;FreeRTOS是我一天过完的&#xff0c;由此回忆并且记录一下。个人认为&#xff0c;如果只是入门&#xff0c;利用STM32CubeMX是一个非常好的选择。学习完本系列课程之后&#xff0c;再去学习网上的一些其他课程也许会简单很多。 &#xff08;2&am…

通过css隐藏popover的效果:即hover显示或隐藏另一个元素

场景一&#xff1a;隐藏旁边的兄弟元素 在原生的微信小程序上实现下图hover后出现提示的效果&#xff0c;如果是PC端就可以直接使用el-popover&#xff0c;但是小程序&#xff0c;我没有看到适合的组件。 样式代码<van-field value"{{ username }}" clearable pl…

数据结构与算法——队列

概述 计算机科学中&#xff0c;queue 是以顺序的方式维护的一组数据集合&#xff0c;在一端添加数据&#xff0c;从另一端移除数据。添加的一端称为尾&#xff0c;移除的一端称为头。 功能 插入offer(value : E) : boolean  取值并移除poll() : E  取值peek() : E  判断…

RCC——使用HSE/HSI配置时钟

RCC 文章目录 前言一、背景二、 2.1 2.2 总结 前言 前期疑问&#xff1a;1、RCC是什么意思。 2、最终配好的72M是系统时钟吗&#xff1f; 3、一共有哪些时钟 本文目标&#xff1a;将PLL时钟配置成72M 疑问解答&#xff1a;最终配好的时钟是PLL时钟。可以看一下时钟图就知道…

面向对象编程(进阶)(上)

文章目录 一. 关键字&#xff1a;this1.1 this是什么&#xff1f;1.2 什么时候使用this1.2.1 实例方法或构造器中使用当前对象的成员1.2.2 同一个类中构造器互相调用 1.3 练习 二. 面向对象特征二&#xff1a;继承(Inheritance)2.1 继承的概述2.1.1 生活中的继承2.1.2 Java中的…

LeetCode刷题---二叉树的最大深度

解题思路: 二叉树的最大深度是从根节点到最远叶子节点的最长路径上的节点数。 对于任意一个节点&#xff0c;其深度为其左子树深度和右子树深度的最大值加1。 最大高度是从根节点到最远叶子节点的最长路径的长度。 使用先序遍历的方法来找出二叉树的最大深度&#xff0c;即先访…

linuxshell日常脚本命令之if判断

shell脚本if中判断大于、小于、等于、不等于的符号 脚本有问题&#xff0c;有没有哪位大佬能帮忙检查一下&#xff1f; #!/bin/bash#run_num$(squeue | grep shifting | wc -l) run_numsqueue | grep shifting | wc -l #run_num$(squeue | grep shifting | wc -l 2>&1…

Qt环境搭建及基础

目录 Qt背景及环境搭建 ​编辑 基础语法 数据类型 内联函数 inline Lambda表达式 通过函数调用中加lambda匿名函数 参数捕获 Lambda和内联函数区别​编辑 函数指针 Lambda匿名函数小案例 通过结构体初始化&#xff0c;和指针初始化结构体 c类的引入 &#xff1a;&am…
最新文章