思想
归并排序是建立在归并操作上的一种排序算法,该算法是采用分治法的典型应用。算法的目的是将长的无序数列递归的划分成小的数列,对小的数列进行排序后再进行合并操作,最终完成整个数列的排序。这么说有点粗略了,下面看详细步骤。
有序数列的合并操作
假设一个数组共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);
}
}