C++編寫算法(三) ——排序問題進(jìn)階嘹悼,歸并排序

(一)文講到了選擇排序、插入排序和希爾排序等基本排序問題层宫。但人們并不滿足于這幾類排序杨伙,提出了一些排序算法。

一萌腿、歸并排序

歸并排序的思路如下:

  1. 先將一個(gè)數(shù)組分為兩個(gè)子數(shù)組限匣,
  2. 再對(duì)這兩個(gè)子數(shù)組分別進(jìn)行排序(可用前文將到的基本排序方法)
  3. 再將這兩個(gè)子數(shù)組進(jìn)行歸并。

所以毁菱,個(gè)人理解的歸并排序是指第2步的排序完成后米死,對(duì)這兩個(gè)有序的子數(shù)組進(jìn)行歸并為一個(gè)數(shù)組的方法。

1贮庞、原地歸并的抽象方法

原地歸并的抽象方法是指重新建立一個(gè)與數(shù)組大小相符的內(nèi)存空間峦筒,然后將兩個(gè)子有序數(shù)組中的元素從小到大放入這個(gè)數(shù)組當(dāng)中。

#include <iostream>
#include <vector>
using namespace std;
vector<int> SORT(int arr[], int Size);
int main()
{
    int SIZE;
    cout << "輸入數(shù)組大小: ";
    cin >> SIZE;
    int *arr = new int[SIZE];
    vector<int> Result(SIZE);
    for (int i = 0; i < SIZE; i++)
    {
        cin >> arr[i];
    }
    cout << "數(shù)組為:";
    for (int j = 0; j < SIZE; j++)
    {
        cout << arr[j] << " ";
    }
    cout << endl;
    Result = SORT(arr, SIZE);
    cout << "排序后數(shù)組為:";
    for (int j = 0; j < SIZE; j++)
    {
        cout << Result[j] << " ";
    }
    cout << endl;
    delete[] arr;
    system("pause");
    return 0;
}

vector<int> SORT(int arr[], int Size)
{
    vector<int> result(Size);
    int mid = Size / 2;
    int i = 0;
    int j = mid;
    vector<int> temp(Size);
    for (int k = 0; k < Size; k++)
    {
        if (i >= mid)
        {
            temp[k] = arr[j];
            j++;
        }
        else if (j >= Size)
        {
            temp[k] = arr[i];
            i++;
        }
        else if (arr[i] > arr[j])
        {
            temp[k] = arr[j];
            j++;
        }
        else
        {
            temp[k] = arr[i];
            i++;
        }

    }
    for (int i = 0; i < Size; i++)
    {
        result[i] = temp[i];
    }
    return result;
}

2窗慎、自頂向下的歸并排序

當(dāng)然物喷, 如果將前面的原地歸并的方法卤材,加上遞歸的思想后,就可以用于各種數(shù)組的排序中了峦失。遞歸的思想在歸并排序中顯得十分重要扇丛。

基本的思路如下:

  1. 需要寫出原地歸并的抽象方法merge(),
  2. 再通過遞歸的思想尉辑,寫出SORT()函數(shù)帆精。
#include <iostream>
#include <vector>
const int SIZE = 16;
using namespace std;
void SORT(int *arr, int begin, int end);
void merge(int arr[], int begin, int middle, int end);
int main()
{
    int *arr = new int[SIZE];
    for (int i = 0; i < SIZE; i++)
    {
        cin >> arr[i];
    }
    cout << "數(shù)組為:";
    for (int j = 0; j < SIZE; j++)
    {
        cout << arr[j] << " ";
    }
    cout << endl;
    SORT(arr, 0, SIZE-1);
//  merge(arr, 2, 2, 3);
    cout << "排序后數(shù)組為:";
    for (int j = 0; j < SIZE; j++)
    {
        cout << arr[j] << " ";
    }
    cout << endl;
    delete[] arr;
    system("pause");
    return 0;
}

void SORT(int *arr, int begin, int end)
{
    if (begin >= end)
        return;
    int middle = begin + (end - begin) / 2;
    SORT(arr, begin, middle);              // 遞歸的思想
    SORT(arr, middle+1, end);            // 遞歸的思想
    merge(arr, begin, middle, end);
}

void merge(int arr[], int begin, int middle, int end)  // 原地歸并抽象方法
{
    int i = begin;
    int j = middle+1;
    vector<int> temp(SIZE);
    for (int m = begin; m <= end; m++)
        temp[m] = arr[m];
    for (int k = begin; k <= end; k++)
    {
        if (i > middle)
        {
            arr[k] = temp[j];
            j++;
        }
        else if (j > end)
        {
            arr[k] = temp[i];
            i++;
        }
        else if (temp[i] > temp[j])
        {
            arr[k] = temp[j];
            j++;
        }
        else
        {
            arr[k] = temp[i];
            i++;
        }
    }
}

歸并排序可用于處理數(shù)百萬甚至更大規(guī)模的數(shù)組。
分析:由上述所說隧魄,歸并排序使用的是遞歸的思想進(jìn)行排序卓练。如果在小規(guī)模的數(shù)組中頻繁使用歸并排序,將會(huì)影響算法的性能堤器。所以昆庇,我們可以在對(duì)小規(guī)模數(shù)組進(jìn)行簡(jiǎn)單的基礎(chǔ)排序,如插入排序闸溃,就可以降低算法的運(yùn)行時(shí)間整吆。插入排序?qū)σ恍┍容^小型的數(shù)組,有較好的排序效果辉川,這體現(xiàn)在排序時(shí)間上表蝙。

據(jù)書上所說,使用插入排序處理小規(guī)模的子數(shù)組(比如長(zhǎng)度小于15)一般可以將歸并排序的運(yùn)行時(shí)間縮短10%~15%

如何來讓子數(shù)組自動(dòng)地在我們規(guī)定的長(zhǎng)度上進(jìn)行插入排序呢乓旗?其實(shí)一開始我也沒有想明白這個(gè)問題府蛇。但后來書上一詞提醒了我——“改進(jìn)”。所以屿愚,我們需要在前面的遞歸歸并排序的基礎(chǔ)上汇跨,改進(jìn)算法,縮短歸并排序的時(shí)間妆距。
首先穷遂,我想到的第一個(gè)想法就是減少遞歸的次數(shù)。減少遞歸次數(shù)娱据,就需要在一下代碼段中修改蚪黑,修改跳出遞歸函數(shù)的條件!

void SORT(int *arr, int begin, int end)
{
    if (begin >= end)                  //需修改遞歸結(jié)束的條件中剩,以盡早結(jié)束遞歸
        return;
    int middle = begin + (end - begin) / 2;
    SORT(arr, begin, middle);              
    SORT(arr, middle+1, end);            
    merge(arr, begin, middle, end);
}

因此忌穿,可以將子數(shù)組的長(zhǎng)度設(shè)定為4,之前的(begin>=end)條件可以改寫為((end-begin) > 4-1)结啼,再在條件判斷體中加入插入排序算法掠剑,即可完成改進(jìn)。

#include <iostream>
#include <vector>
const int SIZE = 16;
const int SpiltSize = 4;   //子數(shù)組的長(zhǎng)度
using namespace std;
void SORT(int *arr, int begin, int end);
void merge(int arr[], int begin, int middle, int end);
void InsertSort(int *arr, int begin, int end);
int main()
{
    int *arr = new int[SIZE];
    for (int i = 0; i < SIZE; i++)
    {
        cin >> arr[i];
    }
    cout << "數(shù)組為:";
    for (int j = 0; j < SIZE; j++)
    {
        cout << arr[j] << " ";
    }
    cout << endl;
    SORT(arr, 0, SIZE-1);
    cout << "排序后數(shù)組為:";
    for (int j = 0; j < SIZE; j++)
    {
        cout << arr[j] << " ";
    }
    cout << endl;
    delete[] arr;
    system("pause");
    return 0;
}

void SORT(int *arr, int begin, int end)
{
    if (end - begin > (SIZE / SpiltSize) - 1)
    {
        InsertSort(arr, begin, end);
        return;
    }
        
    int middle = begin + (end - begin) / 2;
    SORT(arr, begin, middle);
    SORT(arr, middle+1, end);
    merge(arr, begin, middle, end);
}

void merge(int arr[], int begin, int middle, int end)
{
    int i = begin;
    int j = middle+1;
    vector<int> temp(SIZE);
    for (int m = begin; m <= end; m++)
        temp[m] = arr[m];
    for (int k = begin; k <= end; k++)
    {
        if (i > middle)
        {
            arr[k] = temp[j];
            j++;
        }
        else if (j > end)
        {
            arr[k] = temp[i];
            i++;
        }
        else if (temp[i] > temp[j])
        {
            arr[k] = temp[j];
            j++;
        }
        else
        {
            arr[k] = temp[i];
            i++;
        }
    }
}

void InsertSort(int *arr, int begin, int end)
{
    int Length = (end - begin) + 1;
    int temp;
    for (int i = 0; i < Length; i++)
    {
        for (int j = i; j > 0; j--)
        {
            if (arr[j - 1] > arr[j])
            {
                temp = arr[j - 1];
                arr[j - 1] = arr[j];
                arr[j] = temp;
            }
            else
            {
                continue;
            }
        }
    }
}

3郊愧、自底向上的歸并排序

自底向上的歸并排序思路與自頂向上相反澡腾,即先從大小為2的兩個(gè)子數(shù)組進(jìn)行二二歸并沸伏,再?gòu)拇笮?的兩個(gè)子數(shù)組進(jìn)行四四歸并,以此類推动分。自底向上的代碼量明顯減少,而且無需用到自頂向上的遞歸红选,僅有一個(gè)循環(huán)嵌套即可完成排序澜公。

