19. 排序--歸併排序

歸併排序

將已有序的子序列合併,得到有完全有序的序列

核心:有序子列的歸併

// LeftStart=左邊數組的起始位置,RightStart=右邊數組的起始位置,RightEnd=右邊數組的結束位置
void Merge(ElementType[] A, ElementType[] TmpA, int LeftStart, int RightStart, int RightEnd) {
    LeftEnd = RightStart - 1;   // 左邊數組結束位置,假設左右數組緊挨
    TmpIndex = LeftStart;       // 存放結果數組的初始位置
    LeftIndex = LeftStart;
    RightIndex = RightStart;

    while (LeftIndex <= LeftEnd && RightIndex <= RightEnd) {
        if (A[LeftIndex] <= A[RightIndex])
            TmpA[TmpIndex++] = A[LeftIndex++];
        else 
            TmpA[TmpIndex++] = A[RightIndex++];
    }

    while (LeftIndex <= LeftEnd)        // 直接複製左邊剩下的
        TmpA[TmpIndex++] = A[LeftIndex++];

    while (RightIndex <= RightEnd)      // 直接複製右邊剩下的
        TmpA[TmpIndex++] = A[RightIndex++];

    for (i = LeftStart; i <= RightEnd; i++) // 將TmpA中的數據恢復到A中,如果在循環算法中,不需要執行這個恢復過程
        A[i] = TmpA[i];
}
  • 如果兩個子列一共有N個元素,則歸併的時間複雜度是T(N)=O(N)
  • 注意:在歸併過程中僅僅使用了一個臨時數組,而不是每次都申請開闢一個臨時數組,這樣使額外的空間複雜度爲O(N)

歸併算法實現

遞歸算法

分而治之

  1. 遞歸的把序列一分爲二,直到序列只剩2個元素(可能有一組只有一個元素)
  2. 進行子序列的合併
void MSort(ElementType[] A, ElementType[] TmpA, int LeftStart, int RightEnd) {
    int Center; // 中心位置的下標
    if (LeftStart < RightEnd) {     // 至少要有兩個元素才用歸併的意義
        Center = (LeftStart + RightEnd) / 2;
        MSort(A, TmpA, LeftStart, Center);
        MSort(A, TmpA, Center + 1, RightEnd);
        Merge(A, TmpA, LeftStart, Center + 1, RightEnd);
    }    
}

時間複雜度:T(N)=O(NlogN)

統一函數接口

void Merge_Sort(ElementType[] A, int N) {
    ElmentType *TmpA;
    TmpA = malloc(N * sizeof(ElementType));

    if (TmpA == NULL) {
        Error("空間不足");
        return;
    }

    MSort(A, TmpA, 0, N - 1);
    free(TmpA);
}

非遞歸算法

一趟歸併

  1. 對相鄰的兩段有序子列進行合併
  2. 兩兩子列進行合併,同一段子列不會在一趟歸併中參與兩次合併
  3. 需要額外處理剩餘的單個序列,保證最後把結果歸併到TmpA中
// SubLen = 當前有序子列的長度
void Merge_Pass(ElementType[] A, ElementType[] TmpA, int N, int SubLen) {
    for (i = 0; i <= N - 2 * SubLen; i += 2 * SubLen) // i <= N - 2 * SubLen保證循環過程中至少有一對子列可以進行合併
        Merge(A, TmpA, i, i + SubLen, i + 2 * SubLen - 1);  // Merge函數是將最後結果保存在TmpaA中

    if (i + SubLen < N)     // 說明最後還剩兩個序列
        Merge(A, TmpA, i, i + SubLen, N - 1);
    else    // 最後只剩一個序列 
        for (j = i; j < N; j++)
            Tmp[j] = A[j]
}

統一函數接口

void Merge_Sort(ElementType[] A, int N) {
    ElementType *TmpA;
    TmpA = malloc(N * sizeof(ElementType));

    if (TmpA == NULL) {
        Error("空間不足");
        return;
    }

    SubLen = 1;
    while (SubLen < N) {
        Merge_Pass(A, TmpA, N, SubLen);
        SubLen *= 2;    // 如果SubLen在乘2操作以後大於等於N了,則Merge_Pass只會做將TmpA的內容複製給A
        Merge_Pass(TmpA, A, N, SubLen);
        SubLen *= 2;
    }

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