面試常備題---插入排序


      排序算法是最常見的筆試題目,幾乎所有的筆試和面試都會考到,因為它體現的就是程序員的算法基礎。可惜的是,作為一名菜鳥,而且還是即將面臨畢業的大三菜鳥,這方面的修養還真是不足,所以,在這里整理一下自己收集到的排序基礎知識,以備需要的時候可以查閱。

      先介紹插入排序。

1.直接插入排序

      直接插入排序(straight insertion sort)的原理是這樣的過程:

      每次從無序表中取出第一個元素,把它插入到有序表的合適位置,使有序表仍然有序。實際的過程像是這樣:

      對於序列:46 58 15 45

      第一次:[46] 58 15 45

      第二次:[46 58] 15 45

      第三次:[15 46 58] 45

      第四次:[15 45 46 58]

      正如上面數列的排序過程,我們知道,直接插入排序時間復雜度是O(n * n)(即平均時間復雜度),屬於穩定的排序(所謂穩定,就是原本序列中兩個相同的元素,在排序后仍然維持原本的排列)。空間復雜度為O(1)。

      考慮到要實現的排序算法很多,為了方便我的測試,我專門定義了一個Sort接口和一個測試類:

public interface Sort {
  void sort(int[] arr);
}

public class SortTest {
   public static void main(String[] args){
       int[] arr = {46, 58, 15, 45, 90, 18, 10, 62};
       execute(new DirectInsertSort(), arr);
       for(int element : arr){
           System.out.println(element);
       }
   }

    public static void execute(Sort sort, int[] arr){
       sort.sort(arr);
   }
}

        下面就是直接插入排序的java代碼:

public class DirectInsertSort implements Sort {

    @Override
    public void sort(int[] arr) {
        int len = arr.length;
        int temp = 0;
        int j = 0;
for(int i = 0; i < len; i++){ temp = arr[i]; for(j = i; j > 0 && temp < arr[j - 1]; j--){ arr[j] = arr[j - 1]; } arr[j] = temp; } } }

      直接插入排序是有兩層嵌套循環組成的,外層循環標識待比較的元素,內層循環為待比較元素確定其最終位置。注意一點,我們必須為待比較元素留一個存儲空間,因為我們在每次排序后都需要將待比較元素插入比它小的元素的后一位。

      直接插入排序的優點就是穩定和快,尤其是序列的有序程度越大,它就越快。但是缺點依然很明顯,就是序列如果是完全無序,並且數列非常大,那么比較的次數就會是一個可怕的數字,並且元素的移動非常頻繁。當然,解決這樣的問題可以使用鏈表,因為鏈表的優點就是方便元素的插入和移動,但是有一種排序就是為了解決這種問題。

2.希爾排序(Shell)

      希爾排序是對直接插入排序的改進,該方法又稱為縮小增量排序。它的基本思想扎根於直接插入排序的特點:每次插入一個元素,使有序序列只增加1個節點,並且對插入下一個元素沒有提供任何幫助。於是,就有一個叫希爾的人提出這樣的想法:將要排序的數列按照某個增量d分成若干組,每組中元素的下標相差d,接着對每組中全部元素進行排序,然后用一個較小的增量對它進行分組和排序。當增量減到1時,整個要排序的數列就被分成一組,排序也就完成。

      這種改進是為了消除直接插入排序中大量元素交換移動的問題。增量序列的選擇是個重要問題,它必須滿足下列條件:

(1)最后一個增量必須為1;

(2)應盡量避免序列中的元素,尤其是相鄰元素互為倍數的情況。

      一般情況下,每次增量的選擇都取序列的一半,直到增量為1。

      還是之前那個序列:46 58 15 45,用希爾排序進行排序:

      假設增量為2,第一次的分組結果為:[46, 15], [58, 45]

      然后我們在每組中進行直接插入排序:[15, 46], [45, 58]

      然后增量遞減為1,同樣適用直接插入排序,但是序列的有序程度已經大大增強,非常快就搞定了序列的排序。

      [15, 46, 45, 58] ——>[15, 45, 46, 58]

      上面的增量序列算是比較簡單的,但是它違背了我們上面的要求:增量序列中相鄰元素互為倍數的情況,我們再用一個大一點的數列來演示這個問題:

      序列為:46, 58, 15, 45, 90, 18, 10, 62

      假設增量序列為:4, 2, 1,排序的結果如:

      [46, 90], [58, 18], [15, 10], [45, 62]  ——>[46, 90], [18, 58], [10, 15], [45, 62]

      [46, 18, 10, 45], [90, 58, 15, 62] ——>[10, 18, 45, 46], [15, 58, 62, 90]

      [10, 18, 45, 46, 15, 58, 62, 90] ——>[10, 15, 18, 45, 46, 58, 62, 90]

      假設增量序列為:3, 1,排序的結果如:

      [46, 45, 10], [58, 90, 62], [15, 18] ——>[10, 45, 46], [58, 62, 90], [15, 18]

      [10, 45, 46, 58, 62, 90, 15, 18] ——>[10, 15, 18, 45, 46, 58, 62, 90]

      可見,選擇第二種增量序列,最后增量為1時,序列的有序程度更高,所以效率更快。

      如何選擇增量序列才能讓排序更快,這個問題至今沒有統一的答案,我自己收集到的資料就介紹了這樣的情況:增量序列h = n / 3 + 1, n / 9 + 1, n / 27 + 1,..., 1。

      如果序列比較小,討論這個問題其實很多余,因為它們運行起來的速度幾乎是沒有什么差異。所以,一般情況下,選擇折半的增量序列就已經滿足要求了,而且在編程上更加方便。

      希爾排序的時間復雜度最好情況下為O(n * logn),但它並不是一個穩定的排序,因為增量序列的選擇對它的影響非常大。空間復雜度為O(1)。

      希爾排序的java代碼如下:

public class ShellSort implements Sort {

    @Override
    public void sort(int[] arr) {
        int step = arr.length / 2;
        int tmp = 0;
        while (step >= 1) {
            for (int i = 0, len = arr.length; i < len; i++) {
                for (int j = i; j < len; j += step) {
                    if (arr[i] > arr[j]) {
                        tmp = arr[i];
                        arr[i] = arr[j];
                        arr[j] = tmp;
                    }
                }
            }
            step = step / 2;
        }
    }
}

        

     

      

     

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM