插入排序

中心思想:考慮你正在鬥地主,對方發給你一堆牌,你右手摸牌,左手拿牌,第一次摸到10放到左手,第二次摸到3就放到10左邊,第三次摸到K放10右邊,左手上的牌一直保持有序,直到左手拿着所有的牌爲止。這就是插入排序。
插入排序從第二個元素開始,在前面已排好序的序列中尋找合適的位置並插入,使之仍然有序。
插入排序最多的步驟在於移位操作,每個元素插入之前,數組的大於待插入值的元素必須事先挪位,故插入排序在最壞情況下是o(n*n)的複雜度。

直接插入:

//插入排序,不支持參數檢查
void insert_sort(int * a , int n)
{
    int i,j,key;
    for(i=1;i<n;++i)
    {
        key = a[i];//key 爲待插入的元素
        //挪位
        for( j = i-1;j>=0 && a[j] >key;--j)
        {
            a[j+1] = a[j];//兩重循環,故O(n*n)
        }
        //在合適的位置插入
        a[j+1] = key;
    }
}

在一萬個數據的運行結果:
數據隨機生成,循環20次得到
插入排序有個挪位操作,每次循環必須判斷移動的元素是否大於待插入的元素,爲了省去不必要的判斷,我們可以先用二分法尋找合適的插入位置。

折半插入:

void insert_sort(int * a , int n)
{
    int i,j,key,low,high,mid;
    for(i=1;i<n;++i)
    {
        key = a[i];
        //二分查找運算
        low = 0,high = i-1;
        while(low <= high)
        {
            mid = (low + high)/2;
            if(key > a[mid])
                low = mid + 1;
            else// if(key <= a[mid])
                high = mid - 1;

        }
        //挪位
        for( j = i-1;j >= low;--j)
        {
            a[j+1] = a[j];
        }
        a[j+1] = key;
    }
}

在一萬個數據的運行結果:
可以看出快了大概十多毫秒
結果快了大概十多毫秒。。。我想應該是移位操作步數並沒有因此減少,只是對比較操作優化了一下而已。所以效果並不明顯。

二路插入:

那麼有沒有辦法減少移位的步數呢?仔細一想,有一種情況是不需要移位的,那就是待插入的元素剛好插入到有序序列的最後。我們想一下,如果有序序列前面還有位置放會怎樣?本來需要把所有有序序列移位的現在也變成不需要移位了。那麼如何實現呢?我們可以利用循環數組的想法來做。用first表示有序序列的開始,last表示有序序列的結尾,如果待插入元素小於first值或者大於last值,就放到外面去;如果待插入元素剛好處於first值和last值之間,那麼就插入到有序序列中間,這和直接插入一樣。
舉例:如果有序序列是 1 2 3 5,待插入的是0,那麼我就把它放到數組的最後,讓first等於最後一個(first = n -1),讓last = 2。
二路插入的思想是以空間換取時間的想法。


void insert_sort(int * a , int n)
{
    int *t;
    t = new int [n];
    int i,j,key,first=0,last=0;
    t[0] = a[0];

    for(i=1;i<n;++i)
    {
        key = a[i];//key 爲待插入的元素
        //如果待插入元素比first值還小
        if(key <= t[first])
        {
            first = (first-1+n)%n;
            t[first] = key;

        }
        //如果待插入元素比last值還大
        else if(key >= t[last])
        {
            last = last + 1;
            t[last] = key;

        }
        //如果待插入元素處於first和last之間
        else //if(key > t[first] && key < t[last])
        {
            //挪位
            for( j = last;j != first && t[j] > key; j= (j-1+n)%n)
            {
                t[(j+1)%n] = t[j];//兩重循環,故O(n*n)
            }
            //在合適的位置插入
            t[(j+1)%n] = key;
            last = (last + 1)%n;
        }


    }
    //賦值給a數組
    j=0;
    i =first;
    do{
         a[j++] = t[i];
         i = (i+1)%n;
    }while(i != (last + 1)%n);
}

但是運行結果卻出乎意料:
這裏寫圖片描述
但是對於完全逆序的數據卻出乎意料的快:
這裏寫圖片描述
經過仔細分析,原來是取餘(循環鏈表需要用到模運算)造成的影響,於是修改一下:

void insert_sort(int * a , int n)
{
    int *t;
    t = new int [n];
    int i,j,key,first=0,last=0;
    t[0] = a[0];

    for(i=1;i<n;++i)
    {
        key = a[i];//key 爲待插入的元素
        //如果待插入元素比first值還小
        if(key <= t[first])
        {
            first = (first-1+n)%n;
            t[first] = key;

        }
        //如果待插入元素比last值還大
        else if(key >= t[last])
        {

            t[++last] = key;

        }
        //如果待插入元素處於first和last之間
        else //if(key > t[first] && key < t[last])
        {
            //挪位
            int k1,k2;
            for( j = last;j != first && t[j] > key; j= (!j)?(j-1+n):j-1)//避免取餘帶來的性能損耗
            {
                k1= (!(j-n+1))?j+1-n:j+1;
                t[k1] = t[j];//兩重循環,故O(n*n)
            }
            //在合適的位置插入
            t[(!(j-n+1))?j+1-n:j+1] = key;
            ++last;
        }


    }
    //賦值給a數組
    j=0;
    i =first;
    do{
         a[j++] = t[i];
         i = (!(i-n+1))?i+1-n:i+1;
    }while(i != last + 1);
}

運行結果:
這裏寫圖片描述
我只能說我已經盡力了,對於均勻隨機產生的數據,二路插入只有70多毫秒,比直接插入還要慢一點,但是對於極端情況即完全逆序或者完全升序的情況來說是很快的,權衡一下應該選擇二路插入好一點。

希爾排序:

爲了省略移位帶來的影響,希爾提出了這樣一種思路:如果待排序的數組元素相對有序,那麼是否會降低移位操作的頻率?
如果在插入元素之前,數組都相對有序,大的元素都在後頭,小的元素都在前面,這樣只需移動少許的元素就可以使數組升序。
上代碼:

void insert_sort(int * a , int n)
{
    int i,j,key,step;//step 爲步長
    step = n >>1;
    while(step!=0)
    {
        //cout<<step<<endl;
        for(i=0;i<n;i += step)
        {
        key = a[i];//key 爲待插入的元素
        //挪位
        for( j = i-step;j>=0 && a[j] >key;j -= step)
        {
            a[j+step] = a[j];//兩重循環,故O(n*n)
        }
        //在合適的位置插入
        a[j+step] = key;
        }
        step = step >>1;
    }
}

我感覺希爾排序跟直接插入排序的代碼差不多,不同的是下標值的變換,希爾排序會依次縮小步長,增大待排序的元素,直到步長爲1,即變成了直接插入排序,但由於之前的排序是數組相對有序(大小錯落有致),所以移位運算大大減少,因此性能得到提高:
這裏寫圖片描述
以上,便是插入排序的大致內容,這是我的第一篇博客,謝謝觀看。

發佈了30 篇原創文章 · 獲贊 186 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章