山东大学软件学院面向对象实验——排序

一、写在最前面

在我的项目中,部分借鉴了往届学长的一个项目,在这里附上这个项目的链接,真的特别感谢

另外,本博客在发布之前也得到了学长的许可

基于QT实现九大排序的动态演示

二、题目要求

排序:设计一个负责排序的程序包,实现多种排序算法,至少包括插入排序、冒泡排序和快速排序算法。要求:可以对任何简单类型和任意对象进行排序;可以支持升序、降序、字典排序等多种顺序要求;可以随意增加排序算法和顺序要求,保证其他程序代码不修改;(选作,不属于基本要求)演示排序过程,演示速度可调整,可以单步、可以暂停、可以回退。

总结一下这个项目所需要的几个功能

  1. 实现多种排序的柱状图演示,要求实现升序排序,降序排序,排序速度可调整,可暂停
  2. 实现对象类型的排序,可支持升序和降序,可按不同对象的不同属性排序
  3. 实现多种排序的单步演示和回退

三、项目截图及录屏

主界面
在这里插入图片描述
录屏放在这里

面向对象实验录屏——排序

四、具体功能的实现

4.1 动态演示功能

4.4.1 显示柱状图的功能

首先,要实现排序,需要有排序的数。在界面上需要输入要排序的数的最大值,以及排序的数量,之后随机生成随机数,将随机数添加到QList里,之后再将QList里的数添加到QBarSet中,之后将QBarSet呈现到QChartView中。
实现显示柱状图总共调用了4个方法,具体的代码如下:
代码执行顺序为:start()->iniBarChartBubble()->buildBarChartBubble()->buildBarCharts()

void MainWindow::start()
{
    //获取最大值
    max = ui->max->text().toInt();
    //获取数量
    n = ui->n->text().toInt();
    //如果用户输入为空,就弹出提示对话框
    if(QString::compare(ui->n->text(),"") == 0 ||  QString::compare(ui->max->text(),"") == 0)
    {
        alertError("您未指定初始数据,请重新输入!");
        return;
    }
    //判断是否为纯数字,如果不是,就弹出提示对话框
    if(!isNumber(ui->max->text()) || !isNumber(ui->n->text()))
    {
        alertError("您输入的不是纯数字,请重新输入!");
        return;
    }
    //初始化要排序的数组
    //清空原有的数据
    dataList.clear();
    srand((unsigned)time(NULL));
    for(int i = 0;i < n;i++)
    {
        int temp = rand() % (max + 1);
        dataList.append(temp);
    }
    //判断用户选择了哪个排序
    //用户选择了冒泡排序
    if(QString::compare(ui->sortType->currentText(),"冒泡排序") == 0)
    {
        iniBarChartBubble();
        buildBarChartBubble();
    }
    //用户选择了插入排序
    else if(QString::compare(ui->sortType->currentText(),"插入排序") == 0)
    {
        iniBarChartInsert();
        buildBarChartInsert();
    }
    //用户选择了快速排序
    else if(QString::compare(ui->sortType->currentText(),"快速排序") == 0)
    {
        iniBarChartQuick();
        buildBarChartQuick();
    }
    //用户选择了选择排序
    else if(QString::compare(ui->sortType->currentText(),"选择排序") == 0)
    {
        iniBarChartSelect();
        buildBarChartSelect();
    }
}
void MainWindow::iniBarChartBubble()
{
    QChart *chart = new QChart(); // 创建chart
    chart->setTitle(tr("冒泡排序动态演示"));
    // 不设置动画效果,当数据较大时动画会出现卡顿、降低运行速度,并且显示效果不好
    chart->setAnimationOptions(QChart::NoAnimation);
    ui->chartView->setChart(chart); // 为chartViewBubble设置chart
    ui->chartView->setRenderHint(QPainter::Antialiasing);
}
void MainWindow::buildBarChartBubble()
{
    QChart *chart =ui->chartView->chart(); // 获取chartViewBubble关联的chart
    QBarSet *barSet = new QBarSet(tr("随机数")); // 创建一个数据集QBarSet
    buildBarCharts(chart, barSet); // 构建chart
    barsets = barSet;
}
//排序算法构造柱状图的最后一步
void MainWindow::buildBarCharts(QChart *chart, QBarSet *barSet)
{
    chart->removeAllSeries(); // 删除所有序列
    chart->removeAxis(chart->axisX()); // 删除坐标轴
    chart->removeAxis(chart->axisY()); // 删除坐标轴

    for (int i = 0; i < n; i++) // 从dataList中获取数据
        barSet->append(dataList.at(i));
    QBarSeries *series = new QBarSeries(); // 创建一个柱状图序列QBarSeries
    series->append(barSet); // 添加数据集
    chart->addSeries(series); // 添加柱状图序列

    // 设置横坐标
    QStringList categories;
    for (int i = 1; i <= 5; i++)
        categories << QString::number(i);
    QBarCategoryAxis *axisX = new QBarCategoryAxis();
    axisX->setRange(categories.at(0), categories.at(4)); // 这是坐标轴范围
    axisX->setTitleText("位置");
    chart->setAxisX(axisX, series); // 设置横坐标

    int minNum = 0;
    int maxNum = max;
    // 设置纵坐标
    QValueAxis *axisY = new QValueAxis;
    axisY->setRange(minNum, maxNum);
    axisY->setTitleText("数值");
    axisY->setTickCount(11);
    chart->setAxisY(axisY, series);

    //设置暗黑背景
    chart->setTheme(QChart::QChart::ChartThemeDark);

    chart->legend()->setVisible(true); //显示图例
    chart->legend()->setAlignment(Qt::AlignBottom); //图例显示在下方
}

