漸進法分析冒泡/選擇排序法時間復雜度


漸進分析

漸進分析是一種數學方法,漸進分析技術能夠在數量級上對算法進行精確度量。但是,數學不是萬能的,實際上,許多貌似簡單的算法很難用數學的精確性和嚴格性來分析,尤其分析平均情況。算法的實驗分析是一種事后計算的方法,通常需要將算法轉換為對應的程序並上機運行。
計數法是在算法中的適當位置插入一些計數器,來度量算法中基本語句的執行次數。生成合適的測試樣例作為測試的基准,並對輸入實例運行算法對應的程序,記錄得到的實驗數據。最后根據實驗得到的數據,結合實驗目的,對算法結果進行分析。

設計思路

實驗首先需要生成合適的測試樣例,為了盡可能追求實驗結果的一般性,將生成 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)) + '\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容器,輸出排序結果檢驗排序是否正確。

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM