排序4:普通归并排序

来源:互联网 发布:七匹狼皮带怎么样 知乎 编辑:程序博客网 时间:2024/05/22 11:40

归并排序,把数据分词若干部分,每个部分再分为若干部分,这样逐渐直到每个小部分满足预定的可进行排序的数量时,再进行各自的排序;继而每个排序后的小部分再按拆分时的顺序进行再排序的合并,最终合并为之前的整体。

上面的描述非常涩,下面画图来描述:


比如原始数据是[4,3,2,6,5,7,1],那么首先规定我们的预定是:

1、分2路进行归并排序(典型的归并排序是2路归并,实际还包括多路归并排序);

2、当拆分的小数据集的长度小于等于2的时候,不用再拆分了,进入合并排序

那么就根据上面的预定,下面开始归并排序:

1、首先将原始数据集分为2部分,即[4,3,2,6]和[5,7,1],显然长度分别为4和3

2、显然不满足"拆分的小数据集的长度小于等于2的时候"的停止拆分条件,继续拆分,[4,3,2,6]拆分为[4,3]和[2,6],[5,7,1]拆分为[5,7]和[1]

3、[4,3]和[2,6],长度均为2,满足停止拆分条件,进入合并排序,合并排序后结果为[2,3,4,6]

4、[5,7]和[1],长度分别为2和1,满足停止拆分条件,进入合并排序,合并排序后结果为[1,5,7]

5、[2,3,4,6]和[1,5,7]再做合并排序,结果为最终结果为[1,2,3,4,5,6,7]


看上图,其实和快速排序有一个相似的地方就是,都是把数据集不断分成一些区间,但本质上不同的是:

1、快速排序是基于标杆数据和其他数据的比较,划分区间的分治

2、典型的归并排序是直接划分区间,然后再对区间内数据进行排序


归并排序的核心,了解了这些就对归并排序的原理有了较为透彻的理解:

1、两个数据集N1和N2的合并排序:时间复杂度是O[N1 + N2],比如[4,3]和[2,6]的合并排序为[2,3,4,6],时间复杂度是O(4),因为使用了O(4)的空间复杂度,方式是不断找最小的树放入一个临时空间,所以能在O(4)的时间复杂度下完成合并排序。

2、归并排序的时间复杂度:最好最坏平均都是O(N * logN),为什么?看上图的merge部分,每一次merge的时间复杂度都是O(N),进行了多少次merge?很明显,2次,二叉树原理,看原始数据需要多少次二分达到停止继续拆分条件,后面就需要多少次的merge操作,即所需的时间复杂度具体是:O(N * (logN - 1)) -> O(N * logN)。而且归并排序不受原始数据影响,都是

3、归并排序是稳定的:观察上面的图,归并排序根本没有交换排序、插入排序的那些可能出现的颠倒位置的情况。这个是相比于快速排序和堆排序的一个重大优势,保证了同值数据不会排序后顺序变化。

4、归并排序使用了空间复杂度:之前的交换、选择、插入排序都还没有使用额外空间助力排序,而归并排序在合并排序时是通过临时空间达到O(N)的线性时间复杂度。这在数据量较大时是个缺陷。

5、让归并排序的空间复杂度降低为不需要空间复杂度:方法就是把合并排序里改改,往往是改成用快排代替掉,但这样并不太好,虽然空间复杂度下降,但最终归并排序的平均时间复杂度变为了O(N * logN * logN),还不如直接去快排。

6、归并排序更大的改进空间在于:并行化、多路归并排序

7、归并排序应用场景:归并排序需要额外空间的这个特点,往往在基于内存的内部排序中较少用到,最多用于外部排序

代码及注释:

merge.h(类声明):

#include <vector>template<class T> class mergesort {std::vector<T> data;void msplit(int start, int end);void merge(int start1, int end1, int start2, int end2);public:mergesort(T *_data, int size);mergesort(std::vector<T> _data);~mergesort(){data.clear();}void msort();void show(bool direct);};
merge_func.h(类实现):
#include "merge.h"#include <iostream>template<class T> mergesort<T>::mergesort (T *_data, int size) {for (int i = 0; i < size; i++) {data.push_back(_data[i]);}msort();}template<class T> mergesort<T>::mergesort (std::vector<T> _data) {data = _data;msort();}//归并排序的合并排序部分, 达到O(N)的排序时间复杂度, 是因为使用了O(N)的空间复杂度template<class T> void mergesort<T>::merge (int start1, int end1, int start2, int end2) {std::cout << "merge: " << start1 << ", " << end1 << ";  " << start2 << ", " << end2 << std::endl;int baseidx = start1;int size = end2 - start1 + 1;int *tmp = new int[end2 - start1 + 1];int idx = 0;while (start1 <= end1 && start2 <= end2) {if (data[start1] <= data[start2]) {tmp[idx] = data[start1];++start1;++idx;continue;}if (data[start2] < data[start1]) {tmp[idx] = data[start2];++start2;++idx;continue;}}while (start1 <= end1) {tmp[idx] = data[start1];++idx;++start1;}while (start2 <= end2) {tmp[idx] = data[start2];++idx;++start2;}for (int i = 0; i < size; i++) {data[baseidx + i] = tmp[i];}delete []tmp;}//传统的2路归并排序. 整个数据集分为[start1, end1]和[start2, end2]两路各自去做归并排序, //而[start1, end1]和[start2, end2]两个子数据集, 也会继续不断的拆分2路再去做归并排序//直到(end - start <= 1), 往往的实现中是(end >= start), 我这样做是减少了无谓的递归到头, start1 == end1及start2 == end2, merge时只是比较两个数, 过多的递归//其实并没有减少时间复杂度, 因为merge函数的平均时间复杂度就是O(N), 只是喂给merge的输入次数减少, 输入数据尽可能杜绝了单个数template<class T> void mergesort<T>::msplit (int start, int end) {if (end - start <= 1) {return;}//不断的二叉拆分, 直到数据集长度小于等于2时, 停止继续拆分, 进入mergeint start1 = start, end1 = (start + end)/2;int start2 = end1 + 1, end2 = end;std::cout << "msplit1: " << start1 << ", " << end1 << std::endl;msplit(start1, end1);std::cout << "msplit2: " << start2 << ", " << end2 << std::endl;msplit(start2, end2);merge(start1, end1, start2, end2);}template<class T> void mergesort<T>::msort () {msplit(0, data.size() - 1);}template<class T> void mergesort<T>::show (bool direct) {if (direct) {for (int i = 0; i < data.size(); i++) {std::cout << data[i];if (i != data.size() - 1) {std::cout << ", ";}}} else {for (int i = data.size() - 1; i >= 0; i--) {std::cout << data[i];if (i != 0) {std::cout << ", ";}}}std::cout << std::endl;}
merge.cpp(测试程序):
#include "merge_func.h"#include <stdlib.h>int main () {int *testdata = new int[sizeof(int) * 9];srand((int)time(0));for (int i = 0; i < 9; i++) {testdata[i] = rand() % 1000;std::cout << testdata[i] << ", ";}std::cout << std::endl;//int testdata[] = {97, 87, 72, 86, 69, 75, 22, 1, 53, 54, 58, 86, 74, 51, 60, 17, 14, 35, 91, 27, 74, 85, 39, 66, 24, 59, 45, 16, 30, 92};mergesort<int> mergesorter(testdata, 9);mergesorter.show(1);delete []testdata;return 0;}


0 0
原创粉丝点击