4.4.2 动态排序和排序暂停功能

为了实现动态排序,我使用了QBarSet中的replace方法,这个方法可以重置柱状图的内容。此外,为了实现排序暂停的功能,我专门设置了一个bool变量,用于控制是否执行for循环。外层while循环一直运行,但for循环的运行取决于是否处于暂停状态
在动态演示中,所有的排序算法都继承了QThread类,并重写run方法,以实现多线程的排序
下面的代码演示的是冒泡排序类的头文件和源文件

冒泡排序头文件

class BubbleSortThread: public QThread
{
    Q_OBJECT

public:
    BubbleSortThread(QBarSet *barSet);
    BubbleSortThread();
    void beginThread(); // 开始线程
    void pauseThread(); // 暂停线程
    
    int sleepTime;// 排序速度
    int upDown;// 排序类型:升序或降序
protected:
    void run() override; // 线程任务

private:
    QBarSet *barSet; // 柱状图数据
    bool paused = true; // 暂停
    bool stop = false; // 停止
    int dataNum; // barSet->count() 数据数量
};

冒泡排序源文件

//冒泡排序
BubbleSortThread::BubbleSortThread(QBarSet *barSet)
{
    this->barSet = barSet;
    dataNum = barSet->count();
}

BubbleSortThread::BubbleSortThread()
{

}

void BubbleSortThread::beginThread()
{
    paused = false;
}

void BubbleSortThread::pauseThread()
{
    paused = true;
}
void BubbleSortThread::run()
{
    stop = false; // 启动线程时令 m_stop = false
    while (!stop) // 事件主循环
    {
        // 冒泡排序循环
        for (int i = dataNum; i > 1 && !paused && !stop; i--)
        {
            for (int j = 0; j < i-1 && !paused && !stop; j++)
            {
                //upDown = 0 为升序排序
                if(upDown == 0)
                {
                    if (barSet->at(j) > barSet->at(j+1))
                    {
                        // 将两个bar的值交换
                        int temp1 = barSet->at(j);
                        int temp2 = barSet->at(j+1);
                        barSet->replace(j, temp2);
                        barSet->replace(j+1, temp1);
                    }
                }
                //upDown = 1 为降序排序
                else if(upDown == 1)
                {
                    if (barSet->at(j) < barSet->at(j+1))
                    {
                        // 将两个bar的值交换
                        int temp1 = barSet->at(j);
                        int temp2 = barSet->at(j+1);
                        barSet->replace(j, temp2);
                        barSet->replace(j+1, temp1);
                    }
                }
                msleep(sleepTime); // 停留sleepTimes
            }
        }
    }
    quit();
}

4.2 对象排序功能

在对象排序中,我一共准备了两种类型的对象,一个是Person对象,另一个是Student对象,为了演示方便,Person和Student的name,age和score都是随机生成的,不需要让用户再次输入了。age和score由随机数产生,姓名存放在一个数组中,数组的索引随机生成,因此可以给定随机的姓名
此外,还准备了一个专门的ObjectSort类,专门用于对象排序,由于本次实验使用的是C++Qt,在Qt中不推荐使用对象数组,而推荐使用QVector容器进行排序,所以我将所有对象放入QVector容器中,使用泛型技术,进行排序
排序同样支持升序排序和降序排序
下面是具体的Person类,Student类,ObjectSort类的头文件及源文件

头文件

//**********************待排序的Person类******************
class Person
{
public:
    Person();
    Person(QString name, int age);
    //姓名
    QString name;
    //年龄
    int age;
};
//**********************待排序的Student类******************
class Student
{
public:
    Student();
    Student(QString name, int score);
    //姓名
    QString name;
    //分数
    int score;
};
//**********************为对象排序准备的类******************
class ObjectSort
{
public:
    ObjectSort();
    //保存名字的数组
    QVector<QString> nameSet;
    //用于呈现结果的TextEdit
    QTextEdit* ObjectResult;
    //判断升序or降序,0为升序,1为降序
    int sortType;
    //按名字对Person数组排序
    void sortPersonByName(QVector<Person>* p);
    //按年龄对Person数组排序
    void sortPersonByAge(QVector<Person>* p);
    //按名字对学生排序
    void sortStudentByName(QVector<Student>* s);
    //按年龄对学生排序
    void sortStudentByScore(QVector<Student>* s);
    //打印输出所有的名字
    void printAllNames();
};

源文件

按name属性进行排序

void ObjectSort::sortPersonByName(QVector<Person>* p)
{
    //使用选择排序,按Person的名字排序
    for(int index = 0;index < p->length();index++)
    {
        Person temp1;
        Person temp2;
        for(int scan = index + 1;scan < p->length();scan++)
        {
            //升序排序
            if(sortType == 0)
            {
                //使用大小写不敏感方式匹配
                if(QString::compare(p->at(index).name,p->at(scan).name,Qt::CaseInsensitive) > 0)
                {
                    temp1 = p->at(index);
                    temp2 = p->at(scan);
                    p->replace(index,temp2);
                    p->replace(scan,temp1);
                }
            }
            else if(sortType == 1)
            {
                //使用大小写不敏感方式匹配
                if(QString::compare(p->at(index).name,p->at(scan).name,Qt::CaseInsensitive) < 0)
                {
                    temp1 = p->at(index);
                    temp2 = p->at(scan);
                    p->replace(index,temp2);
                    p->replace(scan,temp1);
                }
            }
        }
    }
    //将排序后的结果呈现到文本框中
    this->ObjectResult->append("Person类对象按Name属性排序后:");
    this->ObjectResult->append("");
    for(int index = 0;index < p->length();index++)
    {
        this->ObjectResult->insertPlainText("name = ");
        this->ObjectResult->insertPlainText(p->at(index).name);
        this->ObjectResult->insertPlainText(", age = ");
        this->ObjectResult->insertPlainText(QString::number(p->at(index).age));
        //换行
        this->ObjectResult->append("");
    }
}

按age属性进行排序

