• 基础算法 第七课——归并排序


    导言

    这,是一篇现学现卖的文章。因为,我根本没学过归并排序。所以,这篇文章,绝对能让您学懂归并。如果不懂,那我就再学一遍,再教一遍

    归并排序的概念

    从字面上分析,排序就是排序,归并就是归并。它们结合起来,就可以理解为用归并的方法来进行排序。

    归并:
    还是从字面上分析,归就想成是回归,并就想成是合并。为什么要合并呢?那肯定是已经分散的数据要合起来啊。那么,就很好理解了:归并就是将某些分散的数据合并起来。

    知道归并的意思,就能够理解归并排序的意思了:先将一组无序的序列分散至独立的个体,再将已有序的子序列合并,得到完全有序的序列。

    步骤说明

    1. 将已有数列一分为二,使其分解为左右两组。
    2. 重复执行步骤1,直至其分解成单独一个的个体。
    3. 定义两个指针,初始分别指向相邻两个子集的左端点。
    4. 比较当前两个指针所指数据大小,按照一定规律加入合并数列。
    5. 每次比较后,左指针右移一位,若左指针表示的数据大于右指针表示的数据,右指针右移一位。
    6. 重复执行步骤3、4、5,直至指针所指位置为空,即超出数据集范围。
    7. 重复执行3、4、5、6,直至合并为一组数列。

    逐步分析

    在这里插入图片描述
    (图片源于网络)

    STEP1

    如图,我们先将数列分开来。分开数列很简单吧?我们就定义一个中间值,中间值以前的为左数据集,中间值以后的为右数据集。然后,就可以分别继续拆分,直到该数据集的左端点大于等于右端点。像这样:

    void ms(int l,int r)//merge sort,l表示数据集左端点,r表示数据集右端点
    {
    	//SETP1
    	if(l>=r)//不能再分了
    	{
    		return ;
    	}
    	
    	int mid=(l+r)/2;//中间值
    	
    	ms(l,mid);//分别继续拆分
    	ms(mid+1,r);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    STEP2

    既然已经分解完了,那么就该合并了。如何合并呢?我们可以定义一个数组 b b b 用来存放合并时的数字。按照步骤说明的方法,我们定义两个指针 i , j i,j i,j,一个指向左数据集,一个指向右数据集。但是,我们好像还需要一个指针 k k k,用来指向 b b b 数组的下标,以便存放数据。定义如下:

    void ms(int l,int r)//merge sort,l表示数据集左端点,r表示数据集右端点
    {
    	//SETP1
    	if(l>=r)//不能再分了
    	{
    		return ;
    	}
    	
    	int mid=(l+r)/2;//中间值
    	
    	ms(l,mid);//分别继续拆分
    	ms(mid+1,r);
    	
    	//STEP2
    	int i=l,j=mid+1;//左右指针
    	int k=l;//在l到r的数据中,b数组下标也是l到r
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    STEP3

    最关键的一步来了。既然要合并,那么根据步骤说明,我们就需要比较 a [ i ] , a [ j ] a[i],a[j] a[i],a[j] 的大小。如果 a [ i ] < a [ j ] a[i]a[i]<a[j] b [ k ] b[k] b[k] 就存 a [ i ] a[i] a[i],并且把 i , k i,k i,k 加一。如果 a [ i ] > a [ j ] a[i]>a[j] a[i]>a[j] b [ k ] b[k] b[k],就代表当前左指针已经不能再打了,再打的话就不能使最终数列有序。于是我们将 a [ j ] a[j] a[j] 存进 b [ k ] b[k] b[k],并且把 j , k j,k j,k加一。代码如下:

    void ms(int l,int r)//merge sort,l表示数据集左端点,r表示数据集右端点
    {
    	//SETP1
    	if(l>=r)//不能再分了
    	{
    		return ;
    	}
    	
    	int mid=(l+r)/2;//中间值
    	
    	ms(l,mid);//分别继续拆分
    	ms(mid+1,r);
    	
    	//STEP2
    	int i=l,j=mid+1;//左右指针
    	int k=l;//在l到r的数据中,b数组下标也是l到r
    	
    	//STEP3
    	while(i<=mid&&j<=r)//只要指针没越界,就循环
    	{
    		if(a[i]<a[j])//按照分析进行
    		{
    			b[k]=a[i];
    			i++,k++;
    		}
    		else
    		{
    			b[k]=a[j];
    			j++,k++;
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    STEP4

    上面的程序,有一个问题啊,那就是:循环的条件是同时满足指针 i , j i,j i,j 均没有越界。所以在循环结束后,如果指针 i , j i,j i,j 中,有一个还没有指到边界呢?那些剩下的值怎么办呢?很好办啊,我们直接将其接在数组后面就可以啦!为什么可以这样做?因为在循环结束后,剩下的值绝对是大于等于当前 b b b 数组的最后一位的。所以可以直接接在后面。代码:

    void ms(int l,int r)//merge sort,l表示数据集左端点,r表示数据集右端点
    {
    	//SETP1
    	if(l>=r)//不能再分了
    	{
    		return ;
    	}
    	
    	int mid=(l+r)/2;//中间值
    	
    	ms(l,mid);//分别继续拆分
    	ms(mid+1,r);
    	
    	//STEP2
    	int i=l,j=mid+1;//左右指针
    	int k=l;//在l到r的数据中,b数组下标也是l到r
    	
    	//STEP3
    	while(i<=mid&&j<=r)//只要指针没越界,就循环
    	{
    		if(a[i]<a[j])//按照分析进行
    		{
    			b[k]=a[i];
    			i++,k++;
    		}
    		else
    		{
    			b[k]=a[j];
    			j++,k++;
    		}
    	}
    	
    	//STEP4
    	while(i<=mid)//两种情况都有可能
    	{
    		b[k]=a[i];
    		i++,k++;
    	}
    	while(j<=r)
    	{
    		b[k]=a[j];
    		j++,k++;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    STEP5

    相邻的左右两组数据集已经合并完啦!但是,它是存在 b b b 数组里的,所以我们还要将其复制给 a a a 数组:

    void ms(int l,int r)//merge sort,l表示数据集左端点,r表示数据集右端点
    {
    	//SETP1
    	if(l>=r)//不能再分了
    	{
    		return ;
    	}
    	
    	int mid=(l+r)/2;//中间值
    	
    	ms(l,mid);//分别继续拆分
    	ms(mid+1,r);
    	
    	//STEP2
    	int i=l,j=mid+1;//左右指针
    	int k=l;//在l到r的数据中,b数组下标也是l到r
    	
    	//STEP3
    	while(i<=mid&&j<=r)//只要指针没越界,就循环
    	{
    		if(a[i]<a[j])//按照分析进行
    		{
    			b[k]=a[i];
    			i++,k++;
    		}
    		else
    		{
    			b[k]=a[j];
    			j++,k++;
    		}
    	}
    	
    	//STEP4
    	while(i<=mid)//两种情况都有可能
    	{
    		b[k]=a[i];
    		i++,k++;
    	}
    	while(j<=r)
    	{
    		b[k]=a[j];
    		j++,k++;
    	}
    	
    	//STEP5
    	for(int I=l;I<=r;I++)
    	{
    		a[I]=b[I];
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    STEP6

    已经完成了,这一步是调用的步骤。如果您学懂了,那么应该知道,调用的步骤就为:ms(数组起始下标,数组终止下标);

    STEP0

    以上内容是从小到大排序的方法,如果从大到小,我们只需改一改判断。当然,归并排序还可以将两组数据排列成一组数据,对吧?下面,我将演示如何将两组数据按从小到大的顺序排成一组:

    #include
    
    using namespace std;
    
    int x[10000000],y[10000000];
    
    int b[20000000];
    
    void ms(int a[],int l,int r)//merge sort,l表示数据集左端点,r表示数据集右端点
    {
    	//SETP1
    	if(l>=r)//不能再分了
    	{
    		return ;
    	}
    	
    	int mid=(l+r)/2;//中间值
    	
    	ms(a,l,mid);//分别继续拆分
    	ms(a,mid+1,r);
    	
    	//STEP2
    	int i=l,j=mid+1;//左右指针
    	int k=l;//在l到r的数据中,b数组下标也是l到r
    	
    	//STEP3
    	while(i<=mid&&j<=r)//只要指针没越界,就循环
    	{
    		if(a[i]<a[j])//按照分析进行
    		{
    			b[k]=a[i];
    			i++,k++;
    		}
    		else
    		{
    			b[k]=a[j];
    			j++,k++;
    		}
    	}
    	
    	//STEP4
    	while(i<=mid)//两种情况都有可能
    	{
    		b[k]=a[i];
    		i++,k++;
    	}
    	while(j<=r)
    	{
    		b[k]=a[j];
    		j++,k++;
    	}
    	
    	//STEP5
    	for(int I=l;I<=r;I++)
    	{
    		a[I]=b[I];
    	}
    }
    
    int n,m;
    
    int main()
    {
    	cin>>n>>m;//两组的数据个数
    	for(int i=1;i<=n;i++)
    	{
    		cin>>x[i];
    	}
    	for(int i=1;i<=m;i++)
    	{
    		cin>>y[i];
    	}
    	
    	ms(x,1,n);//排序
    	ms(y,1,m);
    	
    	int i=1,j=1;//按照归并的方法,将两组数组看做左右数据集
    	int k=1;
    	while(i<=n&&j<=m)
    	{
    		if(x[i]<y[j])
    		{
    			b[k]=x[i];
    			i++,k++;
    		}
    		else
    		{				
    			b[k]=y[j];
    			j++,k++;
    		}
    	}
    	while(i<=n)
    	{
    		b[k]=x[i];
    		i++,k++;
    	}	
    	while(j<=m)
    	{
    		b[k]=y[j];
    		j++,k++;
    	}
    	
    	for(int i=1;i<=n+m;i++)//输出
    	{
    		cout<<b[i]<<" ";
    	}
    	
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109

    有什么比上面的代码更简单的吗,有!因为最终都是要排成一组,所以我们是不是可以在输入的时候直接输入在一起,再统一排序,然后输出呢?这样就大大减少了我们的码量。

    总结

    在开头说了,我也是才学归并。评价一下,归并排序其实也没有那么难,只要能理解归并是什么意思,在将其的排序步骤理清楚,就不难解出了。

  • 相关阅读:
    性能测试:工具篇:Jmeter实时可视化平台搭建
    从运维到运维大神,只需要一个正确的选择
    openMVS编译
    Git常用命令汇总
    Ubuntu/Linux系统安装(非虚拟机)
    (一)JDK、转义字符、数据类型
    STL(标准模板库)入门
    LeetCode 第7题:整数反转(Python3解法)
    java优秀毕业生推荐系统ssm
    自动推理的逻辑06--谓词演算
  • 原文地址:https://blog.csdn.net/m0_66603329/article/details/127694054