排序4:普通归并排序

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

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


比如原始数据是[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时, 停止继续拆分, 进入merge
	int 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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值