归并排序——图解及C++实现

归并思想

归并排序是采用分治策略(二分)方法将一个数组不断的分隔成若干子序列,这些子序列都是有序的,然后对这些子序列逐步合并,合并的结果也有序,这样不断的操作,最终将整个数组进行排序。
从这里也可以看出,如果需要合并某两个序列,则要求两个序列必须内部是已经排好序的才可以,而这就是归并排序的分解过程,

归并排序的图解

归并图解:
假设有一个名为arr的数组,其元素为{1,3,5,2,4,6},则其归并的过程如下:
在这里插入图片描述
在这里插入图片描述

一步归并的实现
//merge_array方法,用来归并一个数组中的两个部分,这两个部分已经是内部排好序的
template<class T>
void merge_array(T arr[],const int left,const int middle,const int right){//其中,arr表示要排序的数组,left表示要排序的整个部分的左边界,而middle表示左边有序部分的右边界,right表示右边有序部分的右边界
	T temp[right-left+1]; //创建一个数组,其长度等于原数组
	int i = left; //等于左边排好序的部分的左边界
	int j = middle+1; //等于右边排好序的左边界
	int k = 0; //这个是用来指向temp数组的
	int m = 0; //用于最后将结果复制到原数组arr中
	//开始归并
	while(i<=middle && j<=right){  //遍历数组排好序的两个部分,其中i用来遍历左边的部分,j用于遍历右边的部分
		if(arr[i]<=arr[j]){ //如果左边的部分的元素小于等于右边部分的,这里只能是小于等于,不能写小于,因为归并过程是一个稳定过程,而归并排序也是一个稳定排序
			temp[k]=arr[i]; //将左边部分的元素写入temp
			k++; //给k加1,让其右移,为下一次向temp中写入数据做准备
			i++; //i所指向的元素已经遍历过,然后往右继续
		}
		else if(arr[i]>arr[j]){ //如果如果左边的部分的元素大于右边部分的
			temp[k]=arr[j]; //将右边部分的元素写入temp
			k++; //给k加1,让其右移,为下一次向temp中写入数据做准备
			j++; //右移j,遍历下一个元素
		}
	}
	//此时,左右两个部分中可能还会有一些剩下的元素,这个在上面归并的图解过程中已经体现了出来(其在最后一步)。这里就是将某个部分中剩余的元素填入temp数组
	//写入左边部分剩余的元素
	while(i<=middle){
		temp[k] = arr[i];
		k++;
		i++;
	}
	//写入右边部分剩余的元素
	while(j<=right){
		temp[k] = arr[j];
		k++;
		j++;
	}
	//最后,将排好序的数组复制到原数组即可,因为用于保存排序数组的数组temp会在函数执行完后自动销毁
	while(m<k){	//这里不是等于是因为在上面的过程中,在最后一次向temp中写入元素后,还给k加了1,所以这里要写小于,否则会造成越界
		arr[left+m] = temp[m]; //表示这是从左边界起第m个排好序的数
		m++;
	}
}

测试:

int main(){
	int arr[]={1,3,5,2,4,6};
	merge_array(arr,0,2,5); //表示要排序的部分的左边界为0,右边界为5,左边排好序的部分的右边界为2
	for(int i=0;i<6;i++){
		cout<<arr[i]<<endl;
	}
} //结果为:1,2,3,4,5,6
归并排序的递归实现

下面,就开始利用上面的一步递归的方法来实现归并排序

template<class M>
void merge_sort(M arr[],int left,int right){ //left和right表示要排序的左边界和右边界
	if(left>=right) return; //如果left大于等于right,停止递归
	//求出中点
	int middle = left+(right-left)/2; //这里可以防止left+right的值过大
	//对左半部分进行递归
	merge_sort(arr,left,middle);
	//对右半部分进行递归
	merge_sort(arr,middle+1,right);

	//对传入进来的数据调用归并函数
	merge_array(arr,left,middle,right);
}

测试程序:

int main(){
	int arr[]={2,1,5,8,7,6};
	merge_sort(arr,0,5); //调用归并排序方法,左边界为0,右边界为5
	for(int i=0;i<6;i++){
		cout<<arr[i]<<endl;
	}
} //结果为:1,2,5,6,7,8

测试程序调用归并排序执行的过程

在这里插入图片描述
上面是merge_sort(arr,0,2)这一函数递归的过程,merge_sort(arr,3,5)的过程和其类似,这里不再细说。最后得到两个排好序的序列0到2和3到5,然后对这两个序列再进行归并,就可以得到最后的排序结果。

归并排序的迭代实现

首先,对于单次归并的过程并不改变,即merge_array()方法不改变。只改变merge_sort()方法中的一些内容。
此方法参考自《数据结构算法与应用——C++语言描述》

//merge_pass:用来确定归并的序列的两端
template<class A>
void merge_pass(A a[],const int step,const int len){
    //归并长度为step的相邻段
    int i=0;
    while(i<(len-2*step)){ //i表示归并第几个相邻段,i表示的是步长为step时需要合并的每个子序列的首位的下标,而(len-2*step)表示有多少个步长相同的可以归并的序列,比如当len为6,而step为2时,表示只有两个步长相同的可以进行归并的序列,也就是说只能够归并一次(因为每次归并需要两个序列)。这点也可以参看后面的解析(步长为2时各参数的关系)
        merge_array(a,i,i+step-1,i+2*step-1);
        i = i+2*step;
    }
    //两两合并后剩下的元素
    if(i+step < len){ //表示按照相同step归并之后,剩下两个步长不同的子序列,对其进行合并
        merge_array(a,i,i+step-1,len-1); //表示按照
    }
    //此时,可能还剩下单独一个元素或者序列不能归并,将其继续保留在原数组中即可
}

//归并算法的非递归实现
template<class T>
void merge_sort1(T a[],const int len){
    int step = 1; //步长,即是合并序列的长度,刚开始为1
    while(step<len){ //当步长小于整个数组的长度的时候
        merge_pass(a,step,len); //确定要归并的子序列的左边界和右边界
        step = step*2; //步长的变化规律为每次归并的长度为上一次的两倍,如第一次归并长度为1,第二次为2,第三次为4...
    }
}
迭代实现归并排序的图解

迭代的过程比起递归的过程,少了逐步拆分数组的过程,而是直接将数组的每个元素当成一个子序列,逐步进行归并;而递归是将数组逐步拆分成单个元素的子序列,再逐步进行归并,由此也可以看到,迭代实现的归并排序的效率要高于递归实现的归并排序。
int arr[]={2,1,5,8,7,6};为例,下面是其用迭代法归并的图解:
在这里插入图片描述
具体的一次的归并过程参考上面归并的图解。

归并排序迭代实现方式的测试程序
int main(){
	int arr[]={2,1,5,8,7,6};
	merge_sort1(arr,6); //调用归并排序方法,左边界为0,右边界为5
	for(int i=0;i<6;i++){
		cout<<arr[i]<<endl;
	}
} //结果为:1,2,5,6,7,8
迭代实现时各个参数的含义(以实例中的数组为例)

1、 刚开始各项参数的值:
在这里插入图片描述
此时的图解:
在这里插入图片描述
2、 下来各项参数的值
在这里插入图片描述
在这里插入图片描述
3、 下来各项参数的值
在这里插入图片描述
4、当步长为2时刚开始各项值的关系
在这里插入图片描述
5、当步长为4时,各个参数之间的关系
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值