算法|4.归并排序及应用

news2024/11/28 10:49:38

算法|4.归并排序及应用

1.归并排序算法

题意:归并排序的递归和非递归实现

解题思路:

​ 递归实现:

  • 预处理:数组为空或者长度小于2的直接返回
  • 调用子过程
  • 子过程终止条件L==R
  • 分解成[L,mid],[mid+1,R] ,子数组有序,合并子问题的解,全数组有序
  • 合并使用的是双指针法,最终需要将辅助数组的值再还给原数组。

​ 非递归实现:

  • 注意:**当N非常接近整数最大值时,*必须加那一句if(mergeSize>N/2){ break;}不然2之后可能滚成一个负数,但是加了只是保证循环能够停止,结果可能最后一部分右组没有归并完成,但是不加很可能死循环。
  • 主要解决的就是拆分过程:两种办法①模拟栈Stack②自底向上有序
  • 这里主要使用第二种,模拟栈的话需要压入的参数就是左右的坐标,可以封装一个成类,针对对象操作,暂无需要,略。
  • 还是先预处理:处理特殊情况
  • 这里引入了步长的概念,多少步长为1组,然后组内有序(直接拷贝到原数组,不再需要辅助数组)
  • 之后不断扩大步长,然后使用merge合并,直至全数组有序

优化的点:

  • 这里主要是非递归实现的版本
  • 这里合并使用的是左组和右组——我们每次需要指定左组下标和右组下标
  • 控制左指针的边界条件:L<N
  • 控制中间指针边界条件(左组存在):mergeSize+L-1,M<N(不满足就是左组不够或者右组没了,不满足直接不做合并)
  • 控制右指针的边界条件(右组存在):R=M+mergeSize-1,右组只要有够不够都需要做合并,只不过R的值需要重新赋值判断一下去边界值和当前计算的最小值
  • 每次重置步长之后,L的起始位置都是0

核心代码:

​ 递归实现:

//递归实现
public static void mergeSort1(int[] arr){
    if(arr==null||arr.length<2){
        return ;
    }
    process(arr,0,arr.length-1);
}

private static void process(int[] arr, int L, int R) {
    if(L==R){
        return ;
    }
    int mid=L+((R-L)>>1);
    process(arr,L,mid);
    process(arr,mid+1,R);
    merge(arr,L,mid,R);
}

private static void merge(int[] arr, int L, int M, int R) {
    int[] help=new int[R-L+1];
    int index=0;
    int p1=L;
    int p2=M+1;
    while(p1<=M&&p2<=R){
        help[index++]=arr[p1]<=arr[p2]?arr[p1++]:arr[p2++];
    }
    while(p1<=M){
        help[index++]=arr[p1++];
    }
    while(p2<=R){
        help[index++]=arr[p2++];
    }
    for (int i = 0; i < help.length; i++) {
        arr[L+i]=help[i];
    }
}

​ 非递归实现:

//非递归实现
public static void mergeSort2(int[] arr){
    if(arr==null||arr.length<2){
        return ;
    }
    int N=arr.length;
    int mergeSize=1;
    while(mergeSize<N){
        int L=0;
        while(L<N){
            int M=L+mergeSize-1;
            //左组不够了或者根本左组够了,但是右组没有
            if(M>=N){
                break;
            }
            int R=Math.min(M+mergeSize-1,N-1);
            merge(arr,L,M,R);
            L=R+1;
        }
        if(mergeSize>N/2){

        }
        mergeSize<<=1;
    }
}

测试代码:

// for test
public static int[] generateRandomArray(int maxSize, int maxValue) {
    int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
    for (int i = 0; i < arr.length; i++) {
        arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
    }
    return arr;
}

// for test
public static int[] copyArray(int[] arr) {
    if (arr == null) {
        return null;
    }
    int[] res = new int[arr.length];
    for (int i = 0; i < arr.length; i++) {
        res[i] = arr[i];
    }
    return res;
}

// for test
public static boolean isEqual(int[] arr1, int[] arr2) {
    if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
        return false;
    }
    if (arr1 == null && arr2 == null) {
        return true;
    }
    if (arr1.length != arr2.length) {
        return false;
    }
    for (int i = 0; i < arr1.length; i++) {
        if (arr1[i] != arr2[i]) {
            return false;
        }
    }
    return true;
}

