当前位置: 首页 > article >正文

大一计算机的自学总结:归并排序及归并分治

前言

之前写过最基础的三种排序算法大一计算机的自学总结:初见排序,但时间复杂度都是O(n^2),运行起来会很慢,而归并排序的时间复杂度是O(n*logn),会比之前三种排序算法快很多。

一、归并排序

原理就是每次都将数组分为左半部分和右半部分,使它们各自有序,再合并起来,让整体有序。

1.递归版

根据原理不难看出,如果用递归的话过程会非常简单。

#include<bits/stdc++.h>
using namespace std;

//全局变量
#define n 10
int arr[n]; 

//左跨右
void merge(int l,int m,int r)
{
	int i=l; 
	int a=l;
	int b=m+1;
	int help[n];//辅助数组 
	
	while(a<=m&&b<=r)
	{
		help[i++]=arr[a]<=arr[b]?arr[a++]:arr[b++];
	}
	
	while(a<=m)
	{
		help[i++]=arr[a++];
	}
	while(b<=r)
	{
		help[i++]=arr[b++];
	}
	
	for(i=l;i<=r;i++)
	{
		arr[i]=help[i];		
	}
} 

//归并排序 ——递归版 
void mergeSort(int l,int r)
{
	if(l==r)
	{
		return ;
	}
	
	int m=(l+r)/2;
	mergeSort(l,m);
	mergeSort(m+1,r);
	merge(l,m,r);
}

int main()
{
	for(int i=0;i<n;i++)
	{
		cin>>arr[i];
	}
	
	//归并排序 
	mergeSort(0,n-1);
	
	cout<<"Sorted:"<<endl;
	for(int i=0;i<n;i++)
	{
		cout<<arr[i]<<" ";
	}
	
	return 0;
}

排序函数没什么好说的,就是一个递归函数,重点是merge函数。

merge函数的作用是让两部分整体有序,考虑到直接在原数组上操作很麻烦,所以这里借助一个辅助数组,先将两部分的数从大到小填入辅助数组,再用辅助数组覆盖原数组即可。

将两部分的数从大到小填入,思路是,借助两个指针a和b,分别控制左半部分和右半部分的数,还有指针i,控制填入help数组的位置。当a和b都在各自范围内时,根据各自位置数的大小填入辅助数组。之后,将两部分的剩余部分填入辅助数组即可。

2.非递归版

在有些题目中,可能要求额外的空间复杂度为O(1),此时就不能使用递归的方法了。

#include<bits/stdc++.h>
using namespace std;

//全局变量
#define n 10
int arr[n];

//左跨右
void merge(int l,int m,int r)
{
	int i=l; 
	int a=l;
	int b=m+1;
	int help[n];//辅助数组 
	
	while(a<=m&&b<=r)
	{
		help[i++]=arr[a]<=arr[b]?arr[a++]:arr[b++];
	}
	
	while(a<=m)
	{
		help[i++]=arr[a++];
	}
	while(b<=r)
	{
		help[i++]=arr[b++];
	}
	
	for(i=l;i<=r;i++)
	{
		arr[i]=help[i];		
	}	
} 

//归并排序——非递归版
void mergeSort(void)
{
	for(int l,m,r,step=1;step<n;step*=2)
	{
		l=0;
		while(l<n)
		{
			m=l+step-1;
			if(m+1>n-1)
			{
				break;
			}
			r=(m+step>n-1)?(n-1):(m+step);
			merge(l,m,r);
			l=r+1;
		}	
	}	
} 

int main()
{
	for(int i=0;i<n;i++)
	{
		cin>>arr[i];
	}
	
	//归并排序 
	mergeSort();
	
	cout<<"Sorted:"<<endl;
	for(int i=0;i<n;i++)
	{
		cout<<arr[i]<<" ";
	}
	
	return 0;
}

观察递归的过程,可以发现,每次都是从中间分割直到只剩一个数,那么考虑设置步长step变量来控制每次merge的两部分大小。

merge函数跟递归版一样,重点是排序函数的变化。

初始化step=1,每次都乘以二,表示递归中分割的过程。之后使l=0,只要l没到最后,此时可以借助step来计算m的位置,若m+1越界,则表示只剩下一部分,所以break;否则计算r的大小,此时注意当剩余部分不够一个step时让r=n-1。之后merge两部分,让l=r+1往下跳即可。

