1 如何評價、分析一個排序算法?
很多語言、數據庫都已經封裝了關於排序算法的實現代碼。所以我們學習排序算法目的更多的不是為了去實現這些代碼,而是靈活的應用這些算法和解決更為復雜的問題,所以更重要的是學會如何評價、分析一個排序算法並在合適的場景下正確使用。
分析一個排序算法,主要從以下3個方面入手:
1.1 排序算法的執行效率
1)最好情況、最壞情況和平均情況時間復雜度
待排序數據的有序度對排序算法的執行效率有很大影響,所以分析時要區分這三種時間復雜度。除了時間復雜度分析,還要知道最好、最壞情況復雜度對應的要排序的原始數據是什么樣的。
2)時間復雜度的系數、常數和低階
時間復雜度反映的是算法執行時間隨數據規模變大的一個增長趨勢,平時分析時往往忽略系數、常數和低階。但如果我們排序的數據規模很小,在對同一階時間復雜度的排序算法比較時,就要把它們考慮進來。
3)比較次數和交換(移動)次數
內排序算法中,主要進行比較和交換(移動)兩項操作,所以高效的內排序算法應該具有盡可能少的比較次數和交換次數。
1.2 排序算法的內存消耗
也就是分析算法的空間復雜度。這里還有一個概念—原地排序,指的是空間復雜度為O(1)的排序算法。
1.3 穩定性
如果待排序的序列中存在值相等的元素,經過排序之后,相等元素之間原有的先后順序不變,那么這種排序算法叫做穩定的排序算法;如果前后順序發生變化,那么對應的排序算法就是不穩定的排序算法。
在實際的排序應用中,往往不是對單一關鍵值進行排序,而是要求排序結果對所有的關鍵值都有序。所以,穩定的排序算法往往適用場景更廣。
2 三種時間復雜度為O(n2)的排序算法
2.1 冒泡排序
2.1.1 原理
兩兩比較相鄰元素是否有序,如果逆序則交換兩個元素,直到沒有逆序的數據元素為止。每次冒泡都會至少讓一個元素移動到它應該在的位置。
2.1.2 實現
void BubbleSort(int *pData, int n) //冒泡排序 { int temp = 0; bool orderlyFlag = false; //序列是否有序標志 for (int i = 0; i < n && !orderlyFlag; ++i) //執行n次冒泡 { orderlyFlag = true; for (int j = 0; j < n - 1 - i; ++j) //注意循環終止條件 { if (pData[j] > pData[j + 1]) //逆序 { orderlyFlag = false; temp = pData[j]; pData[j] = pData[j + 1]; pData[j + 1] = temp; } } } }
測試結果

2.1.3 算法分析
1)時間復雜度
最好情況時間復雜度:當待排序列已有序時,只需一次冒泡即可。時間復雜度為O(n);
最壞情況時間復雜度:當待排序列完全逆序時,需要n次冒泡。時間復雜度為O(n2);
平均情況時間復雜度:當待排序列完全逆序時,逆序度為n * (n - 1) / 2。只有當交換逆序對時才會才會使得有序,取中間逆序度n * (n - 1) / 4,那么就要進行n * (n - 1) /4次交換,而比較的次數大於交換次數,所以平均情況時間復雜度為O(n2)。
2)空間復雜度
只借助了一個臨時變量temp,所以空間復雜度為O(1)。
3)穩定性
該算法中只有交換操作會改變數據元素的順序,只要我們在數據元素值相等時不交換數據元素,那么算法就是穩定的。
4)比較和交換的次數
交換操作的執行次數與逆序度相等,比較操作的執行次數大於等於逆序度小於等於n * (n - 1) / 2。
2.2 插入排序
2.2.1 原理
將待排序序列分為已排序區間和未排序區間,開始時已排序區間只有一個數據元素也就是序列的第一個元素,將未排序區間中的數據元素插入已排序區間中同時保持已排序區間的有序,直到未排序區間沒有數據元素。
2.2.2 實現
void InsertSort(int *pData, int n) //插入排序 { int temp = 0, i, j; for (i = 1; i < n; ++i) //未排序區間 { if (pData[i] < pData[i - 1]) //逆序 { temp = pData[i]; for (j = i - 1; pData[j] > temp; --j) //搬移數據元素 pData[j + 1] = pData[j]; pData[j + 1] = temp; //插入數據 } } }
測試結果:

2.2.3 算法分析
1)時間復雜度
最好情況時間復雜度:當待排序列已有序時,只需遍歷一次即可完成排序。時間復雜度為O(n);
最壞情況時間復雜度:當待排序列完全逆序時,需要進行n-1次數據搬移和插入操作。時間復雜度為O(n2);
平均情況時間復雜度:與冒泡法的分析過程一樣,平均情況時間復雜度為O(n2)。
2)空間復雜度
排序過程中只需要一個臨時變量存儲待插入數據,空間復雜度為O(1)。
3)穩定性
插入排序過程中只有插入操作會改變數據元素的相對位置,只要元素大小比較時相等情況下不進行插入操作,插入排序算法就是穩定的。
4)比較操作和數據搬移操作執行次數
數據搬移操作執行次數和逆序度相同。比較操作次數大於等於逆序度,小於等於n * (n - 1) / 2。
2.3 選擇排序
2.3.1 原理
選擇排序的原理類似於插入排序都分為已排序區間和未排序區間,選擇排序的已排序區間初始大小為零,每次從未排序區間取關鍵值最大(或最小)的數據元素放在已排序區間的后一個位置,直到未排序區間沒有數據元素則完成排序。
2.3.2 實現
void SelectSort(int *pData, int n) //選擇排序 { int i, j, min, temp; for (i = 0; i < n; ++i) //未排序區間 { min = i; //最小值下標 for (j = i + 1; j < n; ++j) { if (pData[min] > pData[j]) //逆序 min = j; //保存當前較小值下標 } if (i != min) //如果不是最小值,交換元素 { temp = pData[i]; pData[i] = pData[min]; pData[min] = temp; } } }
測試結果:

2.3.3 算法分析
1)時間復雜度
不管是已有序序列還是完全逆序序列,都要進行n次遍歷無序區間操作,時間復雜度為O(n2)。
2)空間復雜度
排序過程中只需要保存每次遍歷無序區間最小值的下標和第i個元素的數值,所以空間復雜度為O(1)。
3)穩定性
選擇排序算法中改變數據元素相對位置的操作為交換操作,當第i次中第i個數據元素不為當前無序區間最小值時則和最小值交換數據元素。當有重復元素時,就有可能發生相對位置改變。例如5,3,4,5,1第一次選擇操作后為1,3,4,5,5,此時兩個5的相對位置已經改變。所以選擇排序算法不是穩定的。
4)比較操作和交換操作的執行次數
比較操作執行次數為n * (n - 1) / 2,交換操作執行次數小於等於n-1。
2.4 三種算法之間的比較
1)一般待排序列長度n較小時,我們選擇這三種排序算法;
2)當排序要求穩定時,一般選擇插入排序,因為相同的情況下,移動數據比交換數據執行速度快;
3)當數據元素信息量較大時,可以考慮用選擇排序,因為它交換操作執行次數最少。
該篇博客是自己的學習博客,水平有限,如果有哪里理解不對的地方,希望大家可以指正!
