算法学习笔记(5.0)-基于比较的高效排序算法-归并排序

news2024/12/23 1:43:27

##时间复杂度O(nlogn)

目录

##时间复杂度O(nlogn)

##递归实现归并排序

##原理

##图例

##代码实现

##非递归实现归并排序

##释

#代码实现


##递归实现归并排序

##原理

是一种基于分治策略的基础排序算法。

1.划分阶段:通过不断递归地将数组从中点处分开,将长数组的排序问题转化成段数组的排序问题。

2.合并阶段:当子数组的长度为1时终止划分,开始合并,持续不断地将左右两个较短的有序数组合并为一个较长的有序数组,直到结束。

##图例

##代码实现

1.向下递归,对半分割

2.递归返回条件:递归到最小,1个就是有序【控制的是范围,归并的是区间】

3.递归到最深处,最小时,就递归回去,开始分割按对半分割出的范围,将已有子序列合并,在tmp里面进行合并

4.再将tmp里形成的有序序列,拷贝回原数组【因下一次递归回去上一层的归并过程中,会将数据在tmp中进行归并,会将tmp中的数据覆盖,因此要及时,拷】

//python代码示例
def Merge(a, start, mid, end) :
    tmp = [] #创建一个临时列表,用来存储合并的值
    #两段起点的开始
    l = start
    r = mid + 1
    #当两段都没到达末尾,则继续循环,将其添加到tmp
    while l <= mid and r <= end :
        if a[l] <= a[r] :
            tmp.append(a[l])
            l += 1
        else :
            tmp.append(a[r])
            r += 1
    #此时肯定只剩下,单一的一个值,即将剩余元素塞入到tmp中
    tmp.extend(a[l:mid+1])
    tmp.extend(a[r:end+1])
    #将合并完成的元素,赋值到a原数组中
    for i in range(start,end+1):
        a[i] = tmp[i - start]
    print(start,end,tmp)

def MergeSort(a,start,end) :
    #利用递归来实现归并排序,将大的问题分解成小的子问题
    if start == end : #当递归到数组只有一个元素时,直接返回
        return
    mid = (start + end) // 2 #计算出mid,找到递归点
    MergeSort(a,start,mid) #左递归
    MergeSort(a,mid+1,end) #右递归

    Merge(a,start,mid,end)
    #左递归,左合并,右递归,右合并,类似于树的后序遍历

a = [7,3,2,6]
MergeSort(a,0,3)

//c++代码实现示例
#include<iostream>
#include<vector>
using namespace std;
//c++代码实现示例
void merge(vector<int> &a, int left, int mid, int right)
{
    int i = left ;
    int j = mid + 1 ;
    int k = left ;
    vector<int> tmp(right - mid + 1) ;
    while ( i <= mid && j <= right )
    {
        if ( a[i] <= a[j] )
        {
            tmp[k++] = a[i++] ;
        }
        else
        {
            tmp[k++] = a[j++] ;
        }
    }
    while ( i <= mid )
    {
        tmp[k++] = a[i++] ;
    }
    while ( j <= right )
    {
        tmp[k++] = a[j++] ;
    }
    for (int m = left ; m <= right ; m++)
    {
        a[m] = tmp[m - left] ;
    }
    cout << left << right << " " ;
    for (int n = 0 ; n < tmp.size() ; ++n)
    {
    	
    	cout << tmp[n] << " " ;
	}
}


void mergeSort(vector<int> &a, int left, int right)
{
    if (left >= right)
    {
        return ;
    }
    int mid = (left + right) / 2 ;
    mergeSort(a,left,mid) ;
    mergeSort(a,mid+1,right) ;
    merge(a,left,mid,right) ;
}

int main()
{
//	vector<int> arr = {1,2,3,4,5};
//	
	vector<int> arr;
    arr.push_back(7);
    arr.push_back(3);
    arr.push_back(2);
    arr.push_back(6);
//    arr.push_back(5);
	mergeSort(arr,0,3) ;
	
	return 0 ;

}
void _MergerSort(DataType* a ,DataType* tmp, int begin,int end)
{
	//递归返回条件,不正常的范围,或只剩下一个数 
	if (begin >= end)
	{
		return ;
	}
	int mid = (begin + end) / 2 ;
	//递归过程 
	_MergeSort(a,tmp,begin,mid) ;
	_MergeSort(a,tmp,mid+1,end) ;
	
	//合并过程 
	//归并到tmp数组中,再拷贝回去 
	int begin1 = begin ;
	int end1 = mid ;
	int begin2 = mid + 1 ;
	int end2 = end ;
	//指向tmp,=begin是 根据要进行比较插入的数组的位置 找到其对应在tmp中所对应的位置,则不会覆盖前面已经排好序的数据
	int index = begin ;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2])
		{
			tmp[index++] = a[begin++] ;
		}
		else
		{
			tmp[index++] = a[begin2++] ;
		}
	}
	//剩下还没有插入进去的 
	while (begin1 <= end1)
	{
		tmp[index++] = a[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[index++] = a[begin2++] ;
	}
	
	//拷贝回去原数组a中
	memcpy(a+begin,tmp+begin,sizeof(DataType)*(end-begin+1)) ; 
}

