1. 程式人生 > >演算法卷軸(排序卷)[氣泡排序]

演算法卷軸(排序卷)[氣泡排序]

定義

烈日炎炎的夏天,沒有什麼比一杯冰鎮汽水更能讓人平靜了。

小氣泡從杯底慢慢浮到表面的過程,就像氣泡排序的原理一樣。

把每個元素當做一個氣泡,每個元素與相鄰的元素兩兩比較,根據大小來交換位置,一點點往陣列的一端移動,最終得到一個有序的集合。

栗子

現在有 6 個數字組成的無序數列:
6, 4, 7, 2, 8, 5

  • 第一步:比較 6 和 4,發現 6 比 4 大,所以 6 和 4 交換位置。
    4, 6, 7, 2, 8, 5
  • 第二步:比較 6 和 7,發現 6 比 7 小,所以位置不變。
  • 第三步:比較 7 和 2,發現 7 比 2 大,所以 7 和 2 交換位置。
    4, 6, 2, 7, 8, 5
  • 第四步:比較 7 和 8,發現 7 比 8 小,所以位置不變。
  • 第五步:比較 8 和 5,發現 8 比 5 大,所以 8 和 5 交換位置。
    4, 6, 2, 7, 5, 8
    這時候,氣泡排序的第一輪就完成了,8 作為最大的元素,就像汽水裡的氣泡一樣,浮到了最右側。
    下面,我們來進行第二輪排序:
  • 第一步:比較 4 和 6,發現 4 比 6 小,所以位置不變。
  • 第二步:比較 6 和 2,發現 6 比 2 大,所以 6 和 2 交換位置。
    4, 2, 6, 7, 5, 8
  • 第三步:比較 6 和 7,發現 6 比 7 小,所以位置不變。
  • 第四步:比較 7 和 5,發現 7 比 5 大,所以 7 和 5 交換位置。
    4, 2, 6, 5, 7, 8
  • 第五步:比較 7 和 8,發現 7 比 8 小,所以位置不變。
    第二輪排序結束後,數列右側的有序區已經有了兩個元素:7、8。
    第三輪排序結束後:
    2, 4, 5, 6, 7, 8

    第四輪排序結束後(已經是有序的了,所以順序沒有變化):
    2, 4, 5, 6, 7, 8
    第五輪排序結束後(已經是有序的了,所以順序沒有變化):
    2, 4, 5, 6, 7, 8
    第六輪排序結束後(已經是有序的了,所以順序沒有變化):
    2, 4, 5, 6, 7, 8
    冒泡演算法的每一輪排序,都要遍歷所有元素,所以時間複雜度是 O(n²)。

實現

原始版

public static void sort(int[] array) {
	int temp = 0;
	for (int i = 0; i < array.length; i++) {
		for (int j = i + 1; j < array.length; j++) {
			if (array[i] > array[j]) {
			temp = array[i];
			array[i] = array[j];
			array[j] = temp;
			}
		}
	}
}

public static void main(String[] args) {
	int[] array = new int[] { 6, 4, 7, 2, 8, 5 };
	sort(array);
	// 結果:[2, 4, 5, 6, 7, 8]
	System.out.println(Arrays.toString(array));
}

Q:從上邊的栗子來看,第三輪排序結束後,已經得到了有序的數列,但按照原始版的實現方式,還需要再進行 3 輪排序,怎樣優化呢?
A:我們可以在判斷出數列已經有序時,做出標記,讓程式提前結束工作。

升級版

public static void sort(int[] array) {
	int temp = 0;
	for (int i = 0; i < array.length; i++) {
		// 每輪排序前,假設已經是有序的了,有序標記都是 true
		boolean isSorted = true;
		for (int j = i + 1; j < array.length; j++) {
			if (array[i] > array[j]) {
				temp = array[i];
				array[i] = array[j];
				array[j] = temp;
				// 如果需要調整,說明還不是有序數列,標記為 false
				isSorted = false;
			}
		}
		// 如果數列已經有序,不再進行下一輪排序
		if (isSorted) {
			break;
		}
	}
}

public static void main(String[] args) {
	int[] array = new int[] { 6, 4, 7, 2, 8, 5 };
	sort(array);
	// 結果:[2, 4, 5, 6, 7, 8]
	System.out.println(Arrays.toString(array));
}

Q:現在的氣泡排序實現方式,有序區域的長度跟排序輪數是一致的,但如果數列本來就存在有序的部分,在前幾輪重複比較有序的元素是沒有意義的,怎樣優化呢?
A:我們可以在每一輪排序結束時,記錄下最後一次交換元素的位置,這個位置就是無序數列的邊界,相反區域就是有序數列了。

最終版

public static void sort(int[] array) {
	int temp = 0;
	// 最後一次交換的位置
	int lastExchange = 0;
	for (int i = 0; i < array.length; i++) {
		// 每輪排序前,假設已經是有序的了,有序標記都是 true
		boolean isSorted = true;
		for (int j = lastExchange; j < array.length; j++) {
			if (array[i] > array[j]) {
				temp = array[i];
				array[i] = array[j];
				array[j] = temp;
				// 如果需要調整,說明還不是有序數列,標記為 false
				isSorted = false;
				// 記錄最後一次交換的位置
				lastExchange = j;
			}
		}
		// 如果數列已經有序,不再進行下一輪排序
		if (isSorted) {
			break;
		}
	}
}

public static void main(String[] args) {
	int[] array = new int[] { 6, 4, 7, 2, 8, 5 };
	sort(array);
	// 結果:[2, 4, 5, 6, 7, 8]
	System.out.println(Arrays.toString(array));
}