氣泡排序(演算法)
常見演算法分類
十種常見排序演算法一般分為以下幾種:
(1)非線性時間比較類排序:交換類排序(快速排序和氣泡排序)、插入類排序(簡單插入排序和希爾排序)、選擇類排序(簡單選擇排序和堆排序)、歸併排序(二路歸併排序和多路歸併排序);
(2)線性時間非比較類排序:計數排序、基數排序和桶排序。
總結:
(1)在比較類排序中,歸併排序號稱最快,其次是快速排序和堆排序,兩者不相伯仲,但是有一點需要注意,資料初始排序狀態對堆排序不會產生太大的影響,而快速排序卻恰恰相反。
(2)線性時間非比較類排序一般要優於非線性時間比較類排序,但前者對待排序元素的要求較為嚴格,比如計數排序要求待排序數的最大值不能太大,桶排序要求元素按照 hash 分桶後桶內元素的數量要均勻。線性時間非比較類排序的典型特點是以空間換時間。
交換類排序
交換排序的基本方法是:兩兩比較待排序記錄的排序碼,交換不滿足順序要求的偶對,直到全部滿足位置。常見的氣泡排序和快速排序就屬於交換類排序。
氣泡排序
演算法思想:
從陣列中第一個數開始,依次遍歷陣列中的每一個數,通過相鄰比較交換,每一輪迴圈下來找出剩餘未排序數中的最大數並”冒泡”至數列的頂端。
演算法步驟:
(1)從陣列中第一個數開始,依次與下一個數比較並次交換比自己小的數,直到最後一個數。如果發生交換,則繼續下面的步驟,如果未發生交換,則陣列有序,排序結束,此時時間複雜度為O(n);
(2)每一輪”冒泡”結束後,最大的數將出現在亂序數列的最後一位。重複步驟(1)。
穩定性:穩定排序。
時間複雜度: O(n)至O(n2),平均時間複雜度為O(n2)。
最好的情況:如果待排序資料序列為正序,則一趟冒泡就可完成排序,排序碼的比較次數為n-1次,且沒有移動,時間複雜度為O(n)。
最壞的情況:如果待排序資料序列為逆序,則氣泡排序需要n-1次趟起泡,每趟進行n-i次排序碼的比較和移動,即比較和移動次數均達到最大值:
比較次數:Cmax=∑i=1n−1(n−i)=n(n−1)/2=O(n2)
移動次數等於比較次數,因此最壞時間複雜度為O(n2)。
基礎程式碼示例:
package com.bubblesort; /** * 氣泡排序(針對陣列) * * @author Lenovo * */ public class Bubble { public void bubblesort(int array[]) { int count = 0; // 需要排序的次數 // 迴圈的次數為陣列長度減一,剩下的一個數不需要排序 for (int i = 0; i < array.length - 1; i++) { // 迴圈次數為待排序數第一位數冒泡至最高位的比較次數 for (int j = 0; j < array.length - i - 1; j++) { if (array[j] > array[j + 1]) { int temp = array[j + 1]; array[j + 1] = array[j]; array[j] = temp; } // 交換或者使用如下方式 // a=a^b; // b=b^a; // a=a^b; count++; } } for (int i = 0; i < array.length; i++) { System.out.print(array[i] + "、"); } System.out.println(); System.out.println("排序總次數:"+count); } }
一組不確定的陣列利用氣泡排序,在時間複雜度上的開銷有點過大,而對於有的陣列排序也並不需要達到最大的開銷就可以將排序做好,所以要做一定的優化,進行簡單的優化就是在迴圈過程中增加一個布林值變數作為監聽作用,在每一趟的每一輪排序結束都會進行有效判斷,如果當前排序已經達到排序要求,即可跳出排序工作,以節省時間。下面例項即簡單優化程式碼:
package com.bubblesort;
/**
* 氣泡排序(針對陣列)
*
* @author Lenovo
*
*/
public class Bubble {
public void bubblesort(int array[]) {
int count = 0; // 需要排序的次數
// 迴圈的次數為陣列長度減一,剩下的一個數不需要排序
for (int i = 0; i < array.length - 1; i++) {
boolean tate = true; // 利用布林變數設定,優化排序
// 迴圈次數為待排序數第一位數冒泡至最高位的比較次數
for (int j = 0; j < array.length - i - 1; j++) {
if (array[j] > array[j + 1]) {
int temp = array[j + 1];
array[j + 1] = array[j];
array[j] = temp;
tate = false;
}
count++;
}
if (tate) {
break;
}
}
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + "、");
}
System.out.println();
System.out.println("排序總次數:"+count);
}
}
簡單測試
package com.bubblesort;
public class Test {
public static void main(String[] args) {
Bubble bubble = new Bubble();
int array[] ={5,4,8,9,2,8,4,12,9,45,1,7};
int array1[]={1,5,3,4,2,9,8};
int array2[]={5,4,3,2,1};
bubble.bubblesort(array);
bubble.bubblesort(array1);
bubble.bubblesort(array2);
}
}
測試結果:
從測試可以看出,array2是完全倒序,這樣的排序用冒泡就必然會達到咋打複雜程度的時間;而陣列array1這樣的陣列就可以根據陣列的組合節約一定的排序時間。
那麼,除了以上的簡單優化,還有沒有更好的其他優化方法呢?
除此之外,在做的時候發現一個程式碼少,同樣可以達到以上氣泡排序優化後的效果,程式碼如下:
package com.bubblesort;
/**
* 氣泡排序(針對陣列)
*
* @author Lenovo
*
*/
public class Bubble {
public void bubblesort2(int array[]) {
int coun = 0; // 交換次數
int left = 0;
int right = array.length - 1;
long time = System.currentTimeMillis(); // 系統起始時間
for (int i = left; i < right; i++) {
if (array[i] > array[i + 1]) {
int temp = array[i];
array[i] = array[i + 1];
array[i + 1] = temp;
if (i != 0) {
i -= 2;
}
}
coun++;
}
time = System.currentTimeMillis() - time; // 排序完成後的所花時間
System.out.println("排序所花時間:" + time);
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + "、");
}
System.out.println();
System.out.println("自定義氣泡排序總次數:" + coun);
}
}
在學習的時候還發一種雙向定向排序法,又稱為“雞尾酒排序法”,在效率比氣泡排序更好。雞尾酒排序,也叫定向氣泡排序,是氣泡排序的一種改進。此演算法與氣泡排序的不同處在於從低到高然後從高到低,而氣泡排序則僅從低到高去比較序列裡的每個元素。他可以得到比氣泡排序稍微好一點的效能。
package com.bubblesort;
/**
* 氣泡排序(針對陣列)
*
* @author Lenovo
*
*/
public class Bubble {
/**
* 定向氣泡排序
*
* @param array
*/
public void bubblesort1(int array[]) {
int coun = 0;
int left = 0;
int right = array.length - 1;
boolean flag = false;
while (left < right / 2) { // 因為是雙向排序,所以比較次數是原陣列比較次數的1/2即可。
// 將數字大的全部丟到右邊
for (int i = left; i < right; i++) {
if (array[i] > array[i + 1]) {
int temp = array[i];
array[i] = array[i + 1];
array[i + 1] = temp;
flag = true;
}
// coun++;
}
right--; // 每當一個大的數字在右邊排好後,排序陣列長度自動減少一位,為下一次排序減少迴圈比較次數
// 正向升序排序完成後,立即反向降序排序,將小的數字依次丟到最左邊。
for (int i = right; i > left; i--) {
if (array[i - 1] > array[i]) {
int temp1 = array[i - 1];
array[i - 1] = array[i];
array[i] = temp1;
flag = true;
}
// coun++;
}
left++; // 每當一個小的數字在右邊排好後,排序陣列長度自動減少一位,為下一次排序減少迴圈比較次數
if (!flag) {
break;
}
coun++; // 資料交換次數
}
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + "、");
}
System.out.println();
System.out.println("定向氣泡排序總次數:" + coun);
}
}
最後做一個簡單的測試,結果如下:
總結:儘管氣泡排序是最容易瞭解和實現的排序演算法之一,但它對於少數元素之外的數列排序是很沒有效率的。在亂數序列的狀態下,雞尾酒排序與氣泡排序的效率都很差勁。