渐进法分析冒泡/选择排序法时间复杂度

渐进分析#

渐进分析是一种数学方法,渐进分析技术能够在数量级上对算法进行精确度量。但是,数学不是万能的,实际上,许多貌似简单的算法很难用数学的精确性和严格性来分析,尤其分析平均情况。算法的实验分析是一种事后计算的方法,通常需要将算法转换为对应的程序并上机运行。
计数法是在算法中的适当位置插入一些计数器,来度量算法中基本语句的执行次数。生成合适的测试样例作为测试的基准,并对输入实例运行算法对应的程序,记录得到的实验数据。最后根据实验得到的数据,结合实验目的,对算法结果进行分析。

设计思路#

实验首先需要生成合适的测试样例,为了尽可能追求实验结果的一般性,将生成 3 组不同规模的实验数据。每组数据在规模不同的基础上,需要包含 3 种不同特点的数据。由于做的是排序算法时间复杂度的分析,因此 3 种不同情况分别为最好(正序)、最差(倒叙)和随机情况。
接着需要编写运行不同排序算法的程序,由于数据集的规模不同,使用纯 C 语言的动态内存分配并不能很好地适应不同数据集。因此此处选择使用 STL 库中的 vector 容器来自动管理内存,依次适应不同规模的测试数据。通过添加计数变量来记录基本语句的执行次数,在每个数据集运行完毕后输出,进行实验数据统计。

数据生成#

数据生成脚本#

由于实验中的实验数据打算从文件中读取,因此生成数据时需要把数据保存在文件中。选择使用 Python 脚本生成实验所需数据集:

Copy Highlighter-hljs
import random filename = 'XXX.txt' with open(filename, 'w') as file_object: for i in range(10000): file_object.write(str(random.randint(-10000,10000)) + '\n') #file_object.write(str(i) + '\n') #file_object.write(str(10000 - i) + '\n') print("成功生成数据集" + filename)

数据集概况#

数据集序号 数据集数据量(个) 数据集特点
1 100 正序自然数等差数列
2 100 (-10000,10000)随机数
3 100 (-10000,10000)随机数
4 100 逆序自然数等差数列
5 1000 正序自然数等差数列
6 1000 (-10000,10000)随机数
7 1000 (-10000,10000)随机数
8 1000 逆序自然数等差数列
9 10000 正序自然数等差数列
10 10000 (-10000,10000)随机数
11 10000 (-10000,10000)随机数
12 10000 逆序自然数等差数列

算法程序#

主函数#

首先编写试验所需的程序框架,即主函数。设计输入数据集名时,程序接受文件名,然后把文件名交付给 file_Read()文件读取函数进行读取。使用 C++ STL 库的 vector 容器进行存储数据,因此 file_Read()函数的返回值应该是存储文件中所有数据的 vector 容器。由于实验实现2种排序算法,因此程序需要实现2种算法对应的函数。主函数调用排序算法进行排序并回显基本语句数量,进行试验数据记录。最后使用迭代器遍历排序完毕的 vector容器,输出排序结果检验排序是否正确。

