归并排序 学习笔记(第一次如此了解递归)

归并排序 @bilibili正月点灯笼
首先先讲一下归并排序的基本操作方法
给定一个数组,有两部分,两部分的数组都是已经排好序了的,如:
arr[] {2,8,9,10,4,5,6,7};
1.我们记该数组最左边的下标为L,最右边的为R,中间(若偶数就靠右)的为M
有 L = 0 , R = 7 ,M = 4

2.以M为界将原数组分成两个有规律的数组left和right
left[] {2,8,9,10} size = M - L
right[] {4,5,6,7} size = R - M + 1

3.定义3个数i,j,k,分别对应left[],right[],arr[]的初始下标,有
i = 0 , j = 0 , k = 0 开始循环

4.归并到原函数中:从初始位置开始对比,小的数字将其放入arr[k]中,如:如果left[i]<=right[j],那么arr[k] = left[i]

5.k++(第k个位置的数字已确定),i++(这个位置中的数字已使用)
对于例子中的数组,前两次循环是:
left[i] = 2,right[j] = 4,那么arr[k] == left[i],随后i++,k++.
第二次循环
此时 i = 1 , k = 1 , j = 0 , left[i] = 8 , right[j] = 4
有left[i]>right[j],那么有arr[k] = = right[j],随后j++,k++.

6.一直循环下去后,总会有一个数字会先遍历完毕,再进行上述判断的话会有数组溢出的危险,所以结束此处循环,将另一个数组的所有元素添加进去即可。
对于例子中的数组,right[]数组会先遍历完毕,此时j = 3,那么接下来就是将另一个数组的所有数字放入arr[]中就可以了

代码实现:原视频中是用的while,非常精妙,因为while就相当于是一种特殊的判断出口的方法,我原本想的是for,这样的话还需要增加2个监听i,j是否已经出界,占内存而且代码写起来会比较丑

#include<stdio.h>
void merge(int *arr,int L,int M,int R);
int main(){
	int a[]={2,8,9,10,4,5,6,7};
	int L=0,R=7,M=4;
	merge(a,L,M,R); 
	for(int i = 0;i<=R;i++){
		printf("%d ",a[i]);
	}
	return 0;
} 
void merge(int *arr,int L,int M,int R){
	int LEFT_SIZE = M - L;
	int RIGHT_SIZE = R - M + 1;
	int left[LEFT_SIZE];
	int right[RIGHT_SIZE]; 
	int i,j,k;//i作为指向left数组的下标,j指向right数组的下标 ,k指向arr的下标 
	 
	//1.补满左边数组 fill in the left sub array
	for(i = L;i<M;i++){
		left[i-L] = arr[i];//left是从0开始,arr是从L开始 
	}
	
	//2.补满右边数组 fill in the right sub array
	for(i = M;i<=R;i++){
		right[i-M] = arr[i];
	}
	
	//3.归并到原函数中 merge into the original array 
	i=0;j=0;k=L;
	while(i < LEFT_SIZE && j < RIGHT_SIZE){
		if(left[i]<=right[j]){
			arr[k - L] = left[i++];
		} else {
			arr[k - L] = right[j++];
		}
		k++;
	}
	while (i < LEFT_SIZE){
		arr[k - L] = left[i++];
		k++;
	}
	while (j < RIGHT_SIZE){
		arr[k - L] = right[j++];
		k++;
	} 
}

对于上文,我们所用的数组是一个左右两半已经排好顺序的数组,实际上进行排序肯定不是如此,接下来就需要用的是“分而治之”的办法,对我来讲这才是归并排序真正的精妙之处

给定一个乱序数组
{4,3,7,10,2,5,1,9}
为了实现归并排序的作用,我们需要的是左右两边的数组是已经排好规律了的,现在左右两边并不规律,那么先对数组的左右两边进行操作