void ObjectSort::sortPersonByAge(QVector<Person>* p)
{
    //使用选择排序,按Person的年龄排序
    for(int index = 0;index < p->length();index++)
    {
        Person temp1;
        Person temp2;
        for(int scan = index + 1;scan < p->length();scan++)
        {
            //升序排序
            if(this->sortType == 0)
            {
                if(p->at(index).age > p->at(scan).age)
                {
                    temp1 = p->at(index);
                    temp2 = p->at(scan);
                    p->replace(index,temp2);
                    p->replace(scan,temp1);
                }
            }
            else if(sortType == 1)
            {
                if(p->at(index).age < p->at(scan).age)
                {
                    temp1 = p->at(index);
                    temp2 = p->at(scan);
                    p->replace(index,temp2);
                    p->replace(scan,temp1);
                }
            }
        }
    }
    //将排序后的结果呈现到文本框中
    this->ObjectResult->append("Person类对象按Age属性排序后:");
    this->ObjectResult->append("");
    for(int index = 0;index < p->length();index++)
    {
        this->ObjectResult->insertPlainText("name = ");
        this->ObjectResult->insertPlainText(p->at(index).name);
        this->ObjectResult->insertPlainText(", age = ");
        this->ObjectResult->insertPlainText(QString::number(p->at(index).age));
        //换行
        this->ObjectResult->append("");
    }
}

4.3 单步回退功能

在本次面向对象实验中,单步和回退功能的实现思路是这样的
首先对QList进行相应的排序(比如冒泡排序),将排序每一步的QList变化都记录下来,保存在QVector<QList<int>>容器中,之后当用户选择前进或后退时,会指定一个索引,这个索引会锁定QVector的索引,找到对应的QList,之后再将QList添加到QBarSet中,再呈现到QChartView中

前进和回退按钮对应的槽函数

//前进
void MainWindow::front()
{
    allData.clear();
    //判断用户选择了哪个排序
    //用户选择了冒泡排序
    if(QString::compare(ui->sortTypeStep->currentText(),"冒泡排序") == 0)
    {
        index++;
        bubbleSortStep(dataListStep,dataListStep.length());
        iniShowStepBar();
        buildShowStepBar();
    }
    //用户选择了插入排序
    else if(QString::compare(ui->sortTypeStep->currentText(),"插入排序") == 0)
    {
        index++;
        insertSortStep(dataListStep,dataListStep.length());
        iniShowStepBar();
        buildShowStepBar();
    }
    //用户选择了快速排序
    else if(QString::compare(ui->sortTypeStep->currentText(),"快速排序") == 0)
    {
        index++;
        quickSortStep(dataListStep,dataListStep.length());
        iniShowStepBar();
        buildShowStepBar();
    }
    //用户选择了选择排序
    else if(QString::compare(ui->sortTypeStep->currentText(),"选择排序") == 0)
    {
        index++;
        selectSortStep(dataListStep,dataListStep.length());
        iniShowStepBar();
        buildShowStepBar();
    }
}

以冒泡排序为例,展示将排序的每一步添加到QVector<QList<int>>容器中
这里的allData就是这个容器

//单步冒泡排序
void MainWindow::bubbleSortStep(QList<int> dataList, int dataNum)
{
    allData.append(dataList);
    // 冒泡排序循环
    for (int i = dataNum; i > 1; i--)
    {
        for (int j = 0; j < i-1; j++)
        {
            //upDown = 0 为升序排序
            if(QString::compare(ui->upDownStep->currentText(),"升序排序") == 0)
            {
                if (dataList.at(j) > dataList.at(j+1))
                {
                    // 将两个bar的值交换
                    int temp1 = dataList.at(j);
                    int temp2 = dataList.at(j+1);
                    dataList.replace(j, temp2);
                    dataList.replace(j+1, temp1);
                }
                allData.append(dataList);
            }
            //upDown = 1 为降序排序
            else if(QString::compare(ui->upDownStep->currentText(),"降序排序") == 0)
            {
                if (dataList.at(j) < dataList.at(j+1))
                {
                    // 将两个bar的值交换
                    int temp1 = dataList.at(j);
                    int temp2 = dataList.at(j+1);
                    dataList.replace(j, temp2);
                    dataList.replace(j+1, temp1);
                }
                allData.append(dataList);
            }
        }
    }
}