// for test
public static void printArray(int[] arr) {
    if (arr == null) {
        return;
    }
    for (int i = 0; i < arr.length; i++) {
        System.out.print(arr[i] + " ");
    }
    System.out.println();
}

// for test
public static void main(String[] args) {
    int testTime = 500000;
    int maxSize = 10;
    int maxValue = 100;
    System.out.println("测试开始");
    for (int i = 0; i < testTime; i++) {
        int[] arr1 = generateRandomArray(maxSize, maxValue);
        int[] arr2 = copyArray(arr1);
        mergeSort1(arr1);
        mergeSort2(arr2);
        if (!isEqual(arr1, arr2)) {
            System.out.println("Oops!Error!");
            printArray(arr1);
            printArray(arr2);
            break;
        }
    }
    System.out.println("测试结束");
}

测试结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z5wiiOeG-1685098557569)(F:\typora插图\image-20230526174128752.png)]

2.小和问题

题意:在一个数组中,一个数左边比它小的数的总和,叫该数的小和,数组中所有数的小和累加起来,叫数组小和。给定一个数组arr,求数组小和。

暴力方法:O(N^2)
归并排序应用:O(NlogN)

解题思路:

  • 注意:ans+=arr[p1]<arr[p2]?(R-p2+1)*arr[p1]:0;在复制之前求
  • 合并过程中计算小和
  • 小和产生规则:左组拷贝产生小和,右组拷贝不产生小和,相等先拷贝右组:小和是左边比当前数小的总和。要的是原数组中的小和。
  • 总体来说,是一个规则的转换:左边有多少数比当前数小转变成右边有多少数比当前数要大。
  • 指针不回退技巧:单调的,已经排好序了

对数器:

  • 穷举遍历

核心代码:

public static int smallSum(int[] arr){
    if(arr==null||arr.length<2){
        return 0;
    }
    return process(arr,0,arr.length-1);
}

private static int process(int[] arr, int L, int R) {
    if(L==R){
        return 0;
    }
    int mid=L+((R-L)>>1);
    return process(arr,L,mid)+
            process(arr,mid+1,R)+
                merge(arr,L,mid,R);
}

private static int merge(int[] arr, int L, int M, int R) {
    int ans=0;
    int[] help=new int[R-L+1];
    int index=0;
    int p1=L;
    int p2=M+1;
    while(p1<=M&&p2<=R){
        ans+=arr[p1]<arr[p2]?(R-p2+1)*arr[p1]:0;
        help[index++]=arr[p1]<arr[p2]?arr[p1++]:arr[p2++];
    }
    while(p1<=M){
        help[index++]=arr[p1++];
    }
    while(p2<=R){
        help[index++]=arr[p2++];
    }
    for (int i = 0; i < help.length; i++) {
        arr[L+i]=help[i];
    }
    return ans;
}

测试代码:

// for test
public static int test(int[] arr) {
    if (arr == null || arr.length < 2) {
        return 0;
    }
    int res = 0;
    for (int i = 1; i < arr.length; i++) {
        for (int j = 0; j < i; j++) {
            res += arr[j] < arr[i] ? arr[j] : 0;
        }
    }
    return res;
}

非特殊情况,不再放生成随机数组代码,只放对数器代码。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-87ikTikr-1685098557570)(F:\typora插图\image-20230526175839813.png)]

3.逆序对个数

题意:在一个数组中,任何一个前面的数a,和任何一个后面的数b,如果(a,b)是降序的,就称为降序对。给定一个数组arr,求数组的降序对总数量。

暴力方法:O(N^2)
归并排序应用:O(NlogN)

解题思路:

  • 注意:ans+=arr[p1]>arr[p2]?(p2-M):0;help[index--]=arr[p1]<=arr[p2]?arr[p2--]:arr[p1--];下边拷贝规则相等先右边,上边的顾好!!!!!结合例子反复验算谨慎!!!!!不要再反复改了!醉了…
  • 转换指标:从左向右,当左边比右边大的个数,转换成从右向左,右边比当前左数小的个数
  • 计数规则:左边产生,右边不产生,相等的先拷贝左边的
  • 对应的,归并的顺序改变一下

