算法入門(一)冒泡排序及優化詳解

一、冒泡排序基礎

  冒泡排序是比較基礎的排序算法之一,其思想是相鄰的元素兩兩比較,較大的數下沉,較小的數冒起來,這樣一趟比較下來,最大(小)值就會排列在一段。整個過程如同氣泡冒起,因此被稱作冒泡排序。
  冒泡排序的步驟是比較固定的:
   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;
        }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章