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


    渐进分析

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

    设计思路

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

    数据生成

    数据生成脚本

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

    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)) + '
    ')
            #file_object.write(str(i) + '
    ')
            #file_object.write(str(10000 - i) + '
    ')
    
    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容器,输出排序结果检验排序是否正确。

    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 种算法的实现方式编写函数,注意要在基本语句——比较和交换语句处设置计数器。当算法执行完毕时输出基本语句的执行次数,进行记录。

    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介绍,使用方法与例子

  • 相关阅读:
    Pytest单元测试框架——Pytest+Allure+Jenkins的应用
    Postman+Newman+Git+Jenkins接口自动化测试
    Pytest单元测试框架——Pytest简介
    unittest单元测试框架
    Postman学习笔记(二)
    CukeTest+Puppeteer的Web自动化测试(二)
    Postman学习笔记(一)
    CukeTest+Puppeteer的Web自动化测试(一)
    Puppeteer笔记(八):Puppeteer执行自定义Javascript方法
    Puppeteer笔记(七):Puppeteer切换浏览器TAB页
  • 原文地址:https://www.cnblogs.com/linfangnan/p/13773720.html
Copyright © 2020-2023  润新知