核心代码:

public static int reversePairNumber(int[] arr){
    if(arr==null||arr.length<2){
        return 0;
    }
    return process(arr,0,arr.length-1);
}

private static int process(int[] arr, int L, int R) {
    if(L==R){
        return 0;
    }
    int mid=L+((R-L)>>1);
    return process(arr,L,mid)+
            process(arr,mid+1,R)+
            merge(arr,L,mid,R);
}

private static int merge(int[] arr, int L, int M, int R) {
    int ans=0;
    int[] help=new int[R-L+1];
    int index=help.length-1;
    int p1=M;
    int p2=R;
    while(p1>=L&&p2>=M+1){
        ans+=arr[p1]>arr[p2]?(p2-M):0;
        help[index--]=arr[p1]<=arr[p2]?arr[p2--]:arr[p1--];
    }
    while(p1>=L){
        help[index--]=arr[p1--];
    }
    while(p2>=M+1){
        help[index--]=arr[p2--];
    }
    for (int i = 0; i < help.length; i++) {
        arr[L+i]=help[i];
    }
    return ans;
}

测试代码:

// for test
public static int test(int[] arr) {
    int ans = 0;
    for (int i = 0; i < arr.length; i++) {
        for (int j = i + 1; j < arr.length; j++) {
            if (arr[i] > arr[j]) {
                ans++;
            }
        }
    }
    return ans;
}

测试结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YrJznnb5-1685098557571)(F:\typora插图\image-20230526181737428.png)]

4.大于其2倍右侧数的个数

题意:在一个数组中,对于任何一个数num,求有多少个(后面的数*2)依然<num,返回总个数

暴力方法:O(N^2)
归并排序方法:O(NlogN)

解题思路:

  • 这部分判断逻辑需要单独判断,否则会污染数据或者越界,不容易处理。
  • 本质上来说,归并排序从左向右从右向左均可

核心代码:

public static int reversePairs(int[] arr){
    if(arr==null||arr.length<2){
        return 0;
    }
    return process(arr,0,arr.length-1);
}

private static int process(int[] arr, int L, int R) {
    if(L==R){
        return 0;
    }
    int mid=L+((R-L)>>1);
    return process(arr,L,mid)+
            process(arr,mid+1,R)+
            merge(arr,L,mid,R);
}

private static int merge(int[] arr, int L, int M, int R) {
    int ans=0;
    //[L,M],[M+1,R]
    //符合要求的是[M+1,p)
    int p=M+1;
    //遍历左组
    for (int i = L; i <= M; i++) {
        while(p<=R&&(long)arr[i]>(long)arr[p]*2){
            p++;
        }
        ans+=p-M-1;
    }
    int[] help=new int[R-L+1];
    int index=help.length-1;
    int p1=M;
    int p2=R;
    while(p1>=L&&p2>=M+1){
        help[index--]=arr[p1]<=arr[p2]?arr[p2--]:arr[p1--];
    }
    while(p1>=L){
        help[index--]=arr[p1--];
    }
    while(p2>=M+1){
        help[index--]=arr[p2--];
    }
    for (int i = 0; i < help.length; i++) {
        arr[L+i]=help[i];
    }
    return ans;
}

测试代码:

public static int test(int[] arr) {
    int ans = 0;
    for (int i = 0; i < arr.length; i++) {
        for (int j = i + 1; j < arr.length; j++) {
            if (arr[i] > (arr[j] << 1)) {
                ans++;
            }
        }
    }
    return ans;
}

测试结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qxsM0lci-1685098557571)(F:\typora插图\image-20230526184057230.png)]

归并算法总结

算法描述:

基于分治法的一种排序算法,将全序列拆分成子序列,使子序列有序后,再进行合并得到全有序的序列。

复杂度分析及算法评价:

  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(logn)
  • 稳定的算法

改写方法:

  • 抓住指针不回退,单调的即技巧
  • 流程像——顺序决策,与大小有关