Copy Highlighter-hljs
int main() { vector<int> dataset; vector<int>::iterator it; char file_name[10]; cin >> file_name; dataset = file_Read(file_name); dataset = BubbleSort(dataset); //dataset = SelectSort(dataset); /*for(it = dataset.begin(); it!= dataset.end(); it++) { cout << *it << endl; }*/ return 0; }

排序函数#

选取冒泡排序法和选择排序法进行分析,分别按照 2 种算法的实现方式编写函数,注意要在基本语句——比较和交换语句处设置计数器。当算法执行完毕时输出基本语句的执行次数,进行记录。

Copy Highlighter-hljs
vector<int> BubbleSort(vector<int> dataset) //冒泡排序 { int temp; int compare_count = 0; int exchange_count = 0; int exchange = dataset.size() - 1; int bound; while(exchange != 0) { bound = exchange; exchange = 0; for(int i = 0; i < bound; i++) { compare_count++; if(dataset[i] > dataset[i + 1]) { exchange = i; temp = dataset[i]; dataset[i] = dataset[i + 1]; dataset[i + 1] = temp; exchange_count += 3; } } } cout << "比较次数为:" << compare_count << endl; cout << "交换次数为:" << exchange_count << endl; return dataset; } vector<int> SelectSort(vector<int> dataset) //选择排序 { int idx; int temp; int compare_count = 0; int exchange_count = 0; for (int i = 0; i < dataset.size(); i++) { idx = i; for (int j = i + 1; j < dataset.size(); j++) { compare_count++; if (dataset[idx] < dataset[j]) { idx = j; } } temp = dataset[i]; dataset[i] = dataset[idx]; dataset[idx] = temp; exchange_count += 3; } cout << "比较次数为:" << compare_count << endl; cout << "交换次数为:" << exchange_count << endl; return dataset; }

记录实验数据#

依次输入 12 个数据集,分别运行冒泡排序法和选择排序法,所获取的实验数据如下:

数据集序号 冒泡排序比较次数 冒泡排序交换次数 选择排序比较次数 选择排序交换次数
1 99 0 4950 300
2 4895 7257 4950 300
3 4745 7668 4950 300
4 4950 14850 4950 300
5 999 0 499500 3000
6 495821 771342 499500 3000
7 496056 756987 499500 3000
8 499500 1498500 499500 3000
9 9999 0 49995000 30000
10 49931801 75580422 49995000 30000
11 49925382 74716506 49995000 30000
12 49995000 149985000 49995000 30000

实验数据分析#

首先对比较次数进行分析,3种规模的数据条形图如下。可以明显地看到,当数据已经基本有序时,冒泡排序算法能够在很低的次数就完成排序。当数据完全失序或者处于较为随机的状态时,冒泡排序算法的比较次数略小于选择排序,但是差别并不大。这个趋势会随着数据的规模增大而变得更加明显。



接下来分析交换次数,3 种规模的数据条形图如下。可以明显地看出虽然在基本有序的情况下,冒泡排序的交换次数为 0。但是在其他情况下冒泡排序算法的交换次数远大于选择排序,尤其是在完全失序的情况下,冒泡排序算法的交换次数甚至是随机情况下的 2 倍。而选择排序的交换次数是固定的,是数据集数据量的 3 倍。



最后分析算法基本语句的总执行次数,3 种规模的数据条形图如下。在数据基本有序的情况下,冒泡排序的基本语句执行次数远小于选择排序。但是在其他情况,冒泡排序的基本语句执行次数会是选择排序的 2 倍以上,当完全失序时甚至能达到 3 倍以上。


时间复杂度#

首先我们看冒泡排序。在最好情况下,也就是初始序列是一趟排序时,只需要进行一趟排序。排序过程中进行 n-1 次关键字比较,且不移动记录。在初始序列为逆序的最坏情况下,需要进行 n-1 趟排序,总的比较次数 num1 为:

总的移动次数 num2 为:

所以在平均情况下,冒泡排序的关键字比较次数和记录移动次数分别约为 n^2/4 和 3n^2/4,时间复杂度为 O(n^2) 。从上文的统计数据来看,冒泡排序的基本语句执行次数远大于n的一次方阶,远小于n的三次方阶,与平方阶的数量级更为接近。其中最坏情况时间复杂度为 O(n^2),最好情况时间复杂度为
O(1)。
接下来看看选择排序,选择排序所需要进行移动的次数较少。最好情况,也就是数据集时正序序列时不需要移动。在逆序的最坏情况下,算法需要移动 3(n-1)次。无论记录的初始排列如何,所需进行的关键字间比较次数相同,num 值都是:

因此选择排序算法的时间复杂度也是 O(n^2)。从上文的统计数据来看,选择排序的基本语句执行次数远大于 n 的一次方阶,远小于 n 的三次方阶,与平方阶的数量级更为接近。其中最坏情况和最好情况的时间复杂度都是 O(n^2)。

参考资料#

《数据结构(C语言版|第二版)》—— 严蔚敏 李冬梅 吴伟民 编著,人民邮电出版社
《算法设计与分析(第二版)》——王红梅,胡明 编著,清华大学出版社
算法:排序
c++输入文件流ifstream用法详解
C++ stringstream介绍,使用方法与例子

posted @   乌漆WhiteMoon  阅读(1222)  评论(0编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示
CONTENTS