1. 程式人生 > >漫畫:什麼是雞尾酒排序

漫畫:什麼是雞尾酒排序

那麼,雞尾酒排序又是何方神聖呢?我們這一期將會詳細講述。

讓我們首先來回顧一下氣泡排序的思想:

氣泡排序的每一個元素都可以像小氣泡一樣,根據自身大小,一點一點向著陣列的一側移動。演算法的每一輪從都是從左到右比較元素,進行單向的位置交換

那麼雞尾酒排序做了怎樣的優化呢?

雞尾酒排序的元素比較和交換過程是雙向的。

讓我們來舉一個栗子:

有8個數組成一個無序數列:2,3,4,5,6,7,8,1,希望從小到大排序。

如果按照氣泡排序的思想,排序的過程是什麼樣呢?

第一輪結果(8和1交換)

第二輪結果(7和1交換)

第三輪結果(6和1交換)

第四輪結果(5和1交換)

第五輪結果(4和1交換)

第六輪結果(3和1交換)

第七輪結果(2和1交換)

雞尾酒排序是什麼樣子呢?讓我們來看一看詳細過程:

第一輪(和氣泡排序一樣,8和1交換)

第二輪

此時開始不一樣了,我們反過來從右往左比較和交換:

8已經處於有序區,我們忽略掉8,讓1和7比較。元素1小於7,所以1和7交換位置:

接下來1和6比較,元素1小於6,所以1和6交換位置:

接下來1和5比較,元素1小於5,所以1和5交換位置:

接下來1和4交換,1和3交換,1和2交換,最終成為了下面的結果:

第三輪(雖然已經有序,但是流程並沒有結束)

雞尾酒排序的第三輪,需要重新從左向右比較和交換:

1和2比較,位置不變;2和3比較,位置不變;3和4比較,位置不變......6和7比較,位置不變。

沒有元素位置交換,證明已經有序,排序結束。

這就是雞尾酒排序的思路。排序過程就像鐘擺一樣,第一輪從左到右,第二輪從右到左,第三輪再從左到右......

public class CockTailSort {

private static void sort(int array[])

{

    int tmp  = 0;

   for(int i=0; i<array.length/2; i++)

   {

       //有序標記,每一輪的初始是true

       boolean isSorted = true;

       //奇數輪,從左向右比較和交換

       for(int j=i; j<array.length-i-1; j++)

       {

           if(array[j] > array[j+1])

           {

               tmp = array[j];

               array[j] = array[j+1];

               array[j+1] = tmp;

               //有元素交換,所以不是有序,標記變為false

               isSorted = false;

           }

       }

       if(isSorted){

           break;

       }

    //偶數輪之前,重新標記為true

       isSorted = true;

       //偶數輪,從右向左比較和交換

       for(int j=array.length-i-1; j>i; j--)

       {

           if(array[j] < array[j-1])

           {

               tmp = array[j];

               array[j] = array[j-1];

               array[j-1] = tmp;

               //有元素交換,所以不是有序,標記變為false

               isSorted = false;

           }

       }

       if(isSorted){

           break;

       }

   }

}

public static void main(String[] args){

   int[] array = new int[]{2,3,4,5,6,7,8,1};

   sort(array);

   System.out.println(Arrays.toString(array));

}

}

這段程式碼是雞尾酒排序的原始實現。程式碼外層的大迴圈控制著所有排序回合,大迴圈內包含兩個小迴圈,第一個迴圈從左向右比較並交換元素,第二個迴圈從右向左比較並交換元素。

讓我們來回顧一下冒牌排序針對有序區的優化思路:

原始的氣泡排序,有序區的長度和排序的輪數是相等的。比如第一輪排序過後的有序區長度是1,第二輪排序過後的有序區長度是2 ......

要想優化,我們可以在每一輪排序的最後,記錄下最後一次元素交換的位置,那個位置也就是無序數列的邊界,再往後就是有序區了。

對於單向的氣泡排序,我們需要設定一個邊界值,對於雙向的雞尾酒排序,我們需要設定兩個邊界值。請看程式碼:

public class CockTailSort {

private static void sort(int array[])

{

   int tmp  = 0;

   //記錄右側最後一次交換的位置

   int lastRightExchangeIndex = 0;

   //記錄左側最後一次交換的位置

   int lastLeftExchangeIndex = 0;

   //無序數列的右邊界,每次比較只需要比到這裡為止

   int rightSortBorder = array.length - 1;

   //無序數列的左邊界,每次比較只需要比到這裡為止

   int leftSortBorder = 0;

   for(int i=0; i<array.length/2; i++)

   {

       //有序標記,每一輪的初始是true

       boolean isSorted = true;

       //奇數輪,從左向右比較和交換

       for(int j=leftSortBorder; j<rightSortBorder; j++)

       {

           if(array[j] > array[j+1])

           {

               tmp = array[j];

               array[j] = array[j+1];

               array[j+1] = tmp;

               //有元素交換,所以不是有序,標記變為false

               isSorted = false;

               lastRightExchangeIndex = j;

           }

       }

       rightSortBorder = lastRightExchangeIndex;

       if(isSorted){

           break;

       }

    //偶數輪之前,重新標記為true

       isSorted = true;

       //偶數輪,從右向左比較和交換

       for(int j=rightSortBorder; j>leftSortBorder; j--)

       {

           if(array[j] < array[j-1])

           {

               tmp = array[j];

               array[j] = array[j-1];

               array[j-1] = tmp;

               //有元素交換,所以不是有序,標記變為false

               isSorted = false;

               lastLeftExchangeIndex = j;

           }

       }

       leftSortBorder = lastLeftExchangeIndex;

       if(isSorted){

           break;

       }

   }

}


public static void main(String[] args){

   int[] array = new int[]{2,3,4,5,6,7,8,1};

   sort(array);

   System.out.println(Arrays.toString(array));

}

}

程式碼中使用了左右兩個邊界值,rightSortBorder 代表右邊界,leftSortBorder代表左邊界。 

在比較和交換元素時,奇數輪從 leftSortBorder 遍歷到 rightSortBorder 位置,偶數輪從 rightSortBorder 遍歷到 leftSortBorder 位置。