1. 程式人生 > >排序算法學習整理三(插入)

排序算法學習整理三(插入)

排序類 基本 得出 for循環 b- lose ron 核心 穩定

三、插入排序:

  插入排序是一種非常簡單的排序,它的實現難度低於冒泡和選擇。(我第一個for循環寫出的排序就是插入排序)插入排序類似整理撲克牌,將每一張牌插到其他已經有序的牌中適當的位置。

  基本思想:

    插入排序由N-1趟排序組成,對於P=1到N-1趟,插入排序保證從位置0到位置P上的元素為已排序狀態。

    簡單的說,就是插入排序總共需要排序N-1趟,從Index為1開始,講該位置上的元素與之前的元素比較,放入合適的位置,這樣循環下來之後,即為有序數組。

  代碼實現 

技術分享圖片
 1 void insertionSort(int *array, int len)
 2 {
 3     for
(int i = 1; i < len; i++) 4 { //即使i從開始0開始,也不會進入第二個循環。 5 for (int j = 0; j < i; j++) 6 { /*從0~i都是已經排好序的,將array[i]與array[0]~array[i-1]一一進行比較, 7 找到插入點*/ 8 if (array[i] < array[j]) 9 { //找到大於array[i]的元素就立即交換(這裏存在優化的可能) 10 array[i] = array[i] ^ array[j];
11 array[j] = array[i] ^ array[j]; 12 array[i] = array[i] ^ array[j]; //交換 13 } 14 } 15 } 16 }
insertionSort

  代碼的問題很明顯,就像選擇排序為什麽會比冒泡快一樣,我們可以減少交換次數來優化代碼。

  但是不交換怎麽排序呢?用賦值語句進行覆蓋實現,

    其核心的代碼如下:    

    

    for(j = i; j > 0 && temp < array[j-1]; j--)
    { 
       array[j] = array[j-1];
    }
    array[j] = temp;

    打個比方:

       原數組元素為 : 7 1 2 3

      第一輪:

        1 < 7;執行array[j] = array[j-1]

          1 1 2 3;

        再執行array[j] = temp;

          1 7 2 3;

      第二輪:

        2 < 7;執行array[j] = array[j-1]

          1 2 2 3

        再執行array[j] = temp;

          1 2 7 3

        2 !< 1;退出循環

      第三輪:

        3 < 7;執行array[j] = array[j-1]

          1 2 3 3

        再執行array[j] = temp;

          1 2 3 7        

        3 !< 2;退出循環;

    代碼實現:

技術分享圖片
 1 void insertionSort(int *array, int len)
 2 {   
 3     int i, j;
 4     int temp = 0;    
 5     for (i = 1; i < len; i++)
 6     {    
 7         temp = array[i];     //記錄需要插入的元素   
 8         for(j = i; j > 0 && temp < array[j-1]; j--)
 9         {   //從array[i]開始和自己的前一個相比較(存在優化的可能)
10         //如果array[i] < array[j-1],就代表array[i]插入的位置不對,
11         //如果temp < array[j-1],不成立,就代表到了array[i]該插入的位置了
12             array[j] = array[j-1];
13         }
14         array[j] = temp;    //找到正確位置後立即插入
15     }
16 }
insertionSor

  現在讓我來想一想一個問題,一個數組其元素為 5 1 2 3 4 6 7 8,

     根據插入排序的代碼可得到相應的過程

  1. 第一輪:1 5 2 3 4 6 7 8     一次比較,兩次賦值
  2. 第二輪:1 2 5 3 4 6 7 8 兩次比較,三次賦值
  3. 第三輪:1 2 3 5 4 6 7 8 三次比較,四次賦值
  4. 第四輪:1 2 3 4 5 6 7 8 四次比較,五次賦值

  這個數組的元素除了5原本都是有序的,但是,為了找到5正確的插入位置,總共進行了10次比較,14次賦值。

明眼人都看得出來5的正確位置應該是整個數組的最中間的位置,可是計算機偏偏就是個“瞎子”,那我們該怎麽樣讓這個“瞎子”,知道這個元素是在數組的最中間呢?

這就涉及到到上篇拓展的內容——運用二分查找來這個元素的正確位置。

  這裏先貼上二分的核心代碼(建議先看懂二分查找再來看二分排序)

        while (left <= right)      //如果走到頭都沒有找到就退出
        {
            mid = (left+right) / 2;  //記錄中間數
            if (arr[mid] > temp)    //和中間數相比較
            {
                right = mid - 1;    //比中間數小就向前走
            }
            else
            {
                left = mid + 1;    //比中間數大就向後走
            }
        }

  這裏是用循環來實現二分查找,當然,我們也可以用遞歸來實現。這裏為了節省時間,我就不再多做解釋。

從所貼的代碼可看出通過二分查找我們確定元素5的正確位置只需要1次比較和5次賦值,大大減少了比較次數和賦值次數。

當然對於二分排序來說采用折半查而減少,為O(nlogn),但是元素交換的次數仍為O(n2),二分排序算法是穩定的。

下面我們來看一下完整的代碼:

技術分享圖片
 1 void insertsort(int *arr, int n)
 2 {
 3     int i, j, temp, left, right, mid;    
 4 
 5     for (i = 1; i < n; i++)
 6     {
 7         left = 0;
 8         temp = arr[i];
 9         right = i-1;
10 
11         while (left <= right)
12         {
13 
14             mid = (left+right) / 2;
15             if (arr[mid] > temp)
16             {
17                 right = mid - 1;
18             }
19             else
20             {
21                 left = mid + 1;
22             }
23         }
24 
25         for (j = i-1; j > right; j--)
26         {
27             arr[j+1] = arr[j];
28         }
29         arr[right+1] = temp;
30     }
31 }
insertsort

  大家可以自行嘗試寫一下遞歸版的二分排序,我在這裏就貼代碼了。

排序算法學習整理三(插入)