QT+VS实现Kmeans聚类算法

news2024/12/28 20:51:22

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.coloradmin.cn/o/1413005.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数位dp,HDU 4151 The Special Number

一、题目 1、题目描述 In this problem, we assume the positive integer with the following properties are called ‘the special number’: 1) The special number is a non-negative integer without any leading zero. 2) The numbers in every digit of the special nu…

RCE 漏洞审计

Command Injection 命令注入&#xff08;Command Injection&#xff09;是一种安全漏洞&#xff0c;命令注入攻击的目的是&#xff0c;在易受攻击的应用程序中注入和执行攻击者指定的命令。在这种情况下&#xff0c;执行不需要的系统命令的应用程序就像一个伪系统外壳&#xff…

无线监测终端引领文物保护和管理新篇章

一、文物预防性保护系统大升级 随着科技的不断发展&#xff0c;越来越多的高科技产品进入人们的生活和工作中。在文物保护和管理行业&#xff0c;无线监测终端大放异彩。免布线、即插即用的特点在提供方便的同时&#xff0c;也为文物的长久保存和有效管理带来更好的保护环境。…

兼容树莓派扩展模块,专注工业产品开发的瑞米派强势来袭

近日&#xff0c;米尔电子和瑞萨电子共同定义和开发了瑞萨第一款MPU生态开发板——瑞米派&#xff08;Remi Pi&#xff09;正式上市了&#xff01;在各种Pi板卡琳琅满目的当下&#xff0c;Remi Pi是一款与众不同的开发板&#xff0c;他兼顾了严肃产品开发和爱好者创意实现两种需…

ffmpeg和opencv一些容易影响图片清晰度的操作

ffmpeg 转视频或者图片&#xff0c;不指定码率清晰度会下降 ffmpeg -i xxx.png xxx.mp4 码率也叫比特率&#xff08;Bit rate&#xff09;(也叫数据率)是一个确定整体视频/音频质量的参数&#xff0c;秒为单位处理的字节数&#xff0c;码率和视频质量成正比&#xff0c;在视频…

Linux CentOs7 安装Mysql(5.7和8.0版本)密码修改 超详细教程

CSDN 成就一亿技术人&#xff01; 今天出一期Centos下安装Mysql&#xff08;详细教程&#xff09;包括数据库密码跳过修改 CSDN 成就一亿技术人&#xff01; 目录 1.获取安装包 2.安装程序 安装下载的rpm包 查看安装包 修改5.7版本&#xff08;重要&#xff09; 安装M…

Java七大排序详解

排序 排序的概念 所谓排序 &#xff0c;就是让一串记录&#xff0c;按照其中某些或者某个关键字的大小&#xff0c;递增或递减的排列起来的操作。 稳定性&#xff1a;就比如在待排序的序列中&#xff0c;存在多个具有相同关键字的记录 &#xff0c;如果经过排序这些相同的关键…

通过FileZilla配置FTP

FileZilla服务端的安装 在虚拟机里安装FileZilla服务器 FileZilla的官网 下载一个客户端和一个服务端的FileZilla 如果已经有了一个客户端&#xff0c;可以不下用载。 FileZilla的配置 说明一下&#xff1a;通过FileZilla配置FTP有两种模式&#xff0c;我们先用被动模式 下载…

Javaweb之SpringBootWeb案例之阿里云OSS服务集成的详细解析

2.3.3 集成 阿里云oss对象存储服务的准备工作以及入门程序我们都已经完成了&#xff0c;接下来我们就需要在案例当中集成oss对象存储服务&#xff0c;来存储和管理案例中上传的图片。 在新增员工的时候&#xff0c;上传员工的图像&#xff0c;而之所以需要上传员工的图像&…

CDR绘图软件|安装教程来了(小白福利:有红包封面领取哦!)

前言 今天给小伙伴们讲讲&#xff1a;如何安装CDR软件。 如果未来的你想从事平面设计/广告行业&#xff0c;那应该就会接触到CDR这款软件。 CorelDRAW Graphics Suite是加拿大Corel公司的平面设计软件&#xff1b;该软件是Corel公司出品的矢量图形制作工具软件&#xff0c;这…

