排序算法-归并排序及改进 C++实现

归并排序是一个复杂度为O(nlogn)的算法,一般出现复杂度有logn的情况,都是采用了一种类似树形结构的方法。

原理

归并排序将数据如下图不断二分,如果分开的部分已经排好序,那么只需要将这两个部分合并即可。
但是如果分到最后一层,那么只有一个数据就是排好序的,将所有的数据向上归并即可。
在这里插入图片描述
分完层之后就是对数据进行归并处理,这个问题看起来很简单,但是细细想来还挺复杂的。因为只对原数组进行操作并不能达到目的,此时需要另外开辟一个空间进行辅助操作。这也就是说插入排序需要额外使用O(n)的空间复杂度。
在这里插入图片描述
开辟了额外的空间后,需要三个指针i,j,k,k指向原数组,i和j分别指向一分为二的数组。每次对比 i 和 j 的大小,将小的那一个放入到 k 的位置,并且相应的索引向后移动。
在这里插入图片描述
在这里插入图片描述
为了能够让索引指向正确的位置,我们还需要对数组进行额外的定义。首先将分层数组的索引范围定义为 [left , right], 注意这是一个左闭右闭的区间。其次分层后左边数组最右边的索引定义为middle,此时两个数组的索引分别为 [left , middle] ,[middle+1 , right]

实现

在实现归并排序时,思考一下解决逻辑可以发现,最后要完成整个数组的排序,需要将原数组一分为二进行排序。而第二层数组需要排序则需要将第二层数组分别一分为二排序。那么这里就可以使用递归解决该问题:先将数组分层,然后将分层排好序后归并。代码如下(建议从下至上看,子函数在上面)

template <typename T>
void __merge(T arr[], int l, int mid, int r){
	T *aux = new T[r-l+1];
	for(int i=l; i<=r; i++)   //注意是<=,因为数组区间为[l,r]
		aux[i-l] = arr[i];    //aux与arr数组索引之间有l的偏移量
	int i=l, j=mid+1;
	for(int k=l; k<=r; k++){
		if(i > mid){
			arr[k] = aux[j-l];
			j++;
		}
		else if(j > r){
			arr[k] = aux[i-l];
			i++;
		}
		else if(aux[i-l]<aux[j-l]){
			arr[k] = aux[i-l];
			i++;
		}
		else{
			arr[k] = aux[j-l];
			j++;
		}
	}
	delete[] aux;
}
template <typename T>
void __mergeSort(T arr[] , int l , int r){ //对[l,r]的数组归并排序
	if(l>=r)  //当前数组只有一个元素,无需分层
		return;
	int mid = (r-l)/2 + l;  //可防止溢出
	__mergeSort(arr,l,mid);
	__mergeSort(arr,mid+1,r);
	__merge(arr,l,mid,r);   //归并操作
}
template <typename T>
void mergeSort(T arr[] , int n){
	__mergeSort(arr , 0 , n-1); 
}

时间复杂度

可以看出一个长度为n的数组,将它按照二分法分层最多能分为 log ⁡ 2 n \log_{2}n log2n层,在进行归并排序时,需要对原数组进行遍历赋值,时间复杂度为 n n n,所以整个排序的时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)

对归并算法的改进

通过算法模拟可以发现对于几乎有序的数组,插入排序比归并排序运行速度更快,这是因为对于几乎有序的数组,插入排序可以退化为 O ( n ) O(n) O(n)级别。因为归并算法对于有序数组仍然进行归并操作,但是对于有序数组是不用归并的。此时只需要判断mid中的元素是否小于mid+1,如果小于说明数组有序,就不用进行归并。

template <typename T>
void __mergeSort(T arr[] , int l , int r){ //对[l,r]的数组归并排序
	if(l>=r)  //当前数组只有一个元素,无需分层
		return;
	int mid = (r-l)/2 + l;  //可防止溢出
	__mergeSort(arr,l,mid);
	__mergeSort(arr,mid+1,r);
	if(arr[mid] > arr[mid+1])
		__merge(arr,l,mid,r);   //归并操作
}

此外插入算法虽然是 O ( n 2 ) O(n^2) O(n2)的算法,但是常数项比归并算法小,所以在数据量较小时的运算速度比归并算法要快。那么在一个数组只有少数成员时可以采用插入算法。

template <typename T>
void insertionSort(T arr[], int l, int r){
	for(int i=l+1; i<=r; i++){
		T e = arr[i];
		int j;
		for(j=i; j>l && arr[j-1]>e; j--){
			arr[j] = arr[j-1];
		}
		arr[j] = e;
	}
}
template <typename T>
void __mergeSort(T arr[] , int l , int r){ //对[l,r]的数组归并排序
	if(r-l<=15)
		insertionSort(arr,l,r);     
	int mid = (r-l)/2 + l;  //可防止溢出
	__mergeSort(arr,l,mid);
	__mergeSort(arr,mid+1,r);
	if(arr[mid] > arr[mid+1])
		__merge(arr,l,mid,r);   //归并操作
}

自底向上的归并排序

上面讲的是采用自顶向下的,将原数组不断二分——子数组排序——归并。我们也可以采用自底向上的归并方法。将数组先一个一组归并,再两个一组归并,一直到归并完成。
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在整个过程中依然需要归并这个操作,只是将递归形式换成了迭代。

实现

template <typename T>
void mergeSortBU(T arr[], int n){
	for(int size=1; size<=n; size += size){
		for(int i=0; i+size<n; i += size + size){
		//归并必须保证两个数组存在,所以第二个数组左边索引必须小于n
			//对arr[i,i+size-1]和arr[i+size,i+2*size-1]归并 
			__merge(arr, i, i+size-1, min(i+size+size-1,n-1));
			//i+2*size-1可能越界,即第二个数组数据不足
		}
	}
}

参考:https://coding.imooc.com/learn/list/71.html

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值