思想
歸併排序是建立在歸併操作上的一種排序算法,該算法是採用分治法的典型應用。算法的目的是將長的無序數列遞歸的劃分成小的數列,對小的數列進行排序後再進行合併操作,最終完成整個數列的排序。這麼說有點粗略了,下面看詳細步驟。
有序數列的合併操作
假設一個數組共8個元素,其中左右兩組分別已經排好序了,下面看如何進行合併排序成一個大的數組
a.對於合併的步驟,需要借用一個臨時存儲空間,該空間大小等於待合併的兩個數組長度之和,下圖上面的數組是待合併的兩個有序數列,下面的數組爲臨時空間,這裏對臨時空間的值進行了複製,合併時直接將結果放入原數組,也可以不對臨時空間進行復制,將合併結果放入臨時空間中,最後複製到原數組。申請了臨時空間後需要3個索引:藍色箭頭指向合併後的數組中下一個待存儲的元素位置,兩個黃色箭頭分別指向兩個待合併數組中待比較的元素位置。
b.首先比較黃色箭頭所指的元素大小,1 < 2,那麼將右邊數組所指元素放入藍色箭頭所指位置
c.藍色箭頭後移,表示之前位置的元素已經排好序了,待合併的右邊數組的黃色箭頭後移,表示考慮下一個元素
比較2和4之後,由於2更小,將其讓入藍色箭頭所指位置
d.之後藍色箭頭繼續後移,待合併數組左邊的黃色箭頭後移,指向下一個將要考慮的元素
重複上述步驟,依次將兩個有序的數組合併成一個有序數組。需要注意的是,合併過程中一般會出現其中一個數組的黃色箭頭已經走到結尾了,而另一個還沒有到尾,那麼由於待合併數組本身就是有序的,接下來只需將未走完的數組中剩餘元素全部複製到合併後的數組即可。
遞歸操作
上面的步驟可以看到合併有序數列的效率是比較高的,可以達到O(n),那麼接下來的問題是如何讓待合併的數列有序呢?數組分兩組後單獨排序?那不還是排序嗎,當然不是簡單的分一次,而是一直往下分,一直到兩個數組的長度爲1爲止,當待合併的數組的長度都爲1了,那他們本身自然就是有序的了
圖中第一次分組分爲左右個含有4個元素的數組,繼續分別分組,最終將每組都分成了長度爲1的8個數組,他們各自有序,接下來就是上面講到的合併操作,首先是對長度爲1的數組進行合併,如下圖所示
合併後爲4個長度爲2的數組,他們同樣各自有序,接下來繼續合併長度爲2的有序數組,得到下圖的結果
經過上一步得到了兩個長度爲4的有序數組,繼續合併將得到最終長度爲8的原始數組的有序排序。
以上是歸併排序的步驟,總的來說就是將數組遞歸的劃分爲長度爲1的數組,之後將長度爲1的數組合併成長度爲2的有序數組,一直合併出原始數組
代碼
sortTestHelper.h
//
// Created by 開機燙手 on 2018/4/19.
#ifndef SORT_SORTTESTHELPER_H
#define SORT_SORTTESTHELPER_H
#include <iostream>
#include <ctime>
#include <cassert>
using namespace std;
namespace SortTestHelper {
int *generateRandomArray(int n, int RangeL, int RangeR) {
assert(RangeL <= RangeR);
int *arr = new int[n];
srand(time(NULL));
for (int i = 0; i < n; i++) {
arr[i] = rand() % (RangeR - RangeL + 1) + RangeL;
}
return arr;
}
template<typename T>
void printArray(T arr[], int n) {
for (int i = 0; i < n; i++) {
cout << arr[i] << ' ';
}
cout << endl;
}
template<typename T>
bool isSorted(T arr[], int n) {
for (int i = 0; i < n - 1; i++) {
if (arr[i] > arr[i + 1])
return false;
}
return true;
}
template<typename T>
void testSort(string name, void(*sort)(T [], int n), T arr[], int n) {
clock_t startTime = clock();
sort(arr, n);
clock_t endTime = clock();
assert(isSorted(arr, n));
cout << name << ": " << double(endTime - startTime) / CLOCKS_PER_SEC << " s" << endl;
}
int *copyIntArray(int a[], int n) {
int *arr = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = a[i];
}
return arr;
}
};
#endif //SORT_SORTTESTHELPER_H
main.cpp
#include <iostream>
#include "sortTestHelper.h"
using namespace std;
const int MAX_SIZE = 70000;
template<typename T>
void merging(T list1[], int listSize1, T list2[], int listSize2) {
T temp[listSize1+listSize2];
int i = 0, j = 0, k = 0;
while (i < listSize1 && j < listSize2) {
if (list1[i] > list2[j]) {
temp[k++] = list2[j++];
} else {
temp[k++] = list1[i++];
}
}
while (i < listSize1) {
temp[k++] = list1[i++];
}
while (j < listSize2) {
temp[k++] = list2[j++];
}
for (int m = 0; m < (listSize1 + listSize2); m++) {
list1[m] = temp[m];
}
}
template<typename T>
void MergeSort(T k[], int len) {
if (len > 1) {
T *list1 = k;
int listSize1 = len / 2;
T *list2 = k + len / 2;
int listSize2 = len - listSize1;
MergeSort(list1, listSize1);
MergeSort(list2, listSize2);
merging(list1, listSize1, list2, listSize2);
}
}
int main() {
int *arr = SortTestHelper::generateRandomArray(MAX_SIZE, 0, MAX_SIZE);
SortTestHelper::testSort("Merge Sort", MergeSort, arr, MAX_SIZE);
delete[] arr;
return 0;
}
輸出:
Merge Sort: 0.022 s
歸併排序中,數組長度爲n,將數組分成小的數組需要logn步,每步合併有序數組,可以記爲O(n),總的時間複雜度可以記爲O(nlogn)。算法的效率是比較高的,但是由於遞歸的過程頻繁調用函數以及對棧的操作,因此還有優化的空間。
小改進
上述代碼實現中,在數組合並的函數裏將list1和list2進行合併,中間將比較合併的結果首先放入temp數組,之後將list1和list2中剩餘的元素複製到temp數組中,之後再將temp中元素全部複製到list1中,實際上,可以直接將剩餘的元素直接複製到list1中對應的位置即可,記錄複製到temp中的數據的個數,最後將temp中元素複製回list1中即可。省略了第一次比較大小後剩餘的元素的多次複製
template<typename T>
void merging1(T list1[], int listSize1, T list2[], int listSize2) {
T temp[listSize1 + listSize2];
int i = 0, j = 0, k = 0;
while (i < listSize1 && j < listSize2) {
if (list1[i] > list2[j]) {
temp[k++] = list2[j++];
} else {
temp[k++] = list1[i++];
}
}
int p = -1, q = -1;
if (i < listSize1)
q = i;
while (i < listSize1) {
list1[i + j] = list1[i++];
}
if (j < listSize2)
p = j;
while (j < listSize2) {
list1[i + j] = list2[j++];
}
if (p != -1) {
for (int m = 0; m < (listSize1 + p); m++) {
list1[m] = temp[m];
}
} else if (q != -1) {
for (int m = 0; m < (listSize2 + q); m++) {
list1[m] = temp[m];
}
} else {
for (int m = 0; m < (listSize1 + listSize2); m++) {
list1[m] = temp[m];
}
}
}
template<typename T>
void MergeSort1(T k[], int len) {
if (len > 1) {
T *list1 = k;
int listSize1 = len / 2;
T *list2 = k + len / 2;
int listSize2 = len - listSize1;
MergeSort1(list1, listSize1);
MergeSort1(list2, listSize2);
merging1(list1, listSize1, list2, listSize2);
}
}