将排序的一步显示到柱状图上

void MainWindow::showStepBar(QChart *chart, QVector<QList<int>> allData, int index, QBarSet* barSet)
{
    if(index < 0)
    {
        alertError("不能再回退了");
        index++;
        return;
    }
    else if(index >= allData.length())
    {
        alertError("不能再前进了");
        index--;
        return;
    }
    chart->removeAllSeries(); // 删除所有序列
    chart->removeAxis(chart->axisX()); // 删除坐标轴
    chart->removeAxis(chart->axisY()); // 删除坐标轴

    // 从dataList中获取数据
    for (int i = 0; i < n; i++)
    {
        barSet->append(allData.at(index).at(i));
    }
    QBarSeries *series = new QBarSeries(); // 创建一个柱状图序列QBarSeries
    series->append(barSet); // 添加数据集
    chart->addSeries(series); // 添加柱状图序列
    // 设置横坐标
    QStringList categories;
    for (int i = 1; i <= 5; i++)
        categories << QString::number(i);
    QBarCategoryAxis *axisX = new QBarCategoryAxis();
    axisX->setRange(categories.at(0), categories.at(4)); // 这是坐标轴范围
    axisX->setTitleText("位置");
    chart->setAxisX(axisX, series); // 设置横坐标

    int minNum = 0;
    int maxNum = max;
    // 设置纵坐标
    QValueAxis *axisY = new QValueAxis;
    axisY->setRange(minNum, maxNum);
    axisY->setTitleText("数值");
    axisY->setTickCount(11);
    chart->setAxisY(axisY, series);

    //设置暗黑背景
    chart->setTheme(QChart::QChart::ChartThemeDark);
    chart->legend()->setVisible(true); //显示图例
    chart->legend()->setAlignment(Qt::AlignBottom); //图例显示在下方
}

4.4 二级下拉框的实现

当选择Person类时,二级下拉框的属性为name和age
当选择Student类时,二级下拉框的属性为name和score

具体的图如下
在这里插入图片描述
在这里插入图片描述

通过绑定槽函数的方法,使用switch语句,通过第一个下拉框的值改变第二个下拉框的值

//实现二级下拉框
void MainWindow::on_ObjectType_currentIndexChanged(const QString &arg1)
{
    switch (ui->ObjectType->currentIndex())
    {
        //第一个下拉框选中Person
        case 0:
            ui->TypeName->clear();
            ui->TypeName->insertItem(0,"name");
            ui->TypeName->insertItem(1,"age");
            break;
        //第一个下拉框选中Student
        case 1:
            ui->TypeName->clear();
            ui->TypeName->insertItem(0,"name");
            ui->TypeName->insertItem(1,"score");
            break;
    }
}

4.5 界面美化的实现

这一部分内容和我之前的数据结构课设是一样的,这里就不再赘述了,可以参考我的数据结构课设的文章,里面有具体的描述

山东大学软件学院大二下数据结构课程设计—排序算法的性能分析

五、不足和总结

在这次面向对象的实验中,由于题目要求有些模糊,再加上每个人的理解不同,不同人做出来的效果很不一样。总体来说,由于有了之前数据结构课设的经验,这个项目显得容易一些。最后实现出来的效果个人感觉基本满意,但还有一些不足的地方,比如

  1. 对象排序仅支持两种对象的排序,并且直接显示结果,没有呈现排序过程,显得很单薄
  2. 单步和回退没有和动态演示结合在一起,单独弄了一个单步演示区
  3. 单步回退部分在快速排序部分依然有小bug尚未解决,其它的都正常
  4. 功能还不够丰富,可以再拓展字典排序或其它的功能
  • 5
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值