選擇排序
1.原理
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然後,再從剩餘未排序元素中繼續尋找最小(大)元素,然後放到已排序序列的末尾。以此類推,直到所有元素均排序完畢。
2.特點
①運行時間與輸入無關。
無論輸入初始狀態如何,是否有序,都需要遍歷數組來找出最小的元素。其他算法則更善於利用輸入的初始狀態來優化時間。
②數據移動次數最小。
如果某個元素位於正確的最終位置上,則它不會被移動。選擇排序每次交換一對元素,它們當中至少有一個將被移到其最終位置上,因此對n個元素的表進行排序總共進行至多n-1次交換。在所有的完全依靠交換去移動元素的排序方法中,選擇排序屬於非常好的一種。
3.代碼
public static void selection_sort(int[] arr) {
int i, j, min, temp, len = arr.length;
for (i = 0; i < len - 1; i++) {
min = i;
for (j = i + 1; j < len; j++)
if (arr[min] > arr[j])
min = j;
temp = arr[min];
arr[min] = arr[i];
arr[i] = temp;
}
}
4.複雜度
①交換次數O(n): 最好情況是,已經有序,交換0次;最壞情況是,逆序,交換n-1次
②比較次數O(n^2): (n-1)+(n-2)+…+1=n*(n-1)/2
③賦值次數O(n):0到3(n-1)次之間
5.概述
最差時間複雜度 О(n²)
最優時間複雜度 О(n²)
平均時間複雜度 О(n²)
最差空間複雜度 總共О(n), 需要輔助空間O(1)
動態圖:
選擇排序的示例動畫。紅色表示當前最小值,黃色表示已排序序列,藍色表示當前位置。
插入排序
1.原理
通過構建有序序列,對於未排序數據,在已排序序列中從後向前掃描,找到相應位置並插入。
步湊如下:
①從第一個元素開始,該元素可以認爲已經被排序
②取出下一個元素,在已經排序的元素序列中從後向前掃描
③如果該元素(已排序)大於新元素,將該元素移到下一位置
④重複步驟3,直到找到已排序的元素小於或者等於新元素的位置
⑤將新元素插入到該位置後
⑥重複步驟②~⑤
如果比較操作的代價比交換操作大的話,可以採用二分查找法來減少比較操作的數目。該算法可以認爲是插入排序的一個變種,稱爲二分查找插入排序。
2.特點
直接插入排序是一種穩定的排序。對部分有序的數組十分高效,也很適合小規模數組。
3.代碼
public static void insertion_sort( int[] arr ) {
for( int i=0; i<arr.length-1; i++ ) {
for( int j=i+1; j>0; j-- ) {
if( arr[j-1] <= arr[j] )
break;
int temp = arr[j];
arr[j] = arr[j-1];
arr[j-1] = temp;
}
}
}
另一個版本:
public static void insertion_sort(int[] arr)
{
for (int i = 1; i < arr.length; i++ ) {
int temp = arr[i];
for (int j = i - 1; j >= 0 && arr[j] > temp; j-- ) {
arr[j + 1] = arr[j];
}
arr[j + 1] = temp;
}
4.複雜度
①交換次數O(0): 不需要
②比較次數O(n^2): 最好情況是,已經有序,需(n-1)次即可次;最壞情況是,逆序,需n(n-1)/2次
③賦值次數O(n^2):比較操作的次數加上(n-1)次
平均來說插入排序算法複雜度爲O(n^2)。因而,插入排序不適合對於數據量比較大的排序應用。但是,如果需要排序的數據量很小,例如,量級小於千,那麼插入排序還是一個不錯的選擇。
5.概述
最差時間複雜度 О(n²)
最優時間複雜度 О(n)
平均時間複雜度 О(n²)
最差空間複雜度 總共О(n), 需要輔助空間O(1)
動態圖:
使用插入排序爲一列數字進行排序的過程:
冒泡排序
1.原理
它重複地遍歷過要排序的數列,一次比較兩個元素,如果他們的順序錯誤就把他們交換過來。遍歷數列的工作是重複地進行直到沒有再需要交換,也就是說該數列已經排序完成。
步湊如下:
①比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。
②對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最後一對。這步做完後,最後的元素會是最大的數。
③針對所有的元素重複以上的步驟,除了最後一個。
④持續每次對越來越少的元素重複上面的步驟,直到沒有任何一對數字需要比較。
2.特點
儘管這個算法是最簡單瞭解和實現的排序算法之一,但它對於少數元素之外的數列排序是很沒有效率的。
3.代碼
public static void bubble_sort(int[] arr) {
int i, j, temp, len = arr.length;
for (i = 0; i < len - 1; i++)
for (j = 0; j < len - 1 - i; j++)
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
4.複雜度
①交換次數O(n^2)
②比較次數O(n^2): 可以原地排序。
③賦值次數O(0):不需要,交換就可以了。
冒泡排序是與插入排序擁有相等的運行時間,但是兩種算法在需要的交換次數卻很大地不同。在最好的情況,冒泡排序需要O(n^2)次交換,而插入排序只要最多O(n)交換。冒泡排序的實現通常會對已經排序好的數列拙劣地運行O(n^2),而插入排序在這個例子只需要O(n)個運算。
5.概述
最差時間複雜度 О(n²)
最優時間複雜度 О(n)
平均時間複雜度 О(n²)
最差空間複雜度 總共О(n), 需要輔助空間O(1)
動態圖:
希爾排序
1.原理
將數組列在一個表中並對列排序(用插入排序)。重複這過程,不過每次用更長的列來進行。最後整個表就只有一列了。將數組轉換至表是爲了更好地理解這算法,算法本身僅僅對原數組進行排序(通過增加索引的步長,例如是用i += step_size而不是i++)。
例如,假設有這樣一組數[ 13 14 94 33 82 25 59 94 65 23 45 27 73 25 39 10 ],如果我們以步長爲5開始進行排序,我們可以通過將這列表放在有5列的表中來更好地描述算法,這樣他們就應該看起來是這樣:
13 14 94 33 82
25 59 94 65 23
45 27 73 25 39
10
然後我們對每列進行排序:
10 14 73 25 23
13 27 94 33 39
25 59 94 65 82
45
將上述四行數字,依序接在一起時我們得到:[ 10 14 73 25 23 13 27 94 33 39 25 59 94 65 82 45 ].這時10已經移至正確位置了,然後再以3爲步長進行排序:
10 14 73
25 23 13
27 94 33
39 25 59
94 65 82
45
排序之後變爲:
10 14 13
25 23 33
27 25 59
39 65 73
45 94 82
94
最後以1步長進行排序(此時就是簡單的插入排序了)。
2.特點
步長的選擇是希爾排序的重要部分。只要最終步長爲1任何步長序列都可以工作。算法最開始以一定的步長進行排序。然後會繼續以一定步長進行排序,最終算法以步長爲1進行排序。當步長爲1時,算法變爲插入排序,這就保證了數據一定會被排序。
3.代碼
public static void shell_sort(int[] arr) {
int gap = 1, i, j, len = arr.length;
int temp;
while (gap < len / 3)
gap = gap * 3 + 1; // <O(n^(3/2)) by Knuth,1973>: 1, 4, 13, 40, 121, ...
for (; gap > 0; gap /= 3)
for (i = gap; i < len; i++) {
temp = arr[i];
for (j = i - gap; j >= 0 && arr[j] > temp; j -= gap)
arr[j + gap] = arr[j];
arr[j + gap] = temp;
}
}
4.複雜度
5.概述
最差時間複雜度 根據步長序列的不同而不同,最好時爲О(n(logn)^2)
最優時間複雜度 O(n)
平均時間複雜度 根據步長序列的不同而不同
最差空間複雜度 О(n)
動態圖: