歸併排序——codevs1076

紀念下AC的歸併排序類型題……大佬勿噴,歡迎指出錯誤。

歸併排序是最近學會的一類排序方法(感謝“沒有夢想__何必遠方“),O(nlog2n)的複雜度,雖然和algorithm中的sort複雜度一樣,但在做(裝)題(逼)中也是經常用到的,比如我將會在本週內發佈的另一題解——codevs1688求逆序對中就會有很大用處。

這道題目雖然是一道水題,但用來練習歸併排序也是一道非常好的題目,因此,下面的講解並非最短題解,而是對歸併排序的講解,請注意!~~~

首先,歸併排序的主要思想是——

將兩個或兩個以上的有序表組合成一個新有序表。

這也就決定了,我們將使用遞歸的方法來完成這一排序。

那麼,既然已經知道了大體思路,就應該講講怎麼實現。
(先盜用一張圖)

這裏寫圖片描述

大概從圖片中可以看出,歸併排序就是一次次的二分,然後再找到葉子節點後就開始兩兩比較,將小的存入臨時數組,當遍歷(O(n))完成後,就將臨時數組複製到原數組中,這樣,原數組就可以完成第一次的歸併,然後在回溯中(O(logn))完成剩餘排序,是一種十分巧妙的排序方法。

首先是遞歸的程序:

void MergeSort(int st,int en)
{
    int mid=(st+en)/2;
    if(st<en)
    {
        MergeSort(st,mid);//遍歷左子樹
        MergeSort(mid+1,en);//遍歷右子樹
        Merge(st,mid,en);//對當前這一段進行歸併
    }
}

沒什麼可講的,對吧……

然後就是最關鍵的部分了,如何完成排序操作?

我的思路大概是:首先二分目前的數組變成兩個部分,分別各用一個指針指向兩段數組的最前端,然後比較大小,小的存入臨時數組,大的就留下繼續比較。

由於任何一個指針都需要小於該段的長度,但如果該段並未遍歷完怎麼辦?那麼,我們就直接將剩下的一堆直接插到臨時數組的最後,留給下一次的遍歷去比較。

上代碼吧:

void Merge(int st,int mid,int en)
{
    int i=st;//左子樹首指針
    int j=mid+1;//右子樹首指針
    int k=0;
    int t[100010];//臨時數組,存儲排好序的數列
    while(i<=mid&&j<=en)
    {
        if(m[i]<m[j])
        {
            t[++k]=m[i];
            i++;
        }
        else
        {
            t[++k]=m[j];
            j++;
        }
    }
    //第一次歸併,比較大小
    while(i<=mid)
    {
        t[++k]=m[i];
        i++;
    }
    while(j<=en)
    {
        t[++k]=m[j];
        j++;
    }
    //第二次歸併,直接將剩下的插到隊末
    for(int i=1;i<=k;i++)
        m[st+i-1]=t[i];
    //記得將原數組賦上排好序後的值
}

對了,整段代碼中最容易出錯的是:

for(int i=1;i<=k;i++)
        m[st+i-1]=t[i];

爲什麼這樣說呢,因爲我錯……哦不,因爲這一段中臨時數組是從1(0也可以,隨便你)開始存的,而我們的原數組的賦值應該從該段數組的開頭,也就是st開始,最後還要注意是st+i-1哦!

好的,最後就上一下這道水題的題解吧(註釋請看上面):

#include <iostream>
#include <cstdio>
using namespace std;

int m[100010];

void Merge(int st,int mid,int en)
{
    int i=st;
    int j=mid+1;
    int k=0;
    int t[100010];
    while(i<=mid&&j<=en)
    {
        if(m[i]<m[j])
        {
            t[++k]=m[i];
            i++;
        }
        else
        {
            t[++k]=m[j];
            j++;
        }
    }
    while(i<=mid)
    {
        t[++k]=m[i];
        i++;
    }
    while(j<=en)
    {
        t[++k]=m[j];
        j++;
    }
    for(int i=1;i<=k;i++)
        m[st+i-1]=t[i];
}

void MergeSort(int st,int en)
{
    int mid=(st+en)/2;
    if(st<en)
    {
        MergeSort(st,mid);
        MergeSort(mid+1,en);
        Merge(st,mid,en);
    }
}

int main()
{
    int n;
    cin >> n;
    for(int i=1;i<=n;i++)
        cin >> m[i];
    MergeSort(1,n);
    for(int i=1;i<=n;i++)
        cout << m[i] << " ";
    cout << endl;
    return 0;
}

額,如果說我把這道題做複雜了,那我也無言以對,只是在做1688逆序對之前做個鋪墊吧。

最後,歡迎大家繼續關注我接下來的題解,我將在這周內完成codevs1688求逆序對的題解。

ps:由於作者是一名資深蒟蒻,有錯誤也是不可避免的事,所以歡迎大家指出錯誤,我也會改正的,謝謝。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章