同样的,先给出3个整型变量L,R,M,L = 0,R = 7,M = (R+L) / 2 = 3
那么此时对于左边的数组,我们需要的是L和M,即
{4,3,7,10}
对于右边的数组,我们需要的是M和R,即
{2,5,1,9}
这部分依然是乱序的,所以对其进行再分割
对于左边,此时的L = 0,R = 3,M = (R+L) / 2 = 1
对于右边,此时的L = 3,R = 7,M = (R+L) / 2 = 5
(这些L和R和M均不同)
分割一次后两边依然不是有规律的,那么就再一次进行分割:
{4,3,7,10} → {4,3}(l11),{7,10}(l12)
{2,5,1,9} → {2,5}(r11),{1,9}(r12)
对于l11,此时的L = 0,R = 1,M = (R+L) / 2 = 0
对于l12,此时的L = 2,R = 3,M = (R+L) / 2 = 2
对于r11,此时的L = 4,R = 5,M = (R+L) / 2 = 4
对于r12,此时的L = 6,R = 7,M = (R+L) / 2 = 6
就l11来说,这依然并不是规律的,所以再一次进行分割
{4,3} → {4}(l111),{3}(l112)
对于l111,此时的L = 0,R = 0 ,M = 0,不可再分
对于l112,此时L = 1,R = 1,M = 1,同样不可再分
此时数组内的元素已经是有规律的了(因为就一个了)所以可以进行最开始的归并排序,将元素放到l11数组中
同样的对于l12下标的子数组,同样通过这个流程将元素放入l12数组中
这时候对于l11和l12,它们的数组已经是有规律的了,同样通过归并排序并成一个大数组,直到最后,数组里的所有元素都成功排好序

上述描述中,可以明白的是我们将一个长串的数组细分成长度为1,即不可再分的数组,这样再对其进行归并排序操作,再拼合,再排序,直到最后数组被排列完毕。
这一个思想叫做“递归”。递归函数必须有一个出口,那就是最后不可再分的单个元素,很明显,当L=R=1的时候,这个数组就不可再分了,所以这就是递归函数的最终出口。成功出去之后就开始逐步进行“归并”的操作,最后对整个数组操作成功

代码实现:

#include<stdio.h>
void merge(int arr[],int L,int M,int R);
void merge_sort(int arr[],int L,int R);
int main(){
	int a[]={9,8,2,10,4,5,6,7};
	int L=0,R=7,M=4;
	//merge(a,L,M,R);
	merge_sort(a,L,R); 
	for(int i = 0;i<=R;i++){
		printf("%d ",a[i]);
	}
	return 0;
} 
void merge(int arr[],int L,int M,int R){
	int LEFT_SIZE = M - L;
	int RIGHT_SIZE = R - M + 1;
	int left[LEFT_SIZE];
	int right[RIGHT_SIZE]; 
	int i,j,k;//i作为指向left数组的下标,j指向right数组的下标 ,k指向arr的下标 
	 
	//1.补满左边数组 fill in the left sub array
	for(i = L;i<M;i++){
		left[i-L] = arr[i];//left是从0开始,arr是从L开始 
	}
	
	//2.补满右边数组 fill in the right sub array
	for(i = M;i<=R;i++){
		right[i-M] = arr[i];
	}
	
	//3.归并到原函数中 merge into the original array 
	i=0;j=0;k=L;
	while(i < LEFT_SIZE && j < RIGHT_SIZE){
		if(left[i]<right[j]){
//			arr[k - L] = left[i++];
//			k++;
			arr[k++] = left[i++];
		} else {
//			arr[k - L] = right[j++];
//			k++;
			arr[k++] = right[j++];
		}
	}
	while (i < LEFT_SIZE){
//		arr[k - L] = left[i++];
//		k++;
		arr[k++] = left[i++];
	}
	while (j < RIGHT_SIZE){
//		arr[k - L] = right[j++];
//		k++;
		arr[k++] = right[j++];
	} 
}
void merge_sort(int arr[],int L,int R){
	if(L == R){//不可再分的时候才return,这样进行下一步merge()的操作 
		return;
	}else{
		int M = (L+R)/2;
		merge_sort(arr,L,M);
		merge_sort(arr,M+1,R);
		merge(arr,L,M+1,R);
	}
}

注意:最后使用merge()中的M是M+1

猜你喜欢

转载自blog.csdn.net/a656418zz/article/details/83352315