#include <iostream>
#include <vector>
const int SIZE = 20;
using namespace std;
void SORT(int *arr, int begin, int end);
void merge(int arr[], int begin, int middle, int end);
int min(int a, int b);
int main()
{
    int *arr = new int[SIZE];
    for (int i = 0; i < SIZE; i++)
    {
        cin >> arr[i];
    }
    cout << "數(shù)組為:";
    for (int j = 0; j < SIZE; j++)
    {
        cout << arr[j] << " ";
    }
    cout << endl;
//  InsertSort(arr,0,SIZE-1);
    SORT(arr, 0, SIZE-1);
//  merge(arr, 0, 0, 1);

    cout << "排序后數(shù)組為:";
    for (int j = 0; j < SIZE; j++)
    {
        cout << arr[j] << " ";
    }
    cout << endl;
    delete[] arr;
    system("pause");
    return 0;
}

void SORT(int *arr, int begin, int end)
{
    for (int Rate = 1; Rate < SIZE; Rate += Rate)
    {
        for (int lo = 0; lo < SIZE - Rate; lo += 2 * Rate)
        {
            merge(arr, lo, lo + Rate - 1, min(SIZE - 1, lo + 2 * Rate - 1));
        }
    }
}

void merge(int arr[], int begin, int middle, int end)
{
    int i = begin;
    int j = middle+1;
    vector<int> temp(SIZE);
    for (int m = begin; m <= end; m++)
        temp[m] = arr[m];
    for (int k = begin; k <= end; k++)
    {
        if (i > middle)
        {
            arr[k] = temp[j];
            j++;
        }
        else if (j > end)
        {
            arr[k] = temp[i];
            i++;
        }
        else if (temp[i] > temp[j])
        {
            arr[k] = temp[j];
            j++;
        }
        else
        {
            arr[k] = temp[i];
            i++;
        }
    }
}

void InsertSort(int *arr, int begin, int end)
{
    int Length = (end - begin) + 1;
    int temp;
    for (int i = 0; i < Length; i++)
    {
        for (int j = i; j > 0; j--)
        {
            if (arr[j - 1] > arr[j])
            {
                temp = arr[j - 1];
                arr[j - 1] = arr[j];
                arr[j] = temp;
            }
            else
            {
                continue;
            }
        }
    }
}

int min(int a, int b)
{
    return a > b ? b : a;
}

自底向上的歸并排序比較適合用鏈表組織的數(shù)組。這種方法只需要重新組織鏈表鏈接就能將鏈表原地排序喇肋,并且它無需創(chuàng)建任何新的鏈表節(jié)點(diǎn)坟乾。


這些算法還能在一些小問題上進(jìn)行優(yōu)化。例如蝶防,對(duì)已經(jīng)排序的兩個(gè)子數(shù)組進(jìn)行歸并時(shí)甚侣,可以判斷第一個(gè)子數(shù)組的末位數(shù)A1[end]與第二個(gè)子數(shù)組的首位A2[0]的大小關(guān)系,若A1[end]小于A2[0]间学,則兩子數(shù)組合成的數(shù)組已經(jīng)為有序數(shù)組了殷费,無需再進(jìn)行歸并排序,這樣就可以進(jìn)一步節(jié)省排序的時(shí)間低葫。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末详羡,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子嘿悬,更是在濱河造成了極大的恐慌实柠,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,817評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件善涨,死亡現(xiàn)場(chǎng)離奇詭異窒盐,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)钢拧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來娶靡,“玉大人牧牢,你說我怎么就攤上這事∽硕В” “怎么了塔鳍?”我有些...
    開封第一講書人閱讀 157,354評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)呻此。 經(jīng)常有香客問我轮纫,道長(zhǎng),這世上最難降的妖魔是什么焚鲜? 我笑而不...
    開封第一講書人閱讀 56,498評(píng)論 1 284
  • 正文 為了忘掉前任掌唾,我火速辦了婚禮放前,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘糯彬。我一直安慰自己凭语,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評(píng)論 6 386
  • 文/花漫 我一把揭開白布撩扒。 她就那樣靜靜地躺著似扔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪搓谆。 梳的紋絲不亂的頭發(fā)上炒辉,一...
    開封第一講書人閱讀 49,829評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音泉手,去河邊找鬼黔寇。 笑死,一個(gè)胖子當(dāng)著我的面吹牛斩萌,可吹牛的內(nèi)容都是我干的缝裤。 我是一名探鬼主播,決...
    沈念sama閱讀 38,979評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼术裸,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼倘是!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起袭艺,我...
    開封第一講書人閱讀 37,722評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤搀崭,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后猾编,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瘤睹,經(jīng)...
    沈念sama閱讀 44,189評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評(píng)論 2 327
  • 正文 我和宋清朗相戀三年答倡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了轰传。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,654評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡瘪撇,死狀恐怖获茬,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情倔既,我是刑警寧澤恕曲,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站渤涌,受9級(jí)特大地震影響佩谣,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜实蓬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評(píng)論 3 313
  • 文/蒙蒙 一茸俭、第九天 我趴在偏房一處隱蔽的房頂上張望吊履。 院中可真熱鬧,春花似錦调鬓、人聲如沸艇炎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)冕臭。三九已至,卻和暖如春燕锥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背悯蝉。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工归形, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鼻由。 一個(gè)月前我還...
    沈念sama閱讀 46,382評(píng)論 2 360
  • 正文 我出身青樓暇榴,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親蕉世。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蔼紧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容