排序問題之歸並排序


排序問題

算法問題的基礎問題之一,便是排序問題:

  輸入:n個數的一個序列,<a1, a2,..., an>。

  輸出:一個排列<a1',a2', ... , an'>,滿足a1' ≤ a2' ≤... ≤ an' 。(輸出亦可為降序,左邊給出的例子為升序)

一.算法描述

    (1)分治法

      歸並排序是使用到了分治方法(Divide and Conquer)。

  Divide:將原問題分解為若干子問題,其中這些子問題的規模小於原問題的規模。

  Conquer:遞歸地求解子問題,當子問題規模足夠小時直接求解。

  Merge:將子問題的解合並得到原問題的解。

 (2)歸並排序的分治思想

      分解:分解待排序的n個元素序列為各具n/2個元素的子序列

  解決:使用歸並排序遞歸地對子序列排序

  合並:合並兩個已排序的子序列

   (3)歸並排序

      MergeSort:假設我們要對一個輸入規模為n的序列A[1,2, ... , n]進行排序,我們可以考慮將該序列一分為二,左邊為A_left[1, 2, ... , ⌈n/2⌉],右邊為A_right[⌈n/2⌉+1, ... , n]。然后分別對兩邊規模為n/2的子問題進行求解,並將兩個解合並。(⌈n/2⌉是n除以2的向上取整結果)

  Merge:合並算法是將兩個有序的序列A和B合並為一個有序的序列,這個過程只需要我們每次都取出A和B隊列的對首加入一個新隊列的末尾即可,直至某一個隊列為空,將另一個隊列的剩余元素補到新隊列后面。

     

     下面我們給出一個對序列[5, 2, 4, 6, 1, 3, 8, 7]使用歸並排序得到遞增序列的過程。在圖中白色的部分是未排序的序列,每一層將序列規模折半分成左右兩個子序列排序,直至問題規模將為1,子問題規模足夠小時可以直接求解,而規模為1的序列本身就是有序的,所有不需要進行任何操作。最后一步是將所有子問題的解合並,每次都將兩個有序序列合並為一個有序序列,直至只剩下一個有序序列時終止。

      下圖則給出了一個合並算法的例子。對於A,B兩個排序好的序列,每次取出A、B之中的較小值放入歸並后的序列,直到B中元素被取完,直接將A中的剩余元素按順序尾插到Merge序列中,就能得到A與B的合並。

二.代碼實現

      下面是歸並排序的C++實現:

#include<iostream>
#include<cmath>
using namespace std;
/**
 * @brief 對兩個有序序列合並
 * @param arr    原數組
 * @param start  待合並的數組起始下標
 * @param end    待合並的數組結尾下標
 * @param result 合並后的數組
 */
void Merge(int* arr, int start, int end, int* result){
    
    int i,j = 0;
    int mid = (ceil(start + end)/2);
    int left_index = start;
    int right_index = mid + 1;
    int result_index = start;

    //左右都非空時,循環取左右中的最小元素尾插到result數組
    while( left_index < mid + 1 && right_index < end + 1){
        if(arr[left_index] <= arr[right_index]){
            result[result_index] = arr[left_index];
            left_index++;
        }
        else{
            result[result_index] = arr[right_index];
            right_index++;
        }
        result_index++;
    }

    //左右有一個為空,則將非空的數組元素放到result末尾
    while(left_index < mid + 1){
        result[result_index] = arr[left_index];
        result_index++;
        left_index++;
    }
    while(right_index < end + 1){
        result[result_index] = arr[right_index];
        result_index++;
        right_index++;
    }
}

/**
 * @brief 歸並排序
 * @param arr    待排序的數組
 * @param start  待排序的數組起始下標
 * @param end    待排序的數組結尾下標
 * @param result 保存臨時結果的數組
 */
void MergeSort(int* arr, int start, int end, int* result)
{
    //規模為0時不需進行任何操作
    if(0 == end - start){
        return;
    }
    else{

        //遞歸地對左右序列排序,合並到result中以后覆蓋arr中的對應位置元素
        MergeSort(arr,start,ceil((start+end)/2), result);
        MergeSort(arr,ceil((start+end)/2)+1,end, result);
        Merge(arr,start,end,result);
        for(int i = start; i <= end; i++){
            arr[i] = result[i];
        }

    }

}
    


int main()
{
    int arr[10] = {5,2,4,7,10,9,8,1,6,3};
    int result[10];
    MergeSort(arr,0,9,result);

    for(int i = 0; i < 10; i++){
        cout << arr[i] <<' ';
    }
    
}

 

 

三.算法分析

(1)時間復雜度

   對於最好,最壞和平均情況,問題都要將規模分解到1為止,所以三者的時間復雜度相同。

   T(n) = 2T(n/2) + θ(n)  n > 1時

   T(n) = θ(1)         n = 1時

   通過畫遞歸樹分析可知T(n) = cnlgn + cn, 時間復雜度即為θ(nlgn),當然也為o(nlgn)。

(2)穩定性

   穩定性是指對於原有序列中的等值元素,是否在排序后不改變它們的相對順序關系。歸並時當左邊不大於右邊時先取左邊的元素,遵循這樣的規律時歸並排序就是穩定的。

(3)適合范圍

       一般用於對總體無序,但是各子項相對有序的數列。

(4)算法改進

   TimSort,結合了插入排序來優化,具體不詳細展開。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM