1. 排序算法?
排序算法應該算是咱們最熟悉的算法了,咱們學的第一個算法,可能就是排序算法,而在實際應用中,排序算法也常常會被用到,其重要做用不言而喻。
經典的排序算法有:冒泡排序、插入排序、選擇排序、歸併排序、快速排序、計數排序、基數排序、桶排序。按照時間複雜度,能夠分爲如下三類。算法
![排序算法 排序算法](http://static.javashuo.com/static/loading.gif)
2. 如何分析一個排序算法?
2.1. 排序算法的執行效率
- 最好狀況、最壞狀況、平均狀況時間複雜度。咱們不只要知道一個排序算法的最好狀況、最壞狀況、平均狀況時間複雜度,還要知道它們分別對應的原始數據是什麼樣的。有序度不一樣的數據,對於排序算法的執行時間確定是有影響的。
- 時間複雜度的係數、常量、低階。時間複雜度反映的是數據規模很是大時的一個增加趨勢,但實際中,咱們面臨的數據多是 10 個、100 個、 1000 個這樣的小數據,所以以前咱們忽略的係數、常量、低階也要考慮進來。
- 比較次數和交換(或移動)次數。基於比較的排序算法涉及到兩個主要操做,一個是元素比較大小,另外一個是元素交換或移動。
2.2. 排序算法的內存消耗
- 算法的內存消耗能夠用空間複雜度來衡量,針對排序算法的空間複雜度,咱們引入了一個新的概念,原地排序(Sorted in place),特指空間複雜度爲 $O(1)$ 的排序算法,即指直接在原有數據結構上進行排序,無需額外的內存消耗。
2.3. 排序算法的穩定性
- 排序算法的穩定性指的是,若是待排序的序列中存在值相等的數據,通過排序以後,相等元素之間原有的前後循序不變。
- 好比一組數據 2, 9, 3, 4, 8, 3,按照大小排序以後就是 2, 3, 3, 4, 8, 9,若是排序後兩個 3 的順序沒有發生改變,咱們就把這種算法叫做穩定的排序算法,反之就叫做不穩定的排序算法。
- 咱們學習排序的時候通常都是用整數來舉例,但在真正的軟件開發中,待排序的數據每每不是單純的整數,而是一組對象,咱們須要按照對象的某一個鍵值對數據進行排序。
- 假設咱們有 10 萬條訂單數據,要求金額從小到大排序,而且金額相同的訂單按照下單時間從早到晚排序。這時候,穩定排序就發揮了其做用,咱們能夠先按照下單時間對數據進行排序,而後再用穩定排序算法按照訂單金額從新排序。
![穩定排序算法 穩定排序算法](http://static.javashuo.com/static/loading.gif)
3. 冒泡排序(Bubble Sort)?
3.1. 冒泡排序算法實現
- 冒泡排序只會操做相鄰的兩個數據,將其調整到正確的順序。一次冒泡會讓至少一個數據移動到它應該在的位置,冒泡 n 次,就完成了 n 個數據的排序工做。
![冒泡排序 冒泡排序](http://static.javashuo.com/static/loading.gif)
![冒泡排序 冒泡排序](http://static.javashuo.com/static/loading.gif)
- 若某一次冒泡沒有數據移動,則說明數據已經徹底達到有序,不用再繼續執行後續的冒泡操做,針對此咱們能夠再對剛纔的算法進行優化。
![冒泡排序 冒泡排序](http://static.javashuo.com/static/loading.gif)
// O(n^2)
void Bubble_Sort(float data[], int n)
{
int i = 0, j = 0;
int temp = 0;
int flag = 0;
for(i = n-1; i > 0; i--)
{
flag = 0;
for(j = 0; j < i; j++)
{
if(data[j+1] < data[j])
{
temp = data[j];
data[j] = data[j+1];
data[j+1] = temp;
flag = 1;
}
}
if(!flag)//If no data needs to be exchanged, the sort finishes.
{
break;
}
}
}
3.2. 冒泡排序算法分析
- 冒泡排序是一個原地排序算法,只須要常量級的臨時空間。
- 冒泡排序是一個穩定的排序算法,當元素大小相等時,咱們沒有進行交換。
- 最好狀況下,數據已是有序的,咱們只須要進行一次冒泡便可,時間複雜度爲 $O(n)$。最壞狀況下,數據恰好是倒序的,咱們須要進行 n 次冒泡,時間複雜度爲 $O(n^2)$。
3.3. 有序度和逆序度
- 有序度是數組中具備有序關係的元素對的個數。有序元素對定義:a[i] <= a[j], 若是 i < j。
![有序度 有序度](http://static.javashuo.com/static/loading.gif)
- 徹底倒序排列的數組,其有序度爲 0;徹底有序的數組,其有序度爲 $C_n^2 = \frac{n*(n-1)}{2}$,咱們把這種徹底有序的數組叫做滿有序度。
- 逆序度和有序度正好相反,逆序元素對定義:a[i] > a[j], 若是 i < j。
- 逆序度 = 滿有序度 - 有序度。排序的過程就是一個增長有序度減小逆序度的過程,最後達到滿有序度,排序就完成了。
- 在冒泡排序中,每進行一次交換,有序度就加 1。無論算法怎麼改進,交換次數老是肯定的,即爲逆序度。
- 最好狀況下,須要的交換次數爲 0;最壞狀況下,須要的交換次數爲 $\frac{n * (n-1)}{2}$。平均狀況下,須要的交換次數爲 $\frac{n * (n-1)}{4}$,而比較次數確定要比交換次數多,而複雜度的上限是 $O(n^2)$,因此,平均時間複雜度也就是 $O(n^2)$。
4. 插入排序(Insertion Sort)
4.1. 插入排序算法實現
- 往一個有序的數組插入一個新的元素時,咱們只須要找到新元素正確的位置,就能夠保證插入後的數組依然是有序的。
![插入元素 插入元素](http://static.javashuo.com/static/loading.gif)
- 插入排序就是從第一個元素開始,把當前數據的左側看做是有序的,而後將當前元素插入到正確的位置,依次日後進行,直到最後一個元素。
- 對於不一樣的查找插入點方法(從頭至尾、從尾到頭),元素的比較次數是有區別的,但移動次數是肯定的就等於逆序度。
- 代碼實現
// O(n^2)
void Insertion_Sort(float data[], int n)
{
int i = 0, j = 0;
int temp = 0;
for(i = 1; i < n; i++)
{
temp = data[i];
for(j = i; j > 0; j--)
{
if(temp < data[j-1])
{
data[j] = data[j-1];
}
else// The data ahead has been sorted correctly.
{
break;
}
}
data[j] = temp; // insert the data
}
}
4.2. 插入排序算法分析
- 插入排序是一個原地排序算法,只須要常量級的臨時空間。
- 插入排序是一個穩定的排序算法,當元素大小相等時,咱們不進行插入。
- 最好狀況下,數據已是有序的,從尾到頭進行比較的話,每次咱們只須要進行一次比較便可,時間複雜度爲 $O(n)$。最壞狀況下,數據恰好是倒序的,咱們每次都要在數組的第一個位置插入新數據,有大量的移動操做,時間複雜度爲 $O(n^2)$。
- 在數組中插入一個元素的平均時間複雜度爲$O(n)$,這裏,咱們須要循環執行 n 次插入操做,因此平均時間複雜度爲 $O(n^2)$。
5. 選擇排序(Selection Sort)
5.1. 選擇排序算法實現
- 選擇排序就是從第一個元素開始,從當前數據的右側未排序區間中選取一個最小的元素,而後放到左側已排序區間末尾,依次日後進行,直到最後一個元素。
![選擇排序 選擇排序](http://static.javashuo.com/static/loading.gif)
// O(n^2)
void Selection_Sort(float data[], int n)
{
int i = 0, j = 0, k = 0;
int temp = 0;
for(i = 0; i < n-1; i++)
{
k = i;
for(j = i+1; j < n; j++)
{
if(data[j] < data[k])
{
k = j;
}
}
if(k != i)
{
temp = data[i];
data[i] = data[k];
data[k] = temp;
}
}
}
5.2. 選擇排序算法分析
- 選擇排序是一個原地排序算法,只須要常量級的臨時空間。
- 選擇排序的最好狀況時間複雜度、最壞狀況時間複雜度和平均狀況時間複雜度都爲 $O(n^2)$,由於無論數據排列狀況怎樣,都要進行相同次數的比較。
- 選擇排序是一個不穩定的排序算法,由於每次都要從右側未排序區間選擇一個最小值與前面元素交換,這種交換會打破相等元素的原始位置。
6. 爲何插入排序比冒泡排序更受歡迎?
交換排序和插入排序的時間複雜度都爲 $O(n^2)$,也都是原地排序算法,爲何插入排序更受歡迎呢?
- 前面分析,插入排序的移動次數等於逆序度,冒泡排序的交換次數等於逆序度,但冒泡排序每次交換須要進行三次賦值操做,而插入排序每次移動只須要一次賦值操做,其相應的真實運行時間也會更短。
冒泡排序中數據的交換操做:
if (a[j] > a[j+1]) { // 交換
int tmp = a[j];
a[j] = a[j+1];
a[j+1] = tmp;
flag = true;
}
插入排序中數據的移動操做:
if (a[j] > value) {
a[j+1] = a[j]; // 數據移動
} else {
break;
}
7. 小結
![三種排序算法對比 三種排序算法對比](http://static.javashuo.com/static/loading.gif)
- 冒泡排序和選擇排序在實際中應用不多,僅僅停留在理論層次便可,選擇排序算法仍是挺有用的,並且其還有很大的優化空間,好比希爾排序。
參考資料-極客時間專欄《數據結構與算法之美》數組
獲取更多精彩,請關注「seniusen」!
數據結構