GPT应用程序的限制

尽管GPT&#xff08;Generative Pre-trained Transformer&#xff09;应用程序具有强大的自然语言生成能力&#xff0c;但也存在一些限制和挑战。以下是一些常见的GPT应用程序的限制&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公…

HCIA-HarmonyOS设备开发认证-2.设备开发入门

目录 HarmonyOS设备开发学习路径一、开发项目与工具介绍1.1、设备开发环境准备1.2、设备开发流程1.3、Huawei DevEco Device Tool 二、OpenHarmony介绍OpenHarmony目录结构详细介绍 待续... HarmonyOS设备开发学习路径 一、开发项目与工具介绍 1.1、设备开发环境准备 Window、…

Leetcode刷题笔记题解(C++):1971. 寻找图中是否存在路径

思路&#xff1a; 1.建立图集&#xff0c;二维数组&#xff0c;path[0]里面存放的就是与0相连的节点集合 2.用布尔数组来记录当前节点是否被访问过&#xff0c;深度优先会使用到 3.遍历从起点开始能直接到达的点&#xff08;即与起点相邻的点&#xff09;&#xff0c;判断那…

【Image captioning】论文阅读七—Efficient Image Captioning for Edge Devices_AAAI2023

中文标题:面向边缘设备的高效图像描述(Efficient Image Captioning for Edge Devices) 文章目录 1. 引言2. 相关工作3. 方法3.1 Model Architecture(模型结构)3.2 Model Training (模型训练)3.3 Knowledge Distillation (知识蒸馏)4. 实验4.1 数据集和评价指标4.2 实施细…

Element UI样式修改之NavMenu导航菜单箭头样式修改

UI设计稿给的菜单箭头样式可能与我们饿了么组件NavMenu的菜单箭头样式不一致,目前我们侧边导航菜单的上下翻转箭头如下所示: 希望得到如下的结果: 找到饿了么Icon里我们想要向下箭头,F12后复制content内容content: “\e790”; content: "\e790";然后将默认的c…

计算机网络-PPP与PPPoE协议

我们之前学习的大多是局域网LAN内常用的技术&#xff0c;但是我们总是需要访问Internet&#xff0c;需要访问百度、B站等等&#xff0c;那怎样让局域网访问外面的资源呢&#xff0c;其实我们已经学习过了NAT转换&#xff0c;但是那对于广域网的架构我们还是需要学习下的。 一、…

算法训练营Day60(单调栈)

84.柱状图的最大矩形 84. 柱状图中最大的矩形 - 力扣&#xff08;LeetCode&#xff09; 注意首尾加0的细节就可 class Solution {public int largestRectangleArea(int[] heights) {Deque<Integer> stack new LinkedList<>();int[] newHeight new int[heights.…

Pandas应用-股票分析实战

股票时间序列 时间序列&#xff1a; 金融领域最重要的数据类型之一 股价、汇率为常见的时间序列数据 趋势分析&#xff1a; 主要分析时间序列在某一方向上持续运动 在量化交易领域&#xff0c;我们通过统计手段对投资品的收益率进行时间序列建模&#xff0c;以此来预测未来的收…

【大根堆】【C++算法】871 最低加油次数

作者推荐 【动态规划】【map】【C算法】1289. 下降路径最小和 II 本文涉及知识点 大根堆 优先队列 LeetCode:871最低加油次数 汽车从起点出发驶向目的地&#xff0c;该目的地位于出发位置东面 target 英里处。 沿途有加油站&#xff0c;用数组 stations 表示。其中 statio…

Python教程48:海龟画图turtle画太极八卦阵

---------------turtle源码集合--------------- Python教程91&#xff1a;关于海龟画图&#xff0c;Turtle模块需要学习的知识点 Python源码45&#xff1a;海龟画图turtle画雪容融 Python源码44&#xff1a;海龟画图turtle&#xff0c;画2022卡塔尔世界杯吉祥物 Python教程…