例题总结:

  • 归并排序递归实现:
  • 归并排序非递归实现:判断左组存在,判断右组存在,滚成整数最大值,循环停不下来
  • 小和问题:原则——右组拷贝不产生小和,左组拷贝产生小和,相等时先拷贝右组。先计算再拷贝
  • 逆序数对问题:从右向左拷贝;拷贝原则只与左右方向有关,等号斟酌,计算永远在拷贝之前。
  • 左大右的2倍问题:左右无关;可能有数组下标越界风险,单独处理;复制之前的merge记得去掉ans

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

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

相关文章

九章云极DataCanvas公司诚邀您共享AI基础软件前沿技术盛宴

“杭州通用人工智能论坛暨AIIA人工智能产业发展大会”将于2023年5月30日-31日在杭州举办。本次人工智能产业发展大会由中国信息通信研究院、中国人工智能产业发展联盟主办&#xff0c;杭州城西科创大走廊管委会、杭州市经济和信息化局、杭州未来科技城管理委员会、人工智能关键…

企业级信息系统开发——初探JdbcTemplate操作

文章目录 一、创建数据库与表1、创建数据库2、创建用户表3、用户表添加记录 二、打开Spring项目三、添加数据库相关依赖四、创建用户实体类五、创建用户数据访问接口六、创建用户数据访问接口实现类七、创建用户服务类八、创建数据库配置属性文件九、创建Spring配置文件十、创建…

Springboot +spring security,解决跨域问题

一.简介 这篇文章主要是解释什么是跨域&#xff0c;在Spring中如何解决跨域&#xff0c;引入Spring Security后Spring解决跨域的方式失效&#xff0c;Spring Security 如何解决跨域的问题。 二.什么是跨域 跨域的概率&#xff1a; 浏览器不能执行其他网站的脚本&#xff0c…

jsp页面调试

现象: 访问jsp页面, 页面为空, 网络请求显示失败, 控制台打印错误net::ERR_INCOMPLETE_CHUNKED_ENCODING 200 分析: 错误描述&#xff1a;编码模块不完整&#xff0c;返回浏览器的流不完整 可能得原因: 1、网络是否稳定 2、服务器端是否有对响应数据做限制&#xff0c;比如…

【App自动化测试】(十七)遍历测试工具——Android Maxim

目录 1. Android Maxim介绍2. Android Maxim使用方法3.Android Maxim运行命令4.Android Maxim的策略5.实例演示——Windows系统&#xff0c;使用AVD模拟器&#xff0c;系统 Android6.0 1. Android Maxim介绍 Android Maxim是基于遍历规则的高性能Android Monkey&#xff0c;适…

基于SpringBoot+Vue的毕业生信息招聘平台设计与实现

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架下…

Elasticsearch常用接口使用说明以及postman调用调试

查看集群状态 接口url&#xff1a;http://xxxx:9200/_cat 查看所有索引 http://xxxx:9200/_cat/indices?v 创建索引 http://xxxx:9200/test-20230526?pretty 返回值 { "acknowledged": true, "shards_acknowledged": true, "index": &quo…

Opencv-C++笔记 (2) : opencv的矩阵操作

文章目录 创建与初始化1.1 数据类型1.2 基本方法1.3 初始化方法 矩阵加减法矩阵乘法矩阵转置矩阵求逆矩阵非零元素个数矩阵均值与标准差矩阵全局极值及位置GEMM 通用矩阵乘法Transform 对数组每一个元素执行矩阵变换MulTransposed 计算数组和数组的转置的乘积Trace 返回矩阵的迹…

WIN10:Cognos10.2_x32安装

一、Cognos BI Server 10.2 32Bit 二、Cognos Transformer 10.2 三、Cognos Framework Manager 10.2 四、环境 1、如果使用Cognos自带的Tomcat web容器&#xff0c;将E:\common\Cognos\c10\webcontent下的所有文件拷贝到E:\common\Cognos\c10\webapps\p2pd 下面.(一般我们就使…

redis高级篇 缓存双写一致性之更新策略

闲聊 缓存通用查询3部曲 redis 中数据&#xff0c;返回redis 中的数据redis 中没有&#xff0c;查询数据库并返回完成第二部的同时&#xff0c;将数据库查询结果写到redis,redis和数据库数据一致. 谈谈双写一致性的理解 1.如果redis 中有数据&#xff1a;需要和数据库中的相…

