文章目录
一、题目
排序算法的性能分析
问题描述
设计一个测试程序,比较几种内部排序算法的关键字比较次数和移动次数以取得直观感受。
基本要求
(1)对冒泡排序、直接排序、选择排序、箱子排序、堆排序、快速排序及归并排序算法进行比较。
(2)待排序表的表长不小于100,表中数据随机产生,至少用5组不同数据作比较,比较指标:关键字参加比较次数和关键字的移动次数(关键字交换记为3次移动)。
(3)输出比较结果。
选做内容
(1)对不同表长进行比较。
(2)验证各算法的稳定性。
(3)输出界面的优化。
二、界面图
主界面
比较和移动次数饼图
比较不同表长的对话框
比较结果
验证稳定性的对话框
证明堆排序是不稳定排序
课设录屏
三、题目分析
这个课设主要有以下几个功能
(1)基本要求是用户给出一个关键字,要比较的数据随机产生,数据的表长不小于100,之后求7种排序算法中关键字的比较次数和移动次数
(2)用户输入不同表长并指定一种排序,输出该排序算法对应不同表长的比较次数和移动次数
(3)验证排序算法的稳定性(即两个相同的元素排序前与排序后的前后顺序不发生变化)
(4)输出界面的优化:即不使用传统的dos窗口运行程序,而采用图形界面的方式演示程序,方便用户操作
四、基本思路
1.基本功能的实现思路:
在这里我使用Qt中的LineEdit,让用户填入相应的数据,并当用户点击开始排序后获取数据,利用Qt中的多线程技术,同时进行这七种排序,并将排序的结果输出到界面上,并制作成饼状图,并且用户还可以看到排序前的数据,排序后的数据,以及数据的基本信息(最大值,最小值,平均值,数量)。
2.选做的功能
(1)对一种特定的排序算法,给出不同表长,求关键字的比较次数和移动次数
实现思路:
当用户点击界面上的“比较不同表长”按钮时,自动弹出对话框,用户可以输入要比较的两个表长的值,并选择对应的排序算法,当用户点击确认后,触发Qt中按钮的clicked信号,后端调用对应的槽函数,从而调用相应的排序算法,并求出比较次数,最终将结果呈现到界面上,让用户可以直观地看到
(2)验证排序算法的稳定性
实现思路:
首先用户输入验证稳定性需要比较的关键字,并选择要验证的排序算法,点击确认之后,自动调用对应的槽函数,并初始化一个与要比较数组同长度的标记数组flag,这个数组中的索引值与要排序的数组的值是对应的,两个关键字在这个标记数组flag中的标记值分别为-1和-2(-1在前,-2在后),而flag数组中其它的值均为0。最后将排序前两个数组的情况和排序后两个数组的情况呈现到界面上,如果标记值出现-2在前,-1在后,则说明两个相同值的关键字在排序后的相对顺序已经改变了,说明该排序是不稳定排序
例:排序前的数组(4为重复的关键字)为: 5 4 4 3 1
用于标记的数组flag的值为: 0 -1 -2 0 0
(3)输出界面的优化,使用C++Qt
实现思路:
使用c++qt,在界面上用户可以输入进行排序所需要的信息,并通过按键操作,实现相应的功能。在界面上也可以获取到用户所需的比较次数和移动次数
3.额外添加的功能
(1)显示比较次数和移动次数的饼状图
除了在文本框中显示7中排序算法的比较次数和移动次数,还有饼状图,可以查看7种排序算法的比较次数和移动次数的占比。
(2)显示不同排序算法的基本信息
添加可以查看七种排序算法基本功能的子目录,当用户点击子目录时,就可以查看7种排序算法的基本信息,稳定性,时间复杂度等。
五、项目结构
1.开发环境
开发平台:QtCreator 4.11.0版本,Qt 5.14.1版本
编程语言:C++
采用C/S结构
2.结构介绍
本课设由以下几个部分组成(项目文件图如下)
一、头文件:
(1)commonhelper.h
用来设置整个界面stylesheet的一个文件,可以引用资源文件中的.qss文件,对界面起美化作用
(2)comparelendialog.h
比较不同表长的对话框的头文件
(3)flatui.h
里面有很多设置组件stylesheet的方法,用于美化组件
(4)mainboard.h
系统主界面的头文件
(5) SortThread.h
7个排序算法的线程(当中包含七大排序算法的线程类,继承QThread)
(6)stabledialog.h
验证稳定性的对话框的头文件
二、源文件:
与上面的头文件作用相同,只是后缀名改为了.cpp文件
三、界面文件:
(1)comparelendialog.ui
比较不同表长对话框的界面
(2)mainboard.ui
系统主界面
(3)stabledialog.ui
验证稳定性对话框的界面
四、资源文件
style.qrc
用来设置整个界面的样式,从而使界面呈现暗黑效果
3.关键点及难点
3.1七个排序算法和关键字比较/移动次数的实现
七个排序算法的具体排序过程这里就不再赘述了,只介绍如何求关键字得比较次数和移动次数
在创建要排序数组的同时,再创建一个flag数组作为标记,这个flag数组除了要排序数组关键字的索引值为-1以外其余所有元素均为0
比如:
要排序的数组 2 3 1 4 5
对应flag数组: -1 0 0 0 0
这里数字”2“为关键字
之后flag数组中的标记值随关键字的移动一起移动
比如冒泡排序:
void BubbleSortThread::run()
{
/**1.获取排序需要的基本数据**/
int n = BubbleSortThread::n;
int keyWord = BubbleSortThread::keyWord;
int* array = BubbleSortThread::array;
int* flag = BubbleSortThread::flag;
QTextEdit* result = BubbleSortThread::result;
QLineEdit* bubbleCom = BubbleSortThread::bubbleCom;
QLineEdit* bubbleMov = BubbleSortThread::bubbleMov;
/**2.进行冒泡排序**/
//关键字比较次数
int compareCount = 0;
//关键字移动次数
int moveCount = 0;
//进行冒泡排序
for (int index = n - 1; index > 0; index--)
{
int temp = 0;
for (int scan = 0; scan < index; scan++)
{
if (array[scan] > array[scan + 1])
{
//进入交换,记为3次移动
//两数比较1次
if(array[scan] == keyWord && flag[scan] == -1)
{
compareCount++;
moveCount += 3;
//将flag[scan]置为0
flag[scan] = 0;
//将flag[scan + 1]置为-1
flag[scan + 1] = -1;
}
else if(array[scan + 1] == keyWord && flag[scan + 1] == -1)
{
compareCount++;
moveCount += 3;
flag[scan + 1] = 0;
flag[scan] = -1;
}
temp = array[scan];
array[scan] = array[scan + 1];
array[scan + 1] = temp;
}
}
}
/**3.将排序结果和比较次数输出到界面上**/
//输出排序后的结果
for (int index = 0; index < n; index++)
{
//冒泡排序
result->insertPlainText(QString::number(array[index]));
//使其不换行
//result->moveCursor(QTextCursor::End);
result->insertPlainText(" ");
}
//输出比较次数
bubbleCom->setText(QString::number(compareCount));
//输出移动次数
bubbleMov->setText(QString::number(moveCount));
delete[] array;
delete[] flag;
this->exec();
}
3.2获取QLineEdit输入框的值并转换为数字
调用QLineEdit的text()函数获取输入的字符串(QString),再利用QString的toInt()函数即可
/**1.获取用户输入的数据**/
//根据用户的输入,产生随机数数组
//获取数的个数
QString strN = ui->n->text();
int n = strN.toInt();
showN = n;
//获取关键字
QString strKeyWord = ui->keyWord->text();
int keyWord = strKeyWord.toInt();
//获取最大值
QString strMax = ui->max->text();
int max = strMax.toInt();
showMin = max;
3.3多线程运行7个排序算法
为了采用多线程运行七个排序算法,所有排序算法统一继承QThread,当主程序调用start方法时,这些类的run方法自动执行,注意之后还要调用quit()方法和wait()方法
主程序
/**5.初始化7大排序算法的线程,进行排序,排序后的结果呈现到文本框中,关键字的比较次数也呈现到文本框中**/
//冒泡排序线程
BubbleSortThread b;
//选择排序线程
SelectSortThread s;
//插入排序线程
InsertSortThread i;
//箱子排序线程
BoxSortThread box;
//堆排序线程
HeapSortThread h;
//归并排序线程
MergeSortThread m;
//快速排序线程
QuickSortThread q;
/**6.设置7大排序算法的参数**/
//冒泡排序
b.n = n;
b.max = max;
b.keyWord = keyWord;
b.array = array;
b.flag = flag;
b.result = ui->result;
b.bubbleCom = ui->bubbleCom;
b.bubbleMov = ui->bubbleMov;
//选择排序
s.n = n;
s.max = max;
s.keyWord = keyWord;
s.array = array2;
s.flag = flag2;
s.selectCom = ui->selectCom;
s.selectMov = ui->selectMov;
//插入排序
i.n = n;
i.max = max;
i.keyWord = keyWord;
i.array = array3;
i.flag = flag3;
i.insertCom = ui->insertCom;
i.insertMov = ui->insertMov;
//箱子排序
box.n = n;
box.max = max;
box.keyWord = keyWord;
box.flag = flag4;
box.array = array4;
box.boxCom = ui->boxCom;
box.boxMov = ui->boxMov;
box.c = c;
//堆排序
h.n = n;
h.max = max;
h.array = array5;
h.flag = flag5;
h.heapCom = ui->heapCom;
h.heapMov = ui->heapMov;
h.keyWord = keyWord;
//归并排序
m.n = n;
m.max = max;
m.array = array6;
m.flag = flag6;
m.keyWord = keyWord;
m.mergeCom = ui->mergeCom;
m.mergeMov = ui->mergeMov;
//快速排序
q.n = n;
q.max = max;
q.array = array7;
q.flag = flag7;
q.keyWord = keyWord;
q.quickCom = ui->quickCom;
q.quickMov = ui->quickMov;
/**7.清空之前文本框里的内容**/
ui->result->clear();
ui->bubbleCom->clear();
ui->bubbleMov->clear();
ui->selectCom->clear();
ui->selectMov->clear();
ui->insertCom->clear();
ui->insertMov->clear();
ui->boxCom->clear();
ui->boxMov->clear();
ui->heapCom->clear();
ui->heapMov->clear();
ui->mergeCom->clear();
ui->mergeMov->clear();
ui->quickCom->clear();
ui->quickMov->clear();
/**8.开启7大算法的线程**/
//冒泡排序开启
b.start();
//选择排序开启
s.start();
//插入排序开启
i.start();
//箱子排序开启
box.start();
//堆排序开启
h.start();
//归并排序开启
m.start();
//快速排序开启
q.start();
/**9.退出7算法的线程**/
//冒泡排序
b.quit();
//选择排序
s.quit();
//插入排序
i.quit();
//箱子排序
box.quit();
//堆排序
h.quit();
//归并排序
m.quit();
//快速排序
q.quit();
/**10.等待7大算法的线程**/
//冒泡排序
b.wait();
//选择排序
s.wait();
//插入排序
i.wait();
//箱子排序
box.wait();
//堆排序
h.wait();
//归并排序
m.wait();
//快速排序
q.wait();
排序算法线程run方法中的代码(以选择排序为例)
void SelectSortThread::run()
{
/**1.获取排序需要的基本数据**/
int n = SelectSortThread::n;
int keyWord = SelectSortThread::keyWord;
int* array = SelectSortThread::array;
int* flag = SelectSortThread::flag;
QLineEdit* selectCom = SelectSortThread::selectCom;
QLineEdit* selectMov = SelectSortThread::selectMov;
/**2.进行选择排序**/
//关键字比较次数
int compareCount = 0;
//关键字移动次数
int moveCount = 0;
//选择排序
//在未排序的序列里寻找最小的元素,并将其放在未排序序列的最前面
//循环一遍结束后,未排序的元素数量-1,以排序的元素数量+1
for (int index = 0; index < n; index++)
{
int temp = 0;
for (int scan = index + 1; scan < n; scan++)
{
if (array[index] > array[scan])
{
//关键字比较1次
//关键字交换1次,算移动3次
if(array[index] == keyWord && flag[index] == -1)
{
compareCount ++;
moveCount += 3;
//重新标记关键字
flag[index] = 0;
flag[scan] = -1;
}
else if(array[scan] == keyWord && flag[scan] == -1)
{
compareCount ++;
moveCount += 3;
//重新标记关键字
flag[scan] = 0;
flag[index] = -1;
}
temp = array[index];
array[index] = array[scan];
array[scan] = temp;
}
}
}
/**3.将排序结果和比较次数输出到界面上**/
selectCom->setText(QString::number(compareCount));
selectMov->setText(QString::number(moveCount));
delete[] array;
delete[] flag;
this->exec();
}
3.4检验QString是否为纯数字
/***********************************判断QString是否为纯数字******************************************/
bool MainBoard::isNumber(QString qstrSrc)
{
QByteArray ba = qstrSrc.toLatin1();
const char *s = ba.data();
bool bret = true;
while(*s)
{
if(*s>='0' && *s<='9')
{
}
else
{
bret = false;
break;
}
s++;
}
return bret;
}
3.5验证算法的稳定性
具体的思路上面已经写过了,这里只展示代码
void MainBoard::selectStable(int *array, int* flag, int n, int keyWord)
{
//进行选择排序
for (int index = 0; index < n; index++)
{
int temp = 0;
for (int scan = index + 1; scan < n; scan++)
{
if (array[index] > array[scan])
{
if(array[index] == keyWord && flag[index] == -1)
{
//重新标记关键字
flag[index] = 0;
flag[scan] = -1;
}
else if(array[index] == keyWord && flag[index] == -2)
{
//重新标记关键字
flag[index] = 0;
flag[scan] = -2;
}
else if(array[scan] == keyWord && flag[scan] == -1)
{
flag[scan] = 0;
flag[index] = -1;
}
else if(array[scan] == keyWord && flag[scan] == -2)
{
flag[scan] = 0;
flag[index] = -2;
}
temp = array[index];
array[index] = array[scan];
array[scan] = temp;
}
}
}
//将array数组与flag数组呈现到resultStable中
ui->resultStable->append("排序后的数组:");
for(int index = 0;index < n;index++)
{
ui->resultStable->insertPlainText(QString::number(array[index]));
//使其不换行
ui->resultStable->moveCursor(QTextCursor::End);
ui->resultStable->insertPlainText(" ");
}
ui->resultStable->append("标记值:");
for(int index = 0;index < n;index++)
{
ui->resultStable->insertPlainText(QString::number(flag[index]));
//使其不换行
ui->resultStable->moveCursor(QTextCursor::End);
ui->resultStable->insertPlainText(" ");
}
}
3.6qt组件的美化
由于默认的组件样式不够好看,因此在这里引用了美化类,调用其中的方法设置对应组件的stylesheet即可
(1)按钮的美化
设置圆角和有颜色的按钮
设置鼠标浮动在上面时变色
调用方法设置qss
FlatUI::Instance()->setPushButtonQss(ui->clearNs, 13, 3, "#1ABC9C", "#FFFFFF", "#2EE1C1", "#000000", "#16A086", "#A7EEE6");
对应类的源代码:
void FlatUI::setPushButtonQss(QPushButton *btn, int radius, int padding,
const QString &normalColor, const QString &normalTextColor,
const QString &hoverColor, const QString &hoverTextColor,
const QString &pressedColor, const QString &pressedTextColor)
{
QStringList qss;
qss.append(QString("QPushButton{border-style:none;padding:%1px;border-radius:%2px;color:%3;background:%4;}")
.arg(padding).arg(radius).arg(normalTextColor).arg(normalColor));
qss.append(QString("QPushButton:hover{color:%1;background:%2;}")
.arg(hoverTextColor).arg(hoverColor));
qss.append(QString("QPushButton:pressed{color:%1;background:%2;}")
.arg(pressedTextColor).arg(pressedColor));
btn->setStyleSheet(qss.join(""));
}
(2)文本框的美化
设置圆角输入框
设置输入框聚焦后边框变色
调用方法设置
FlatUI::Instance()->setLineEditQss(ui->keyWord, 10, 2, "#FFFFFF", "#40E0D0");
对应类的源代码
void FlatUI::setLineEditQss(QLineEdit *txt, int radius, int borderWidth,
const QString &normalColor, const QString &focusColor)
{
QStringList qss;
qss.append(QString("QLineEdit{border-style:none;padding:1px;border-radius:%1px;border:%2px solid %3;}")
.arg(radius).arg(borderWidth).arg(normalColor));
qss.append(QString("QLineEdit:focus{border:%1px solid %2;}")
.arg(borderWidth).arg(focusColor));
txt->setStyleSheet(qss.join(""));
}
3.7qt制作饼状图
注意在”.pro"文件中的第一行需要添加QT += charts
才能画饼状图
void MainBoard::alertComPie()
{
//获取需要的数据
int bubbleCom = ui->bubbleCom->text().toInt();
int selectCom = ui->selectCom->text().toInt();
int insertCom = ui->insertCom->text().toInt();
int boxCom = ui->boxCom->text().toInt();
int heapCom = ui->heapCom->text().toInt();
int mergeCom = ui->mergeCom->text().toInt();
int quickCom = ui->quickCom->text().toInt();
//判断数据是否为空
if(bubbleCom == 0 || selectCom == 0 || insertCom == 0 || boxCom == 0 || heapCom == 0 || mergeCom == 0 || quickCom == 0)
{
//弹出错误对话框
alertError("您尚未进行排序");
return;
}
//添加饼图的每一片扇形及内容和值
QPieSeries *series = new QPieSeries();
series->append("冒泡排序", bubbleCom);
series->append("快速排序", quickCom);
series->append("堆排序", heapCom);
series->append("选择排序", selectCom);
series->append("箱子排序", boxCom);
series->append("插入排序", insertCom);
series->append("归并排序", mergeCom);
//设置每片扇形的标题和字体,并显示标签
QPieSlice *slice1 = series->slices().at(0);
slice1->setLabel(slice1->label() + " " + QString::number(bubbleCom) + "次");
slice1->setLabelVisible();
slice1->setLabelFont(QFont("宋体",10,75));
QPieSlice *slice2 = series->slices().at(1);
slice2->setLabel(slice2->label() + " " + QString::number(quickCom) + "次");
slice2->setLabelVisible();
slice2->setLabelFont(QFont("宋体",10,75));
QPieSlice *slice3 = series->slices().at(2);
slice3->setLabel(slice3->label() + " " + QString::number(heapCom) + "次");
slice3->setLabelVisible();
slice3->setLabelFont(QFont("宋体",10,75));
QPieSlice *slice4 = series->slices().at(3);
slice4->setLabel(slice4->label() + " " + QString::number(selectCom) + "次");
slice4->setLabelVisible();
slice4->setLabelFont(QFont("宋体",10,75));
QPieSlice *slice5 = series->slices().at(4);
slice5->setLabel(slice5->label() + " " + QString::number(boxCom) + "次");
slice5->setLabelVisible();
slice5->setLabelFont(QFont("宋体",10,75));
QPieSlice *slice6 = series->slices().at(5);
slice6->setLabel(slice6->label() + " " + QString::number(insertCom) + "次");
slice6->setLabelVisible();
slice6->setLabelFont(QFont("宋体",10,75));
QPieSlice *slice7 = series->slices().at(6);
slice7->setLabel(slice7->label() + " " + QString::number(mergeCom) + "次");
slice7->setLabelVisible();
slice7->setLabelFont(QFont("宋体",10,75));
QChart *chart = new QChart();
chart->addSeries(series);
chart->setTitle("各大排序比较次数饼状图");
chart->setFont(QFont("宋体",10,75));
//设置图表的字体
chart->legend()->setFont(QFont("宋体",10,75));
QChartView *chartView = new QChartView(chart);
//设置抗锯齿
chartView->setRenderHint(QPainter::Antialiasing);
chartView->setFont(QFont("宋体",10,75));
//将window设置为静态变量,防止窗体一闪而过
static QMainWindow window;
window.setCentralWidget(chartView);
window.setFont(QFont("宋体",10,75));
window.resize(1200, 800);
window.show();
}
3.8使输入到QTextEdit中的内容不换行
正常情况下,当我们使用QTextEdit中的append()方法时,会默认进行换行
但采用insertPlainText()方法可使其不换行
for(int index = 0;index < n;index++)
{
ui->resultStable->insertPlainText(QString::number(array[index]));
ui->resultStable->insertPlainText(" ");
}
3.9排序算法继承QThread
以归并排序为例(头文件)
//归并排序线程
class MergeSortThread : public QThread
{
Q_OBJECT
signals:
public:
MergeSortThread(QObject* par = nullptr);
~MergeSortThread();
void run();
void merge(int begin, int mid, int end, int* array, int* flag, int* leftArray, int keyWord, int* leftFlag);
void divide(int begin, int end, int* array, int* flag, int* leftArray, int keyWord, int* leftFlag);
//表长
int n;
//最大值
int max;
//关键字
int keyWord;
//排序的数组
int* array;
//标记关键字的数组
int* flag;
//左数组
int* leftArray;
//比较次数
int compareCount;
//移动次数
int moveCount;
//显示比较次数和交换次数的文本框
QLineEdit* mergeCom;
QLineEdit* mergeMov;
private:
};
源文件
/**************************归并排序**************************/
MergeSortThread::MergeSortThread(QObject* par)
{
}
MergeSortThread::~MergeSortThread()
{
}
//合并
void MergeSortThread:: merge(int begin, int mid, int end, int* array, int* flag, int* leftArray, int keyWord, int* leftFlag)
{
//左边数组的索引
int li = 0;
//左边数组最右边的索引
int le = mid - begin;
//右边数组的索引
int ri = mid;
//右边数组最右边的索引
int re = end;
//array的索引
int ai = begin;
//拷贝左边数组到leftArray
for (int index = li; index < le; index++)
{
leftArray[index] = array[begin + index];
leftFlag[index] = flag[begin + index];
}
//如果左边还没有结束
while (li < le)
{
if (ri < re && array[ri] < leftArray[li])
{
//参与比较和移动
if(array[ri] == keyWord && flag[ri] == -1)
{
compareCount++;
moveCount++;
//重置关键字
flag[ri] = 0;
flag[ai] = -1;
}
else if(array[ai] == keyWord && flag[ai] == -1)
{
flag[ai] = 0;
}
// 拷贝右边数组到array
array[ai++] = array[ri++];
}
else
{
//参与比较和移动
if (leftArray[li] == keyWord && leftFlag[li] == -1)
{
compareCount++;
moveCount++;
//重置关键字
leftFlag[li] = 0;
flag[ai] = -1;
}
else if(array[ai] == keyWord && flag[ai] == -1)
{
flag[ai] = 0;
}
// 拷贝左边数组到array
array[ai++] = leftArray[li++];
}
}
}
//将数组划分为不同的段
void MergeSortThread:: divide(int begin, int end, int* array, int* flag, int* leftArray, int keyWord, int* leftFlag)
{
if (end - begin < 2)
{
return;
}
int mid = (begin + end) / 2;
//归并排序左半序列
divide(begin, mid, array, flag, leftArray, keyWord, leftFlag);
//归并排序右半序列
divide(mid, end, array, flag, leftArray, keyWord, leftFlag);
//合并整个序列
merge(begin, mid, end, array, flag, leftArray, keyWord, leftFlag);
}
void MergeSortThread::run()
{
/**1.获取排序需要的基本数据**/
int n = MergeSortThread::n;
int keyWord = MergeSortThread::keyWord;
int* array = MergeSortThread::array;
int* flag = MergeSortThread::flag;
MergeSortThread::leftArray = new int[n / 2];
QLineEdit* mergeCom = MergeSortThread::mergeCom;
QLineEdit* mergeMov = MergeSortThread::mergeMov;
/**2.进行归并排序**/
//比较次数和移动次数
compareCount = 0;
moveCount = 0;
int* leftFlag = new int[n / 2];
divide(0, n, array, flag, leftArray, keyWord, leftFlag);
/**3.将排序结果和比较次数输出到界面上**/
mergeCom->setText(QString::number(compareCount));
mergeMov->setText(QString::number(moveCount));
delete[] array;
delete[] flag;
this->exec();
}
六、不足
(1)这次课设中没有做柱状图演示动态排序过程的功能
(2)在使用Qt中的多线程方面遇到了一些困难,到最后虽然功能实现了,但是多线程的原理和方法不是很清楚
(3)排序中关键字的比较次数和移动次数可能不够准确
(4)课设可能有一些小的bug还未被发现,比如如果表长过长之类的情况,还未实验过
(5)功能相对较少,比较单薄
七、相关参考资料
在这次课设中,我参考了之前学长写的的一个项目,这个项目给我启发很大,真的很感谢,我在这个项目中也用了它里面的资源文件用于美化界面
此外,我还利用了网上一位好心博主分析的用于美化组件的头文件和源文件
还有很多宝藏文章也写得很好,真的很感谢
八、心得体会
这次课设个人感觉并没有使用什么很高深的技术,采用的方法也比较笨,不算是巧方法,但最终也算完成了这个课设。也许这个课设并不完美,可能有很多的漏洞还没被我发现,但是我想人总是一步步成长的,不论最终结果如何,这个过程让我受益很多。