一、冒泡排序基礎
冒泡排序是比較基礎的排序算法之一,其思想是相鄰的元素兩兩比較,較大的數下沉,較小的數冒起來,這樣一趟比較下來,最大(小)值就會排列在一段。整個過程如同氣泡冒起,因此被稱作冒泡排序。
冒泡排序的步驟是比較固定的:
1>比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。
2>每趟從第一對相鄰元素開始,對每一對相鄰元素作同樣的工作,直到最後一對。
3>針對所有的元素重複以上的步驟,除了已排序過的元素(每趟排序後的最後一個元素),直到沒有任何一對數字需要比較。
此處引用網上一張比較經典的gif來展示冒泡排序的整個過程:
冒泡排序的常規實現代碼如下:
int[ ] array=new int[ ]{5,2,3,9,4};
/*外循環爲排序趟數,array.length個數進行array.length-1趟 */
for(int i=0;i<array.length-1;i++){
/*內循環爲每趟比較的次數,第i趟比較array.length-i次 */
for(int j=0;j<array.length-1-i;j++){
/*相鄰元素比較,若滿足條件則交換(升序爲左大於右,降序反之) */
if(array[j]>array[j+1]){
int temp=array[j];
array[j]=array[j+1];
array[j+1]=temp;
}
}
}
二、冒泡排序優化
上個章節介紹了冒泡排序的常規實現,但是這種最簡單的排序是存在着不必要的比較動作的。稍微修改上述代碼,就可以查看每次比較後的結果:
int[ ] array=new int[ ]{5,2,3,9,4};
/*外循環爲排序趟數,array.length個數進行array.length-1趟 */
for(int i=0;i<array.length-1;i++){
/*內循環爲每趟比較的次數,第i趟比較array.length-i次 */
for(int j=0;j<array.length-1-i;j++){
/*相鄰元素比較,若滿足條件則交換(升序爲左大於右,降序反之) */
if(array[j]>array[j+1]){
int temp=array[j];
array[j]=array[j+1];
array[j+1]=temp;
}
/*查看每趟比較後的數組元素*/
System.out.println("第 "+(i+1)+" 趟,第 "+(j+1)+" 次比較後的結果:");
for(int k=0;k<array.length;k++){
System.out.print(array[k]+" ");
}
System.out.println();
}
}
該代碼的輸出結果爲:
第 1 趟,第 1 次比較後的結果:
2 5 3 9 4
第 1 趟,第 2 次比較後的結果:
2 3 5 9 4
第 1 趟,第 3 次比較後的結果:
2 3 5 9 4
第 1 趟,第 4 次比較後的結果:
2 3 5 4 9
第 2 趟,第 1 次比較後的結果:
2 3 5 4 9
第 2 趟,第 2 次比較後的結果:
2 3 5 4 9
第 2 趟,第 3 次比較後的結果:
2 3 4 5 9
第 3 趟,第 1 次比較後的結果:
2 3 4 5 9
第 3 趟,第 2 次比較後的結果:
2 3 4 5 9
第 4 趟,第 1 次比較後的結果:
2 3 4 5 9
從上面的測試結果可以看出,從第2趟第3次比較後,數組元素已經處於有序狀態,此後所有的比較都不必進行。
2.1 第一種優化
第一種優化就基於上面的測試結果來進行,如果某次比較過程中,發現沒有任何元素移動,則不再進行接下來的比較。具體的做法是在每趟比較時,引入一個boolean型變量isSwap,來判斷下次比較還有沒有必要進行。示例代碼如下:
int[ ] array=new int[ ]{5,2,3,9,4};
/*外循環爲排序趟數,array.length個數進行array.length-1趟 */
for(int i=0;i<array.length-1;i++){
boolean isSwap=false;
/*內循環爲每趟比較的次數,第i趟比較array.length-i次 */
for(int j=0;j<array.length-1-i;j++){
/*相鄰元素比較,若滿足條件則交換(升序爲左大於右,降序反之) */
if(array[j]>array[j+1]){
int temp=array[j];
array[j]=array[j+1];
array[j+1]=temp;
isSwap=true;
}
/*查看每趟比較後的數組元素*/
System.out.println("第 "+(i+1)+" 趟,第 "+(j+1)+" 次比較後的結果:");
for(int k=0;k<array.length;k++){
System.out.print(array[k]+" ");
}
System.out.println();
}
/*如果沒有交換過元素,則已經有序,不再進行接下來的比較*/
if(!isSwap){
break;
}
}
測試結果如下:
第 1 趟,第 1 次比較後的結果:
2 5 3 9 4
第 1 趟,第 2 次比較後的結果:
2 3 5 9 4
第 1 趟,第 3 次比較後的結果:
2 3 5 9 4
第 1 趟,第 4 次比較後的結果:
2 3 5 4 9
第 2 趟,第 1 次比較後的結果:
2 3 5 4 9
第 2 趟,第 2 次比較後的結果:
2 3 5 4 9
第 2 趟,第 3 次比較後的結果:
2 3 4 5 9
第 3 趟,第 1 次比較後的結果:
2 3 4 5 9
第 3 趟,第 2 次比較後的結果:
2 3 4 5 9
從上面的測試結果可以看出,已經對排序過程進行了優化,因爲沒有進行第4趟比較。也許有人會有疑問“第2趟比較結束後,數組已經有序了,爲什麼還要進行第3次比較”?之所以對此有疑問,可能是沒太清楚isSwap變量的作用,該變量的作用是“假如本趟比較過程中沒有交換髮生,則不必進行下一趟比較”,在第2趟比較過程中,發生了交換動作,所以第3趟比較仍會進行。
2.2 第二種優化
第一種優化方式比較容易理解,但同時也存在着缺點:某趟比較只要開始,哪怕數組元素已經有序,也要把該趟的所有次比較完。也就是說,第一種優化方式,只能在“趟”的級別上優化。
第二種優化方式,也就是要實現在“次”的級別進行優化,其思路是“記下最後一次交換的位置,後邊沒有交換,必然是有序的,然後下一次排序從第一個比較到上次記錄的位置結束即可”。示例代碼爲:
int[ ] array = new int[ ]{5,2,3,7,9};
int position = array.length - 1;
/*外循環爲排序趟數,array.length個數進行array.length-1趟 */
for(int i = 0;i<array.length-1;i++){
boolean isSwap = false;
/*用來記錄最後一次交換的位置*/
int newPosition = 0;
/*內循環爲每趟比較的次數,第i趟比較array.length-i次 */
for(int j = 0;j<position;j++){
/*相鄰元素比較,若滿足條件則交換(升序爲左大於右,降序反之) */
if(array[j]>array[j+1]){
int temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
isSwap = true;
/*記錄最後一次交換的位置*/
newPosition = j;
}
/*查看每趟比較後的數組元素*/
System.out.println("第 "+(i+1)+" 趟,第 "+(j+1)+" 次比較後的結果:");
for(int k = 0;k<array.length;k++){
System.out.print(array[k]+" ");
}
System.out.println();
}
/*如果沒有交換過元素,則已經有序,不再進行接下來的比較*/
if(!isSwap){
break;
}
/*下趟比較所需要比較到的位置*/
position = newPosition;
}
測試結果如下:
第 1 趟,第 1 次比較後的結果:
2 5 3 7 9
第 1 趟,第 2 次比較後的結果:
2 3 5 7 9
第 1 趟,第 3 次比較後的結果:
2 3 5 7 9
第 1 趟,第 4 次比較後的結果:
2 3 5 7 9
第 2 趟,第 1 次比較後的結果:
2 3 5 7 9
從上面的測試結果可以看出,在第2趟進行了1次比較後,數組元素已經處於有序狀態,此時便不再進行接下來的比較。
2.3 第三種優化
上面的兩種優化都是單向遍歷比較的,然而在很多時候,遍歷的過程可以從兩端進行,從而提升效率。因此在冒泡排序中,其實也可以進行雙向循環,正向循環把最大元素移動到數組末尾,逆向循環把最小元素移動到數組首部。該種排序方式也叫雙向冒泡排序,也叫雞尾酒排序。
關於此種排序優化,先來個簡單版的:
while(left < right) {
/*找到當前數組元素裏最大的那個,放在右側*/
for(int i = left; i<right; i++) {
if(array[i] > array[i+1]) {
temp = array[i];
array[i] = array[i+1];
array[i+1] = temp;
}
}
right--;
/*找到當前數組元素裏最小的那個,放在左側*/
for(int j = right; j>left; j--) {
if(array[j]<array[j-1]) {
temp = array[j];
array[j] = array[j-1];
array[j-1] = temp;
}
}
left++;
}
當然,這第三種優化方式可以和之前的優化方式結合起來,示例代碼爲:
int[ ] array = new int[ ]{5,2,3,7,9};
int left = 0;
int right = array.length - 1;
/*最後一次交換的位置*/
int lastPosition = 0;
/*是否應該結束遍歷比較的標誌*/
boolean isSwap = false;
while (left < right) {
/*將最大的元素放在數組尾部*/
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;
isSwap = true;
lastPosition = i;
}
}
/*將最後一次交換的位置作爲右邊界*/
right = lastPosition;
if (!isSwap) {
break;
}
isSwap = false;
/*將最小的元素放在數組首部*/
for (int i = right; i > left; i--) {
if (array[i] < array[i-1]) {
int temp = array[i];
array[i] = array[i+1];
array[i+1] = temp;
isSwap = true;
lastPosition = i;
}
}
/*將最後一次交換的位置作爲左邊界*/
left = lastPosition;
if (!isSwap) {
break;
}
isSwap = false;
}