前言
本系列排序包括十大經典排序算法。
- 使用的語言爲:Java
- 結構爲: 定義抽象類
Sort
裏面實現了,交換,大小比較等方法。例如交換兩個值,直接傳入下標就可以了。其他的具體排序的類都繼承抽象類Sort
。這樣我們就能專注於算法本身。
/*
* 返回值等於0,代表 array[i1] == array[i2]
* 返回值小於0,代表 array[i1] < array[i2]
* 返回值大於0,代表 array[i1] > array[i2]
*/
protected int cmp(int i1, int i2) {
return array[i1].compareTo(array[i2]);
}
protected int cmp(T v1, T v2) {
return v1.compareTo(v2);
}
protected void swap(int i1, int i2) {
T tmp = array[i1];
array[i1] = array[i2];
array[i2] = tmp;
}
複製代碼
什麼是冒泡排序
- 冒泡排序(Bubble Sort)是一種計算機科學領域的較簡單的排序算法。
- 它重複地走訪過要排序的元素列,依次比較兩個相鄰的元素,如果他們的順序(如從大到小、首字母從A到Z)錯誤就把他們交換過來。走訪元素的工作是重複地進行直到沒有相鄰元素需要交換,也就是說該元素列已經排序完成。
- 這個算法的名字由來是因爲越大的元素會經由交換慢慢“浮”到數列的頂端(升序或降序排列),就如同碳酸飲料中二氧化碳的氣泡最終會上浮到頂端一樣,故名“冒泡排序”。
算法原理
- 比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。
- 對每一對相鄰元素做同樣的工作,從開始第一對到結尾的最後一對。在這一點,最後的元素應該會是最大的數。
- 針對所有的元素重複以上的步驟,除了最後一個。
- 持續每次對越來越少的元素重複上面的步驟,直到沒有任何一對數字需要比較。
算法分析
時間複雜度
- 若文件的初始狀態是正序的,一趟掃描即可完成排序。所需的關鍵字比較次數C和記錄移動次數M 均達到最小值:C=n-1 , M=0。
- 所以,冒泡排序最好的時間複雜度爲O(n) 。
- 若初始文件是反序的,需要進行 n-1 趟排序。每趟排序要進行 n-i 次關鍵字的比較(1≤i≤n-1),且每次比較都必須移動記錄三次來達到交換記錄位置。在這種情況下,比較和移動次數均達到最大值: C = n(n-1)/2 = O(n^2). M = 3n(n-1)/2 = O(n^2).
冒泡排序的最壞時間複雜度爲 O(n^2)。 綜上,因此冒泡排序總的平均時間複雜度爲O(n^2)。
算法穩定性
- 冒泡排序就是把小的元素往前調或者把大的元素往後調。比較是相鄰的兩個元素比較,交換也發生在這兩個元素之間。所以,如果兩個元素相等,是不會再交換的;如果兩個相等的元素沒有相鄰,那麼即使通過前面的兩兩交換把兩個相鄰起來,這時候也不會交換,所以相同元素的前後順序並沒有改變,所以冒泡排序是一種穩定排序算法。
是否是原地算法
- 何爲原地算法?
- 不依賴額外的資源或者依賴少數的額外資源,僅依靠輸出來覆蓋輸入
- 空間複雜度爲 𝑂(1) 的都可以認爲是原地算法
- 非原地算法,稱爲 Not-in-place 或者 Out-of-place
- 冒泡排序屬於 In-place
代碼
代碼一
public class BubbleSort<T extends Comparable<T>> extends Sort<T> {
@Override
protected void sort() {
for (int end = array.length-1; end>0; end--) {
for (int begin = 1; begin <= end; begin++) {
if (cmp(begin, begin-1)<0) {
//ayyay[begin] 小於 ayyay[begin-1] 就交換
swap(begin, begin-1);
}
}
}
}
}
複製代碼
優化
- 我們知道,每次都是兩兩比較,如果已經拍好順序了。可以提前終止排序
public class BubbleSort1<T extends Comparable<T>> extends Sort<T> {
@Override
protected void sort() {
// TODO Auto-generated method stub
for (int end = array.length-1; end >0; end--) {
boolean isSorted = true; //定義布爾值 isSorted來標記是否有交換
for (int begin = 1; begin <= end; begin++) {
//ayyay[begin] 小於 ayyay[begin-1] 就交換
if (cmp(begin, begin-1)<0) {
swap(begin, begin-1);
isSorted = false;
}
}
if (isSorted) {
//來到這裏,說明沒有交換過。已經是完全有序的了。提前終止排序
break;
}
}
}
}
複製代碼
再次優化
- 如果序列尾部已經局部有序,可以記錄最後1次交換的位置,減少比較次數
public class BubbleSort2<T extends Comparable<T>> extends Sort<T> {
@Override
protected void sort() {
// TODO Auto-generated method stub
for (int end = array.length-1; end >0; end--) {
int sortedIndex = 1;
for (int begin = 1; begin <= end; begin++) {
if (cmp(begin, begin-1)<0) {
swap(begin, begin-1);
sortedIndex = begin;
}
}
end = sortedIndex;
}
}
}
複製代碼
驗證
使用數據源如下
Integer[] array = {7, 3, 5, 8, 6, 7, 4, 5,19,30,40,50};
結果爲:
-
【BubbleSort】 穩定性:true 耗時:0.0s(0ms) 比較次數:66 交換次數:14
-
【BubbleSort1】穩定性:true 耗時:0.001s(1ms) 比較次數:51 交換次數:14
-
【BubbleSort2】穩定性:true 耗時:0.0s(0ms) 比較次數:30 交換次數:14
可以明顯感覺到做了優化之後,比較測試減少了。