排序算法 — 归并排序

文章目录

  • 归并排序介绍
    • 从下往上的归并排序
    • 从上往下的归并排序
  • 归并排序实现
    • 从上往下的归并排序
    • 从下往上的归并排序
  • 归并排序的时间复杂度和稳定性
    • 归并排序时间复杂度
    • 归并排序稳定性
  • 代码实现
  • 核心&总结

每日一道算法,提高脑力。第五天(时隔7天,终于回归),归并排序。

归并排序介绍

根据具体的实现,归并排序包括"从上往下"和"从下往上"2种方式。

从下往上的归并排序

将待排序的数列分成若干个长度为1的子数列,然后将这些数列两两合并;得到若干个长度为2的有序数列,再将这些数列两两合并;得到若干个长度为4的有序数列,再将它们两两合并;直接合并成一个数列为止。这样就得到了我们想要的排序结果。(参考下面的图片)

从上往下的归并排序

它与"从下往上"在排序上是反方向的。它基本包括3步:

  • 分解 – 将当前区间一分为二,即求分裂点 mid = (low + high)/2;
  • 求解 – 递归地对两个子区间a[low…mid] 和 a[mid+1…high]进行归并排序。递归的终结条件是子区间长度为1。
  • 合并 – 将已排序的两个子区间a[low…mid]和 a[mid+1…high]归并为一个有序的区间a[low…high]。

在这里插入图片描述

归并排序实现

从上往下的归并排序

从上往下的归并排序采用了递归的方式实现。它的原理非常简单,如下图:

在这里插入图片描述

通过"从上往下的归并排序"来对数组{80,30,60,40,20,10,50,70}进行排序时:

将数组{80,30,60,40,20,10,50,70}看作由两个有序的子数组{80,30,60,40}和{20,10,50,70}组成。对两个有序子树组进行排序即可。

将子数组{80,30,60,40}看作由两个有序的子数组{80,30}和{60,40}组成。 将子数组{20,10,50,70}看作由两个有序的子数组{20,10}和{50,70}组成。

将子数组{80,30}看作由两个有序的子数组{80}和{30}组成。 将子数组{60,40}看作由两个有序的子数组{60}和{40}组成。将子数组{20,10}看作由两个有序的子数组{20}和{10}组成。将子数组{50,70}看作由两个有序的子数组{50}和{70}组成。

从下往上的归并排序

从下往上的归并排序的思想正好与"从下往上的归并排序"相反。如下图:

在这里插入图片描述

通过"从下往上的归并排序"来对数组{80,30,60,40,20,10,50,70}进行排序时:

  • 将数组{80,30,60,40,20,10,50,70}看作由8个有序的子数组{80},{30},{60},{40},{20},{10},{50}和{70}组成。
  • 将这8个有序的子数列两两合并。得到4个有序的子树列{30,80},{40,60},{10,20}和{50,70}。
  • 将这4个有序的子数列两两合并。得到2个有序的子树列{30,40,60,80}和{10,20,50,70}。
  • 将这2个有序的子数列两两合并。得到1个有序的子树列{10,20,30,40,50,60,70,80}。

归并排序的时间复杂度和稳定性

归并排序时间复杂度

归并排序的时间复杂度是O(N*lgN)。

假设被排序的数列中有N个数。遍历一趟的时间复杂度是O(N),需要遍历多少次呢? 归并排序的形式就是一棵二叉树,它需要遍历的次数就是二叉树的深度,而根据完全二叉树的可以得出它的时间复杂度是O(N*lgN)。

归并排序稳定性

归并排序是稳定的算法,它满足稳定算法的定义。

算法稳定性 – 假设在数列中存在a[i]=a[j],若在排序之前,a[i]在a[j]前面;并且排序之后,a[i]仍然在a[j]前面。则这个排序算法是稳定的!

代码实现


package com.zxn;

/**
 * @author zxn
 * @ClassName MergeSort
 * @Description
 * @createTime 2023年05月04日 23:25:00
 */
public class MergeSort {
    /*
     * 将一个数组中的两个相邻有序区间合并成一个
     *
     * 参数说明:
     *     a -- 包含两个有序区间的数组
     *     start -- 第1个有序区间的起始地址。
     *     mid   -- 第1个有序区间的结束地址。也是第2个有序区间的起始地址。
     *     end   -- 第2个有序区间的结束地址。
     */
    public static void merge(int[] a, int start, int mid, int end) {
        int[] tmp = new int[end-start+1];    // tmp是汇总2个有序区的临时区域
        int i = start;            // 第1个有序区的索引
        int j = mid + 1;        // 第2个有序区的索引
        int k = 0;                // 临时区域的索引

        while(i <= mid && j <= end) {
            if (a[i] <= a[j])
                tmp[k++] = a[i++];
            else
                tmp[k++] = a[j++];
        }

        while(i <= mid)
            tmp[k++] = a[i++];

        while(j <= end)
            tmp[k++] = a[j++];

        // 将排序后的元素,全部都整合到数组a中。
        for (i = 0; i < k; i++)
            a[start + i] = tmp[i];

        tmp=null;
    }

    /*
     * 归并排序(从上往下)
     *
     * 参数说明:
     *     a -- 待排序的数组
     *     start -- 数组的起始地址
     *     endi -- 数组的结束地址
     */
    public static void mergeSortUp2Down(int[] a, int start, int end) {
        if(a==null || start >= end)
            return ;

        int mid = (end + start)/2;
        mergeSortUp2Down(a, start, mid); // 递归排序a[start...mid]
        mergeSortUp2Down(a, mid+1, end); // 递归排序a[mid+1...end]

        // a[start...mid] 和 a[mid...end]是两个有序空间,
        // 将它们排序成一个有序空间a[start...end]
        merge(a, start, mid, end);
    }


    /*
     * 对数组a做若干次合并: 数组a的总长度为len,将它分为若干个长度为gap的子数组;
     *             将"每2个相邻的子数组" 进行合并排序。
     *
     * 参数说明:
     *     a -- 待排序的数组
     *     len -- 数组的长度
     *     gap -- 子数组的长度
     */
    public static void mergeGroups(int[] a, int len, int gap) {
        int i;
        int twolen = 2 * gap;    // 两个相邻的子数组的长度

        // 将"每2个相邻的子数组" 进行合并排序。
        for(i = 0; i+2*gap-1 < len; i+=(2*gap))
            merge(a, i, i+gap-1, i+2*gap-1);

        // 若 i+gap-1 < len-1,则剩余一个子数组没有配对。
        // 将该子数组合并到已排序的数组中。
        if ( i+gap-1 < len-1)
            merge(a, i, i + gap - 1, len - 1);
    }

    /*
     * 归并排序(从下往上)
     *
     * 参数说明:
     *     a -- 待排序的数组
     */
    public static void mergeSortDown2Up(int[] a) {
        if (a==null)
            return ;

        for(int n = 1; n < a.length; n*=2)
            mergeGroups(a, a.length, n);
    }

    public static void main(String[] args) {
        int i;
        int a[] = {80,30,60,40,20,10,50,70};

        System.out.printf("before sort:");
        for (i=0; i<a.length; i++)
            System.out.printf("%d ", a[i]);
        System.out.printf("\n");

        mergeSortUp2Down(a, 0, a.length-1);        // 归并排序(从上往下)
        //mergeSortDown2Up(a);                    // 归并排序(从下往上)

        System.out.printf("after  sort:");
        for (i=0; i<a.length; i++)
            System.out.printf("%d ", a[i]);
        System.out.printf("\n");
    }
}


核心&总结

  • 针对从上之下的归并排序,要先把数组分解 – 将当前区间一分为二,即求分裂点 mid = (low + high)/2;
  • 求解 – 递归地对分解出的两个子区间a[low…mid] 和 a[mid+1…high]进行归并排序。
  • 终止点 – 递归的终结条件是子区间长度为1。
  • 合并 – 将已排序的两个子区间a[low…mid]和 a[mid+1…high]归并为一个有序的区间a[low…high]。

个人认为算法的重点在于要知道他的定义,这样才能看得懂、学得会。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Mybatis 框架 ( 一 ) 基本步骤

1.概念 1.1.什么是Mybatis框架 &#xff08;1&#xff09;Mybatis是一个半ORM&#xff08;Object Relation Mapping 对象关系映射&#xff09;框架&#xff0c;它内部封装了JDBC&#xff0c;开发时只需要关注SQL语句本身&#xff0c;不需要花费精力去处理加载驱动、创建连接、…

【工具使用】- git实现gitee托管代码以及检出代码

1. 下载Git工具 git下载地址1&#xff1a;https://git-scm.com/download/win git下载2&#xff1a;https://mirrors.tuna.tsinghua.edu.cn/github-release/git-for-windows/git/Git%20for%20Windows%202.40.1/ 下载完成后安装 安装直接执行exe可执行程序&#xff0c;下一步…

Packet Tracer - 配置 RIPv2

Packet Tracer - 配置 RIPv2 目标 第 1 部分&#xff1a;配置 RIPv2 第 2 部分&#xff1a;验证配置 拓扑图 背景信息 尽管在现代网络中极少使用 RIP&#xff0c;但是作为了解基本网络路由的基础则十分有用。 在本活动中&#xff0c;您将使用适当的网络语句和被动接口配置…

【Java笔试强训 24】

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点! 欢迎志同道合的朋友一起加油喔&#x1f93a;&#x1f93a;&#x1f93a; 目录 一、选择题 二、编程题 &#x1f525;年终奖 …

VC++ | MFC应用程序设计:框架搭建

VC | MFC应用程序设计&#xff1a;框架搭建 时间&#xff1a;2023-05-01 文章目录 VC | MFC应用程序设计&#xff1a;框架搭建1.启动程序2.新建项目2-1.新建项目2-2.应用程序类型2-3.文档模板属性2-4.用户界面功能2-5.高级功能选项2-6.生成的类2-7.解决方案资源管理器 3.工程文…

springboot websocket通信

目录 一、websocket是什么 二、实现websocket 2.1参考学习b站资料&#xff08;一定要看&#xff0c;前后端详细&#xff09; 2.2学习配套代码 一、websocket是什么 WebSocket_ohana&#xff01;的博客-CSDN博客 二、实现websocket 2.1参考学习b站资料&#xff08;一定要看…

Java 数组在内存中的结构是怎样的?数组访问、遍历、复制、扩容、缩容如何编写代码?

Java是一门面向对象的编程语言&#xff0c;数组是其中的重要数据结构之一。在Java中&#xff0c;数组是一种固定长度、有序的数据结构&#xff0c;可以存储一组相同数据类型的元素。在本文中&#xff0c;我们将详细介绍Java数组在内存中的结构。 Java数组的定义 在Java中&…

linux中使用docker部署微服务

目录 一、制作jar包&#xff08;如果看一眼很简单&#xff0c;可以直接使用结尾的jar&#xff09; 1.首先创建一个微服务 demo2 2.启动微服务&#xff08;在DemoApplication上右键执行启动就行&#xff09; 注意&#xff1a;其他操作导致的 可能遇到的报错 3.修改端口 4.新…

超细Redis(一)

目录 概述 Redis是什么&#xff1f; Redis能干嘛&#xff1f; 特性 如何学习 Linux安装 测试性能 概述 Redis是什么&#xff1f; Redis &#xff08;Remote Dictionary Server&#xff09;,即远程字典服务 是一个开源使用ANSI C语言编写、支持网络、可基于内存亦可持…