二、归并分治

归并排序的思想,即分开求解再合并。

1.条件

(1)答案可以分为左侧答案+右侧答案+左跨右的答案

(2)在计算左跨右的答案时,若左右各有序,则可简便计算

2.例题——计算数组的小和

#include <iostream>
using namespace std;

typedef long long ll;

ll merge(int arr[],int n,int l,int m,int r)
{
    ll ans=0;
    for(ll i=l,j=m+1,sum=0;j<=r;j++)
    {
        while(i<=m&&arr[i]<=arr[j])
        {
            sum+=arr[i++];
        }
        ans+=sum;
    }
    
    int i=l;
    int a=l;
    int b=m+1;
    int help[n];
    while (a<=m&&b<=r)
    {
        help[i++]=arr[a]<=arr[b]?arr[a++]:arr[b++];
    }
    while(a<=m)
    {
        help[i++]=arr[a++];
    }
    while(b<=r)
    {
        help[i++]=arr[b++];
    }
    for(i=l;i<=r;i++)
    {
        arr[i]=help[i];
    }

    return ans;
}

ll smallSum(int arr[],int n,int l,int r)
{
    if(l==r)
    {
        return 0;
    }
    int m=(l+r)/2;
    return smallSum(arr,n,l, m)+smallSum(arr,n,m+1,r)+merge(arr,n,l,m,r);
}

int main() 
{
    int n;
    cin>>n;
    int arr[n];
    for(int i=0;i<n;i++)
    {
        cin>>arr[i];
    }

    cout<<smallSum(arr,n,0,n-1);
    return 0;
}

 由上述第一个条件分析可以得出,求数组中每个数的小和,答案可以分为左半部分数的小和、右半部分数的小和以及左跨右部分的小和三者之和。

求小和的函数就是一个递归函数,重点是merge函数。

merge函数负责返回左跨右部分的小和,即右侧每一个数在左侧的小和。此时考虑上述第二个条件:左右各有序可简便运算。这是肯定的,若左右各有序,只需要遍历一遍即可解决。

解决方法如下:初始化i和j两指针分别控制左侧和右侧,当i始终在左侧,并且i位置的数比j位置的数小时,将数累加到sum里。若不小于j位置的数,因为左右各有序,则说明i位置右侧没有比j位置数更小的数,跳出循环,将sum累加到ans里。之后,将j移动到下一个位置, 此时,由于左右各自有序,所以不需要从头遍历左侧部分,只需要从i位置继续累加即可。

最后加上归并排序的部分使整体有序即可。

总结

归并排序的时间复杂度非常优秀,并且其归并分治的思想更是非常非常重要!!

END


http://www.kler.cn/a/513642.html

相关文章:

  • 【附源码】108个Python实战项目,练完能力飙升
  • Tomcat - 高并发性能参数配置
  • JS宏进阶:正则表达式的使用
  • 《从入门到精通:蓝桥杯编程大赛知识点全攻略》(五)-数的三次方根、机器人跳跃问题、四平方和
  • mysql的测试方案
  • 7. 计算机视觉
  • Git版本控制 – 使用HEAD
  • 【三维分割】Gaga:通过3D感知的 Memory Bank 分组任意高斯
  • Arthas工具详解
  • 03垃圾回收篇(D1_垃圾收集器算法底层导论)
  • 解锁Java正则表达式替换的高级玩法
  • 【矩形拼接——分类讨论】
  • 蓝桥与力扣刷题(73 矩阵置零)
  • Maven多环境打包方法配置
  • SpringBoot拦截器
  • 专题三_穷举vs暴搜vs深搜vs回溯vs剪枝_全排列
  • 【王树森搜索引擎技术】概要04:搜索引擎的链路(查询词处理、召回、排序)
  • Linux的软件包管理器
  • 《Effective Java》学习笔记——第1部分 创建对象和销毁对象的最佳实践
  • Redis使用基础
  • TCP如何保证安全可靠?
  • 我国的金融组织体系,还有各大金融机构的分类,金融行业的组织
  • 【Excel】【VBA】Reaction超限点筛选与散点图可视化
  • 【线性代数】基础版本的高斯消元法
  • Keil自动生成Bin文件(2)
  • 2024年度个人成长与技术洞察总结