1. 程式人生 > >資料結構演算法-插入排序

資料結構演算法-插入排序

插入排序演算法有兩種,一種是直接插入排序,一種是折半插入排序


直接插入排序(straight insertion sort)每次從無序表中取出第一個元素,把它插入到有序表的合適位置,使有序表仍然有序。第一趟比較前兩個數,然後把第二個數按大小插入到有序表中; 第二趟把第三個資料與前兩個數從後向前掃描,把第三個數按大小插入到有序表中;依次進行下去,進行了(n-1)趟掃描以後就完成了整個排序過程。直接插入排序是由兩層巢狀迴圈組成的。外層迴圈標識並決定待比較的數值。內層迴圈為待比較數值確定其最終位置。直接插入排序是將待比較的數值與它的前一個數值進行比較,所以外層迴圈是從第二個數值開始的。當前一數值比待比較數值大的情況下繼續迴圈比較,直到找到比待比較數值小的並將待比較數值置入其後一位置,結束該次迴圈。
插入排序的基本方法是:每步將一個待排序的記錄按其關鍵字的大小插到前面已經排序的序列中的適當位置,直到全部記錄插入完畢為止。    直接插入排序是一種穩定排序演算法!

演算法分析:1.當元素的初始序列為正序時,僅外迴圈要進行n-1趟排序且每一趟只進行一次比較,沒有進入if語句不存在元素之間的交換(移動)。此時比較次數(Cmin)和移動次數(Mmin)達到最小值。

                 Cmin = n-1Mmin = 0;

                此時時間複雜度為O(n)。

              2.當元素的初始序列為反序時,每趟排序中待插入的元素都要和[0,i-1]中的i個元素進行比較且要將這i個元素後移(arr[j+1] = arr[j]),i個元素後移移動次數當然也就為i了,再加上temp = arr[i]與arr[j+1] = temp的兩次移動,每趟移動的次數為i+2,此時比較次數(Cmin

)和移動次數(Mmin)達到最小值。

                 Cmax = 1+2+...+(n-1) = n*(n-1)/2 = O(n2)

                 Mmax = (1+2)+(2+2)+...+(n-1+2) = (n-1)*(n+4)/2 = O(n2)  (i取值範圍1~n-1)

                 此時時間複雜度為O(n2)。

              3.在直接插入排序中只使用了i,j,temp這3個輔助元素,與問題規模無關,所以空間複雜度為O(1).

              4.在整個排序結束後,即使有相同元素它們的相對位置也沒有發生變化,

                  如:5,3,2,3排序過程如下

                     A--3,5,2,3

                     B--2,3,5,3

                     C--2,3,3,5

void InsertSort(int a[], int n)
{
    for(int i= 1; i<n; i++)
    {
        if(a[i] < a[i-1])
        {
            //若第i個元素大於i-1元素,直接插入。小於的話,移動有序表後插入
            int j= i-1;
            int x = a[i];        //複製為哨兵,即儲存待排序元素
            a[i] = a[i-1];           //先後移一個元素
            while(x < a[j])   //查詢在有序表的插入位置
            {
                a[j+1] = a[j];
                j--;         //元素後移
            }
            a[j+1] = x;      //插入到正確位置
        }

    }
    for(int i=0; i<n; i++)
        printf("%d%c",a[i],i==n-1?'\n':' ');
}


折半插入排序:

演算法的基本過程

   1計算 0 ~ i-1 的中間點,用 索引處的元素與中間值進行比較,如果 索引處的元素大,說明要插入的這個元素應該在中間值和剛加入i索引之間,反之,就是在剛開始的位置 到中間值的位置,這樣很簡單的完成了折半;

   2)在相應的半個範圍裡面找插入的位置時,不斷的用(1)步驟縮小範圍,不停的折半,範圍依次縮小為 1/2  1/4  1/8 .......快速的確定出第 i  個元素要插在什麼地方;

   3)確定位置之後,將整個序列後移,並將元素插入到相應位置。


折半插入排序過程:

       第一趟:按上述程式碼的流程分析,從A[2]開始計算,{11}是一個已排序子表,按關鍵字13進行折半查詢它的位置,程式碼的上半部分查詢該元素元素應該插入的位置為A[2],所以下半部分並不需要移動元素,已排序子表為{11,13} 
  第二趟:從A[3]開始計算,low=1,high=2,mid=1,因為7<11,所以high=2-1=1;第二次迴圈mid=1,7<11,high=0,迴圈不滿足條件,此時開始移動元素;要移動的元素範圍為A[1]到A[2],A[1]=7。 
  第三趟第四趟依此類推…..(只要記住一點,先折半查詢元素的應該插入的位置,然後統一移動應該移動的元素,再將這個元素插入到正確的位置) 

void BinaryInsertSort(int array[],int n)//傳遞陣列和陣列元素個數
{
    int i,j,mid,low,high,temp;
    for(i = 1; i < n; ++i) //我看了一些其他人寫得折半插入演算法是從下標1開始的,i = 2;i <=n,並將array[i]儲存到array[0]
    {
        temp = array[i];//把第i+1個元素賦值給temp(陣列從下標0開始)
        low = 0;//初始化low,array[low]代表陣列中第1個元素
        high = i;//初始化high,array[high]代表已插入的最後一個元素
        while(low <= high) //不斷的折半1/2 1/4 ....
        {
            mid = (low + high) / 2;//計算中間位置
            if (temp > array[mid])
            {
                //插入值大於中間值
                low = mid + 1;
            }
            else
            {
                //插入值小於中間值
                high = mid - 1;
            }
        }
        for(j=i-1; j >= low; --j)
        {
            //將需要移動的陣列向後移
            array[j+1] = array[j];
        }
        //將值插入到指定位置
        array[low] = temp;
    }
}
兩個者的區別是:
折半插入排序基本思想和直接插入排序一樣,區別在於尋找插入位置的方法不同,折半插入排序採用折半查詢法來尋找插入位置。折半查詢法只能對有序的序列使用。基本思想就是查詢插入位置的時候,把序列分成兩半(選擇一箇中間數mid),如果帶插入資料大於mid則到右半部分序列去在進行折半查詢;反之,則到左半部分序列去折半查詢。
折半插入排序在記錄移動次數上和直接插入排序是一樣,所以演算法時間複雜度也是一樣,只是在尋找插入位置的時候可能會節約相當多的時間。