void MergeSort(DataType* a, int n)
{
	DataType* tmp = (DataType*)malloc(sizeof(DataType)*n) ;
	if (tmp == NULL)
	{
		perror("malloc fail") ;
		return ;
	}
	_MergeSort(a,tmp,0,n-1) ;
	free(tmp) ;
}

对于链表,归并排序相较于其他排序算法具有显著优势,可以将链表排序任务的空间复杂度优化至 𝑂(1) 。

  • 划分阶段:可以使用“迭代”替代“递归”来实现链表划分工作,从而省去递归使用的栈帧空间。
  • 合并阶段:在链表中,节点增删操作仅需改变引用(指针)即可实现,因此合并阶段(将两个短有序链表合并为一个长有序链表)无须创建额外链表。

##非递归实现归并排序

##释

归并排序时二分的思想=>logN层=>递归不会太深、且现在编译器优化后,递归、非递归的性能差距没有特别大了=>所以可以不用考虑非递归。

递归的缺点:递归消耗栈帧,递归的层数太深,容易爆栈

【栈的空间比较小,在x86(32位)环境下,只有8M。(对比同一环境下的堆,则有2G+)。因为平时函数调用开不了多少个栈帧。理论上递归深度>1w 可能就会爆 ,但实际上5k左右就爆掉了】,这时就需要改非递归了。

非递归改写方法:

1.改变循环

2.利用数据结构栈(本质是通过malloc在堆上开辟的存储空间,内存空间需要足够大)

3.递归逆着求-实际上也差不多也是递归思想(如斐波那契数列逆着来求也是可行的)

