【从头学数据结构和算法】归并排序及其优化(c++实现)

c++实现的归并排序及其优化



普通归并排序

原理

“分治”思想:对于待排序数据,分成前后两部分,对每一部分分别进行排序,排完后再进行合并。其中,对于每一部分的排序也是采取这种分两段排序再合并的思路。

性质

  • 时间复杂度
    ——最好、最坏和平均:O(nlogn)
  • 空间复杂度
    ——O(n):非原地排序!!
  • 稳定性
    ——稳定

代码

/*********************************************************** 
* 未优化归并 
************************************************************/ 
/** 
 * @brief 未优化归并排序使用的合并函数[从小到大](TODO 不完善) 
 * @param a:待排序数组; start,mid:第一段排好序的待合并元素范围,相应[mid+1, end]为第二段 
 * @return void	 
 * @note
 */
template<typename T>
void merge0(T *a, int start, int mid, int end) {
	// 一般可以考虑开辟一个新的满足总长的数组,然后排序在新的数组上,最后复制回原数组 
	// 这里开辟两个数组存放两段原数据,直接在原数组上存最终排序,并借助哨兵使代码简洁
	
	int size_left = mid - start + 1;	// 左半段数量 
	int size_right = end - mid;			// 右半段数量 
	T* left = new T[size_left + 1];		// 加1是为了多加一个哨兵位 
	T* right = new T[size_right + 1];
	int i = 0, j = 0, k = 0;			// 循环计数变量 
	
	// 复制 
	for (i = 0; i < size_left; ++i) {
		left[i] = a[start+i];
	}
	for (i = 0; i < size_right; ++i) {
		right[i] = a[mid+1+i];
	}
	// 加哨兵
	left[size_left] = MAX;
	right[size_right] = MAX; 
	
	// 按序生成新的数组
	for (k = start, i = 0, j = 0; k <= end; ++k) {
		if (left[i] <= right[j]) {
			// “<=” 保证稳定性
			a[k] = left[i];
			++i; 
		}
		else {
			a[k] = right[j];
			++j;
		}
	}
	// 释放动态内存 
	delete [] left;
	delete [] right; 
}
/** 
 * @brief 未优化归并排序使用的递归函数[从小到大](TODO 不完善) 
 * @param a:待排序数组; start,end:待合并元素范围	
 * @return void	 
 * @note
 *	-可改进点: 1.两段排序后先看是否需要排序2.对于小数组使用插入排序;
 */
template<typename T>
void merge_sort0_r(T *a, int start, int end) {
	// 凡递归,先考虑递归终止条件
	if (start >= end) {
		return ;
	} 
	
	int mid = start+((end-start)>>1);	// 中间元素下标
	// 两段数据分开进行排序
	merge_sort0_r(a, start, mid);
	merge_sort0_r(a, mid+1, end);
	// 合并 
	merge0(a, start, mid, end);
}
/** 
 * @brief 未优化归并排序(递归版)[从小到大](TODO 不完善) 
 * @param a:待排序数组; len:待排序元素	
 * @return void	直接修改原 
 * @note
 *	-时间复杂度:最好 - O(nlog);最坏 - O(nlogn);平均 - O(nlogn)  【与待排序数据的有序度无关】 
 *	-空间复杂度:O(n);  !!!不是原地排序算法!!! 
 *	-稳定性:稳定【取决于merge】 
 *	-改进点: 1:对于arr[mid] <= arr[mid+1]的情况,不进行merge 2:对于小数组, 使用插入排序;
 */
template<typename T> 
void merge_sort0(T *a, int len) {
	// 首先检查数据的合法性(TODO 不完善).
	if (a == NULL || len <= 1) {
		return;
	}
	
	// 递归进行归并排序
	merge_sort0_r(a, 0, len-1); 
} 

优化1:merge前增加判断

原理

如果a[mid] <= a[mid+1],说明已经有序,不用再merge

代码

template<typename T>
void merge_sort1_r(T *a, int start, int end) {
	// 凡递归,先考虑递归终止条件
	if (start >= end) {
		return ;
	} 
	
	int mid = start+((end-start)>>1);	// 中间元素下标
	// 两段数据分开进行排序
	merge_sort1_r(a, start, mid);
	merge_sort1_r(a, mid+1, end);
	// 若无序则合并
	if (a[mid] > a[mid+1]) {
		merge0(a, start, mid, end);
	} 
}

优化2:小数据使用插入排序

原理

需要排序的数组足够长时,归并排序肯定比插入排序更快,但当数组长度比较小的时候,常量因子起主导作用,由于插入排序的常量因子比较小,插入排序比归并排序更快。

对归并排序的优化2:当递归排序的子问题变得足够小时,不继续递归调用归并排序,而是直接调用插入排序。

代码

template<typename T>
void merge_sort2_r(T *a, int start, int end) {
	// 凡递归,先考虑递归终止条件
	if (start >= end) {
		return ;
	} 
	// 若数据量小则直接使用插入排序 
	if (end - start < MIN) {
		insert_sort(a, start, end);
	}
	else {
		int mid = start+((end-start)>>1);	// 中间元素下标
		// 两段数据分开进行排序
		merge_sort2_r(a, start, mid);
		merge_sort2_r(a, mid+1, end);
		// 若无序则合并
		if (a[mid] > a[mid+1]) {
			merge0(a, start, mid, end);
		} 
	}
}

测试

以20000个整数进行测试,记录时间,同时assert是否与algorithm中sort结果相同。测试代码略,可见完整代码中。
测试结果时间(同时,assert未报错)如下图所示:
在这里插入图片描述

可见,优化效率提升比较明显,而且对于20000个数据,归并排序优于冒泡排序、插入排序、选择排序
(详情见
【从头学数据结构和算法】冒泡排序及其优化(c++实现)
【从头学数据结构和算法】插入排序及其优化(c++实现)
【从头学数据结构和算法】选择排序及其优化(c++实现)


完整代码

排序代码及优化及测试 完整代码在:
https://github.com/zhangdanzhu/basic_data-structure_algorithm/blob/master/sort/cpp/merge_sort.cpp

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值