【Java笔试强训 12】

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点! 欢迎志同道合的朋友一起加油喔&#x1f93a;&#x1f93a;&#x1f93a; 目录 一、选择题 二、编程题 &#x1f525;二进制插…

Python小姿势 - Python学习笔记——类与对象

Python学习笔记——类与对象 类与对象是面向对象编程的两个基本概念。类是对象的抽象概念&#xff0c;对象是类的具体表现。 类是对一类事物的抽象&#xff0c;它是描述一类事物的模板&#xff0c;而对象是类的具体表现。对象是类的实例&#xff0c;类是对象的模板。 举个例子&…

STM32 系列 DAC的介绍与使用

STM32网上资料多&#xff0c;对自己来说基本的使用也是很简单的&#xff0c; 我的STM32专栏并没有什么系统的基础教学&#xff0c;基本上是某个项目用到了&#xff0c;或者产品使用过程出过问题 才会来记录一下&#xff0c;正好用到了 DAC &#xff0c;一般产品还用得不多&…

QML应用动画(Applying Animations)

目录 一 扩展可点击图像元素版本2&#xff08;ClickableImage Version2&#xff09; 1 第一个火箭 2 第二个火箭 3 第三个火箭 动画可以通过以下几种方式来应用&#xff1a; 属性动画 - 在元素完整加载后自动运行&#xff1b; 属性动作 - 当属性值改变时自动运行&#xf…

【栈】的实现

&#x1f58a;作者 : D. Star. &#x1f4d8;专栏 : 数据结构 &#x1f606;今日分享 : —>&#x1f4d6;区块链 &#xff1a; 小明向你借100块钱&#xff0c;说一周后还你&#xff0c;然后你拿个喇叭大喊一声&#xff1a;我是某某&#xff0c;小明向我借了100块&#xff0c…

Vue3+Element Plus环境搭建和一键切换明暗主题的配置

Vue (发音为 /vjuː/&#xff0c;类似 view) 是一款用于构建用户界面的 JavaScript 框架。而Element Plus是一款基于Vue3面向设计师和开发者的组件库。 最终效果&#xff1a; 环境搭建 已安装 16.0 或更高版本的 Node.js&#xff0c;终端&#xff1a; npm init vuelatest这一…

Three.js--》Gsap动画库基本使用与原理

目录 Gsap动画库使用讲解 Gsap动画库基本使用 修改自适应画面及双击进入全屏 设置stats性能监视器 Gsap动画库使用讲解 GSAP的全名是GreenSock Animation Platform&#xff0c;是一个从flash时代一直发展到今天的专业动画库&#xff0c;今天将其与three.js进行结合&#x…

面试官:你知道 Spring lazy-init 懒加载的原理吗?

普通的bean的初始化是在容器启动初始化阶段执行的&#xff0c;而被lazy-init修饰的bean 则是在从容器里第一次进行context.getBean(“”)时进行触发。 Spring 启动的时候会把所有bean信息(包括XML和注解)解析转化成Spring能够识别的BeanDefinition并存到Hashmap里供下面的初始…

HttpRunner3.x 源码解析(5)-runner.py

首先看下生成的pytest文件 from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCaseclass TestCaseLogin(HttpRunner):config (Config("登录成功").variables(**{"password": "tester", "expect_foo2": "co…

4.4.1内核编译

内核源码下载地址&#xff1a; https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/linux-4.4.1.tar.gz 安装依赖包&#xff1a;报错就装 cp /boot/config-xxx ./.config make mrproper make menuconfig,然后save保存&#xff0c;退出 make -j4 //四线程编译 sudo ma…

Java基础(十六)泛型

1. 泛型概述 1.1 生活中的例子 举例1&#xff1a;中药店&#xff0c;每个抽屉外面贴着标签 举例2&#xff1a;超市购物架上很多瓶子&#xff0c;每个瓶子装的是什么&#xff0c;有标签 举例3&#xff1a;家庭厨房中&#xff1a; Java中的泛型&#xff0c;就类似于上述场景中的…