#代码实现

  1. 开辟新的数组(临时存放)用于归并排序过程
  2. int gap=1;gap*=2【gap控制归并的范围:11归并,22归并,44归并】
  3. for (int i = 0; i < n; i += 2 * gap) { 【i 控制进行比较轮到的组号,控制进行归并的组号】
  4. 归并完一轮,将归并好的有序数组拷贝回原数组memcpy 。
  5. 进入新的一轮归并,直至gap>n则归并完成

     ☆注意的两个情况

     6. if (begin2 >= n) { break; } 第二组不存在,这一组不用归并了 

     7. if (end2 > n) { end2 = n - 1; } 第二组右边界越界问题,修正一下

 
void MergerSortNonR(DataType* a, int n)
{
    DataType* tmp = (DataType*)malloc(sizeof(DataType) * n);   //开辟新的数组(临时存放)用于归并排序过程
    
    if (tmp == NULL)
    {
        perror("malloc fail") ;
        return ;
    }
    
    int gap = 1 ;
    
    while (gap < n) 
    {
        for (int i = 0 ; i < n ; i += 2 * gap)
        {
//            // 1 , 1 归并 
//            // 2 , 2 归并
//            //4 , 4 归并 
//            //[begin1,end1][begin2,end2]归并 
            int begin1 = i ;
            int end1 = i + gap - 1 ;
            
            int begin2 = i + gap ;
            int end2 = i + 2 * gap - 1 ;

//            //如果第二组不存在,这一组不用归并了
              if (begin2 >= n) 
            {
                   break;
              }
              //第二组右边界越界问题,修正一下
              if (end2 > n) 
            {
            end2 = n - 1;
              }
//            
//            //当beigin2 超过 n 的时候,直接break掉就OK了
//            //但end2 超过 n 的时候,需要修改边界问题 n - 1 
//            
            int index = i ;
            
            while (begin1 <= end1 && begin2 <= end2)
            {
                if (a[begin1] < a[begin2])
                {
                    tmp[index++] = a[begin1++] ;
                }
                else
                {
                    tmp[index++] = a[begin2++] ;
                }
            }
            
            while (begin1 <= end1)
            {
                tmp[index++] = a[begin1++] ;
            }
            
            while (begin2 <= end2)
            {
                tmp[index++] = a[begin2++];
            }
//            //为什么这里不能是,2 * gap呢,不一定是2*gap的数都拷贝过去,memcpy(a+i,tmp+i,sizeof(DataType)*(end2 - beigin1 + 1)) 错误 
//            // memcpy(a+i,tmp+i,sizeof(DataType)*(end2 - begin1 + 1)) ; 因为begin1++会发生改变的,因此不可以,错误 
            memcpy(a+i,tmp+i,sizeof(DataType)*(end2 - i + 1)) ;
//            
//        }
        printf("\n");
//        for (int k = 0 ; tmp.size() ; k ++)
//        {
//            printf("%d",tmp[k]) ;
//        }
        gap = gap * 2 ;
    }
    free(tmp);
}
}
 
def MergeSort(a, n) :

    tmp = [0] * (n+1)

    gap = 1

    while (gap < n) :
        z = gap * 2
        for i in range(0,n,z) :

            begin1 = i
            end1 = i + gap - 1

            begin2 = i + gap
            end2 = i + 2 * gap - 1

            if begin2 > n :
                break
            if end2 > n :
                end2 = n - 1

            index = i

            while begin1 <= end1 and begin2 <= end2 :
                if a[begin1] < a[begin2] :
                    tmp[index] = a[begin1]
                    index += 1
                    begin1 += 1
                else :
                    tmp[index] = a[begin2]
                    index += 1
                    begin2 += 1

            while begin1 <= end1 :
                tmp[index] = a[begin1]
                index += 1
                begin1 += 1
            while begin2 <= end2 :
                tmp[index] = a[begin2]
                index += 1
                begin2 += 1

            for j in range(i,end2 + 1) :
                a[j] = tmp[j - i]

        print()
        # print(tmp)
        gap = gap * 2

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1680584.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python之pyQt5实例:树莓派+MPU6050采集数据

1、安装必要的软件包&#xff1a; sudo apt-get update sudo apt-get install python3-smbus python3-dev i2c-tools sudo apt-get install python3-smbus 2、确认I2C接口已经启用&#xff1a; 运行 sudo raspi-config 命令打开Raspberry Pi配置工具。 在菜单中选择 "…

ThreadLocal,一次到位

一、定义 ThreadLocal是线程私有变量&#xff0c;用于保存每个线程的私有数据。 那么什么情况下需要进行线程隔离 二、源码分析 public class ThreadLocalTest01 {ThreadLocal<Integer> t new ThreadLocal<>();public void test() {t.set(1);Integer integer…

如果你还不了解双亲委派模型,来看看这篇吧

文章首发于【Java天堂】&#xff0c;跟随我探索Java进阶之路&#xff01; 类与类加载器 类是由它的类加载器加载进虚拟机中的&#xff0c;在同一个Java虚拟机中&#xff0c;对于同一个Class文件&#xff0c;如果采用不同的类加载器&#xff0c;得到的是不相等的类&#xff0c;…

k8s二进制部署--多master、负载均衡、高可用

目录 1、环境准备 1.1 服务器配置 1.2 master02 节点部署 2、负载均衡部署 2.1 下载nginx 2.2 修改nginx配置文件 2.3 启动nginx 2.3.1 检查配置文件语法 2.3.2 启动nginx服务&#xff0c;查看已监听6443端口 3. 部署keepalived服务(nginx主机&#xff0c;以nginx01为…

十一.吊打面试官系列-JVM优化-深入JVM类加载机制

前言 从本篇文章开始我们来探讨JVM相关的知识&#xff0c;内容附带JVM的启动&#xff0c;JVM内存模型&#xff0c;JVM垃圾回收机制&#xff0c;JVM参数调优等&#xff0c;跟着文章一步一步走相信你对JVM会有一个不一样的认识&#xff0c;如果觉得文章对你有所帮助请给个好评吧…

基于Java+SpringBoot+Mybaties-plus+Vue+elememt 驾校管理系统 设计与实现

一.项目介绍 系统角色&#xff1a;管理员、驾校教练、学员 管理员&#xff1a; 个人中心&#xff1a;修改密码以及个人信息修改 学员管理&#xff1a;维护学员信息&#xff0c;维护学员成绩信息 驾校教练管理&#xff1a;驾校教练信息的维护 驾校车辆管理&…

水离子雾化壁炉与会所房间的氛围搭配

水离子雾化壁炉在会所房间的氛围搭配可以为房间增添舒适、温馨和现代感&#xff0c;以下是一些建议&#xff1a; 主题定位&#xff1a; 根据会所房间的主题和定位选择合适的水离子雾化壁炉款式和设计风格。可以是现代简约、欧式古典或是豪华奢华&#xff0c;确保与房间整体风格…

Java基础学习笔记二

Java基础学习笔记二 6 File1.File类1.1File类概述和构造方法【应用】1.2File类创建功能【应用】1.3File类判断和获取功能【应用】1.4File类删除功能【应用】 2.递归1递归【应用】2递归求阶乘【应用】3递归遍历目录【应用】 3.IO流1 IO流概述和分类【理解】2字节流写数据【应用】…

HL7协议

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 1.介绍2.传输协议规范2.1. MLLP2.1.1. 数据头定义2.1.2. 转义字符集 2.2. 规范说明2.3. 消息格式说明 3.HL7结构介绍3.1. 患者建档&#xff08;ADT^A28&#xff09;…

AI应用之智能体介绍

AI应用之智能体介绍 一、LLM介绍二、智能客服应用1&#xff0c;阿里智能能话机器人2&#xff0c;华为对话机器人3&#xff0c;公司基于讯飞知识库和讯飞大模型的智能客服 三、大模型应用平台介绍1&#xff0c;fastgpt2&#xff0c;毕昇3&#xff0c; 字节海外版&#xff08;科学…

学习Nginx(二):版本介绍和安装

版本 Nginx官方定义了Mainline、Stable、Legacy三种版本。 1. Mainline version&#xff08;主线版本&#xff09; 该版本包含最新的功能和bug修复&#xff0c;被视为开发版&#xff0c;即正在活跃开发中的版本。其版本号通常为单数&#xff0c;例如1.25.5。这个版本的更新较快…

Nvidia Jetson编译安装Opencv With CUDA,完善GSTREAMER功能

简介 Nvidia Jetson 官方刷机流程结束以后&#xff0c;虽然安装了opencv&#xff0c;但是此版本是CPU版本&#xff0c;并且不包含Cpp版本。如果想要完整的OpenCV支持&#xff0c;需要从源码编译。本文介绍如何下载编译&#xff0c;并安装OPENCV库&#xff0c;并获得完整的CUDA…

必应bing广告开户费用介绍,必应搜索广告推广开户服务!

微软必应Bing搜索引擎广告成为了企业提升品牌知名度与市场份额的有效途径之一&#xff0c;作为全球第二大搜索引擎&#xff0c;在中国市场正逐步展现出其独特的广告价值与潜力。对于希望拓展在线市场的中国企业而言&#xff0c;通过云衔科技开启必应Bing国内广告推广之旅&#…

谷歌外贸seo优化怎么做?

一般有两种选择&#xff0c;在大型电商平台开展业务&#xff0c;如亚马逊&#xff0c;阿里巴巴等平台&#xff0c;也可以选择搭建自己的独立站 选择在大型电商平台可以方便迅速建立起自己的商铺&#xff0c;不需要考虑太多交易&#xff0c;支付&#xff0c;物流等方面的问题&am…

2024.05.14 Diffusion 代码学习笔记

配环境 我个人用的是Geowizard的环境&#xff1a;https://github.com/fuxiao0719/GeoWizard。 出于方便考虑&#xff0c;用的pytorch官方的docker容器&#xff0c;因此python版本&#xff08;3.10&#xff09;和原作者&#xff08;3.9&#xff09;不同&#xff0c;其余都是一…

Java小游戏之汤姆猫

背景&#xff1a; 博主写过羊了个羊小游戏&#xff0c;客户觉得羊了个羊同学写过了&#xff0c;想换一个&#xff0c;于是笔者想到了汤姆猫。就是那个以前在苹果手机上的猫。 过程&#xff1a; 初始会有一个猫的图片展示&#xff0c;然后你点击按钮&#xff0c;猫会有不同动作…

力扣刷题 day2

快乐数 202. 快乐数 - 力扣&#xff08;LeetCode&#xff09;   图: java // 快乐数 --> 19 > 1^2 9 ^2 82 > 82 > 8 ^ 2 2 ^ 2 ......public boolean isHappy(int n) {// 使用快慢指针int slow n, fast getSum(n);while (slow ! fast) {slow getSum(slo…

【Day3:JAVA运算符、方法的介绍】

目录 1、运算符1.1 赋值运算符1.2 比较运算符1.3 逻辑运算符1.3.1 逻辑运算符概述1.3.2 逻辑运算符分类1.3.3 短路的逻辑运算符 1.4 三元运算符1.5 运算符优先级 2、方法2.1 方法介绍2.2 方法的定义和调用格式2.2.1 方法的调用2.2.2 带参数方法的调用2.2.3 带返回值方法的调用2…

Zookeeper and RPC dubbo

javaguide zookeeper面试题 Zookeeper 啥是Zookeeper干啥的 ZooKeeper 可以被用作注册中心、分布式锁&#xff1b; ZooKeeper 是 Hadoop 生态系统的一员&#xff1b; 构建 ZooKeeper 集群的时候&#xff0c;使用的服务器最好是奇数台。 启动ZK 下载安装解压 不过多赘述 我的…

正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-15.7讲 GPIO中断实验-编写按键中断驱动

前言&#xff1a; 本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM&#xff08;MX6U&#xff09;裸机篇”视频的学习笔记&#xff0c;在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。…