什么是可视化开发平台?拥有什么优势?

随着科技的进步和发展&#xff0c;可视化开发平台拥有广阔的市场前景&#xff0c;在提升企业办公企业效率、做好数据管理等方面具有自身的特色和优势。在办公自动化发展的年代&#xff0c;低代码开发平台是助力企业实现提质增效办公效率的得力助手&#xff0c;其可视化、易操作…

Windows操作系统存储管理——实存管理和虚存管理

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;今天总结一下Windows操作系统存储管理——实存管理和虚存管理。 存储器管理的对象是主存&#xff08;内存&#xff09;。重点是要知道实存和虚存的管理&#xff0c;而虚存管理重点是逻辑地址和物理地址间的转…

桥梁结构健康监测解决方案

城市桥梁担负着城市的交通和运输网络的重要角色&#xff0c;是城市生命线的重要组成部分。然而&#xff0c;随着时间的推移和日益增长的负荷&#xff0c;桥梁可能会受到各种因素的损害&#xff0c;如自然灾害、疲劳、腐蚀等。因此&#xff0c;桥梁结构健康监测变得至关重要&…

osg给osg::Geometry(自己绘制的几何体)添加纹理(二)

目录 1. 前言 2. 自会集合体贴纹理 2.1. 一张图贴到整个几何体 2.2. 几何体每个面贴不同的图片纹理 3. 说明 1. 前言 前文讲述了如何给osg自带的几何体&#xff0c;如&#xff1a;BOX等&#xff0c;添加纹理&#xff0c;文章参考链接如下&#xff1a; osg给osg::Geometry&…

6步带你弄懂敏捷软件开发管理

敏捷开发是一种项目管理和软件开发的迭代方法&#xff0c;可帮助团队较快地为客户创造价值&#xff0c;同时减少问题。为了获得好处&#xff0c;软件项目团队需要知道如何正确使用敏捷管理方法。 了解敏捷宣言 敏捷宣言阐述了基本的价值观&#xff0c;还详细说明了敏捷团队应…

【云计算与虚拟化】第五章—— vCenter Server 5.5 的高级功能(三)

第五章—— vCenter Server 5.5 的高级功能&#xff08;三&#xff09; 1.使用vsphere client 登陆vcenter服务器,创建一个群集&#xff0c;名称为自己的学号&#xff0c;&#xff08;截图&#xff09; 2.针对该群集打开HA功能&#xff08;截图&#xff09; 3.接入控制策略选择…

【Linux安装】从无到有!在VM虚拟机上安装Linux

系列文章目录 文章目录 系列文章目录准备工作1、Linux阿里云iso镜像: [Centos7.9.2009](http://mirrors.aliyun.com/centos/7.9.2009/isos/x86_64/?spma2c6h.25603864.0.0.5ec0f5adDd8whz) 一、在虚拟机上开辟空间 这里使用VM15做安装例子&#xff0c;16也同样适用1、打开VM 点…

ORB-SLAM内的卡方检验

ORB-SLAM内的卡方检验 1. 概念2. 卡方检验的基本思想3. 卡方检测示例4. ORB-SLAM2中卡方检测剔除外点的策略4.1 示例&#xff0c;卡方检验计算置信度得分: CheckFundamental()、CheckHomography() Reference: 卡方检验(Chi-square test/Chi-Square Goodness-of-Fit Test)卡方检…

chatgpt在复杂问题的回答表现

2023年东南大学论文&#xff1a;Evaluation of ChatGPT as a Question Answering System for Answering Complex Questions 代码库已经无法访问了&#xff1a;https://github.com/tan92hl/Complex-Question-Answering- Evaluation-of-ChatGPT 1.简介 复杂问题的回答&#xff…

ROS学习——在rviz中调用电脑摄像头

一、安装相关软件包 安装uvc camera sudo apt-get install ros-kinetic-uvc-camera安装image相关功能包 sudo apt-get install ros-kinetic-image-* sudo apt-get install ros-kinetic-rqt-image-view 要记得把kinetic换成 你自己的ros版本。 二、启动ros&#xff0c;调用…