以归并排序为基,求证Master公式,分析递归行为、小和问题、逆序对问题

简介: 以归并排序为基,求证Master公式,分析递归行为、小和问题、逆序对问题

用递归方法找一个数组中的最大值

分析

假如需要在数组[6,1,3,5,4]数组中找出其最大值,算法方法为getMax,那么结果res=getMax(6,1,3,5,4)=getMax(getMax(6,1,3),getMax(5,4))=getMax(getMax(6,1,3),getMax(5,4))=getMax(getMax(getMax(6,1),getMax(3,3)),getMax(getMax(5,5),getMax(4,4)))=...,这样就可以用递归去求解。

突破点:用什么标志递归状态?——所求数组范围的leftIndex、rightIndex。

实现

public class GetMax{
    public static int getMax(int[] arr){
        return process(arr, 0, arr.length - 1);
    }
}
// arr[L...R]范围上求最大值
public static int process(int[] arr, int L, int R){
    if(L==R){
        return arr[L];  // arr[L...R]范围上只有一个数,直接返回
    }
    int mid = L+(R-1) >>1;  // 中位
    int leftMax = process(arr, L, mid);
    int rightMax = process(arr, mid+1, R);
    return Math.max(leftMax, rightMax);
}
复制代码

求中点:mid = (L+R)/2,为了防止溢出,mid = L+(R-L)/2=L+(R-L)>>1 (右移比除以2更快)

Master公式

递归是非常常见的一种算法,由于递归相比顺序执行或循环程序,时间复杂度难以计算,而master公式就是用于计算递归程序的时间复杂度。

T(N)=a∗T(N/b)+O(Nd)T(N)=a*T(N/b)+O(N^d)T(N)=aT(N/b)+O(Nd)

  • T(N)T(N)T(N)表示母问题的数据量
  • T(N/b)T(N/b)T(N/b)表示子过程每次递归的数据量
  • a表示子过程被调用的次数
  • O(Nd)O(N^d)O(Nd)表示除去调用之外的剩余过程

满足如上公式的程序都可以根据master公式计算时间复杂度:

  • log⁡ba>d\log_b{a} > dlogba>d :时间复杂度为O(Nlog⁡ba)O(N^{\log_b{a}})O(Nlogba)
  • log⁡ba=d\log_b{a} = dlogba=d :时间复杂度为O(Nd∗logN)O(N^d * {logN})O(NdlogN)
  • log⁡ba<d\log_b{a} < dlogba<d :时间复杂度为O(Nd)O(N^d)O(Nd)

“用递归方法找一个数组中的最大值”的算法是否符合Master公式?

在上述的问题中,将数组分为左右两部分,每部分的计算量为N/2,这个子过程调用了2次,除去调用之外的剩余过程(即比较两部分的最大值)时间复杂度O(1),也就是说上述的递归算法的master公式为T(N)=2∗T(N/2)+O(1)T(N)=2*T(N/2)+ O(1)T(N)=2T(N/2)+O(1)

归并排序是否符合Master公式?

在文章算法复杂度与简单排序算法中给出了归并排序的算法。下图给出了具体分析:

网络异常,图片无法展示
|

因此是满足master公式的,为T(N)=2∗T(N/2)+O(N)T(N)=2*T(N/2)+ O(N)T(N)=2T(N/2)+O(N),即时间复杂度为O(N∗logN)O(N * {logN})O(NlogN)。 因为只需要一个长度为N的临时数组,空间复杂度为O(N)O(N)O(N)

小和问题

小和问题和逆序对问题是归并排序的算法的延伸应用。

小和问题:在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和。

例子:[1,3,4,2,5]中,1左边没有比1小的数;3左边有比3小的数:1;4左边比它小的数:1、3;2左边比它小的数:1;5左边比它小的数:1、3、4、2,因此小和为1+1+3+1+1+3+4+2=161+1+3+1+1+3+4+2=161+1+3+1+1+3+4+2=16

换下思路等效,也就是求每一个数右边比当前数大的个数,(个数 * 当前数) 的累加和就是结果:

在[1,3,4,2,5]中,1右边比它大的数有4个,产生4个1的小和;3右边比它大的数有2个,产生2个3的小和;4右边比它大的数有1个,产生1个4的小和;2右边比它大的数有1个,产生1个2的小和;5右边没有比它大的数。因此小和为4∗1+2∗3+1∗4+1∗2=164*1+2*3+1*4+1*2=1641+23+14+12=16

第二种思路,可以轻松的用归并排序得出。下面给出具体分析步骤:

  • step1:分——将数组分为左右两个部分,再递归的对子数组进行“分”操作,直到分成一个个单独的数。

网络异常,图片无法展示
|

  • step2:合并(1,3),左指针指向1,右指针指向3,当左侧指向值<右侧指向值时产生小和(1个1);合并(1,3,4)时,左指针指向1,右指针指向4,产生1个1,左指针右滑到3,右指针还是指向4,产生小和(1个3)

网络异常,图片无法展示
|

  • step3:合并(1,3,4)时,左指针指向1,右指针指向4,产生1个1,左指针右滑到3,右指针还是指向4,产生小和(1个3)

网络异常,图片无法展示
|

  • step4:合并(2,5),产生小和(1个2)
    网络异常,图片无法展示
    |

  • step5:合并(134,25),产生小和(2个1,1个3,1个4)

网络异常,图片无法展示
|

小和问题与归并排序

在上述小和的分析过程中,我们是手动计算右边比当前数大的数量,对于算法来说,我们就需要对数组进行排序。那小和问题与归并排序有什么关系呢?先来复习下归并排序的思路。

归并排序思路

总体思路:

  • 分:把数组分为两个部分,再递归的对子数组进行“分”操作,直到分成一个个单独的数
  • 合:把两个数合并为有序数组,再对有序数组进行合并,直到全部子数组合并为一个完整数组 合并的思路:
  • 新建一个空数组res,用于存放最终排序后的数组
  • 比较两个有序数组的头部,较小者出队并推入res中
  • 如果有两个数组还有值,重复上一步骤

不同地方

小和问题中的merge过程,如果左右相等,必须要先push右边数组的数。其它跟归并排序无差别。

public static int process(int[] arr, int l, int r){
    if(l==r){
        retun 0;
    }
    int mid = l+((r-l)>>1);
    return process(arr,l,mid)+process(arr,mid+1,r)+merge(arr,l,mid,r)
}
public static int merge(int[] arr, int l, int m, int r){
    int[] help = new int[r-l+1];
    int i=0;
    int p1=l;
    int p2=m+1;
    int res=0;
    while(p1<=m &&p2<=r){
        res+=arr[p1] <arr[p2]?(r-p2+1)*arr[p1]:0;
        help[i++]=arr[p1]<arr[p2]?arr[p1++]:arr[p2++];
    }
    while(p1<=m){
        help[i++]=arr[p1++];
    }
    while(p2<=r){
        help[i++]=arr[p2++];
    }
    for(i=0;i<help.length;i++){
        arr[l+i]=help[i]
    }
    return res;
}
复制代码

逆序对问题

在一个数组中,左边的数如果比右边的数大,则这两个数构成一个逆序对,请打印所有逆序对。 分析过程跟小和问题大同小异,这里暂时不详述了,详情可以点击下方链接看左神视频。

相关链接:

七天刷爆LeetCode,顶级算法大神



相关文章
|
22天前
|
存储 人工智能 监控
Coze 智能体开发标准流程
在Coze平台开发AI智能体分四步:创建(手动或AI生成)、编排(人设/插件/工作流/知识库)、调试(多轮测试+节点监控)、发布(多渠道+API)。国内版用豆包模型,国际版支持GPT-4o/Claude。结构化Prompt与工作流是提效关键。(239字)
|
1月前
|
运维 监控 安全
【OpenClaw保姆级 AI 运维实战教程】:部署+百炼API配置+Agent Dashboard实时监控面板+实现全平台运维及避坑指南
“OpenClaw后台运行时,到底在执行什么任务?哪个Agent把Token额度耗光了?会话是不是卡在某个环节?”——这是所有OpenClaw用户的共同痛点。即便官方提供了默认Gateway控制台(127.0.0.1:18789),但该界面更侧重“网关控制”,缺乏Agent运行态的可视化监控,用户只能对着日志文件反复grep,陷入“运维返祖现场”。
1988 3
|
22天前
|
运维 安全 定位技术
设备代码钓鱼攻击激增背景下 OAuth 授权安全与防御体系研究
2026年,设备代码钓鱼攻击爆发式增长(年增37.5倍),攻击者滥用OAuth 2.0设备授权流程,诱导用户在微软、谷歌等官方登录页输入设备码,绕过MFA窃取长期有效令牌。本文剖析攻击链路与EvilTokens等PhaaS套件,提出覆盖协议管控、身份治理、令牌生命周期的零信任纵深防御体系。(239字)
131 1
|
16天前
|
存储 人工智能 JSON
AI 成为主流负载后,数据基础设施将如何演进?|Apache Doris 2026 Roadmap
Scale Intelligence, Accelerate Insight,不仅是年度主题,也定义了 Doris 在 AI 时代的演进方向。
152 0
|
监控 安全 Unix
技术笔记:libev学习(一)
技术笔记:libev学习(一)
466 0
|
人工智能 自然语言处理 搜索推荐
|
缓存 Android开发 开发者
安卓系统优化:提升手机性能的秘诀
【5月更文挑战第31天】本文将探讨如何通过一系列简单的步骤和技巧,对安卓系统进行优化,以提升手机的性能。我们将从清理无用文件、管理后台应用、调整系统设置等方面入手,帮助你的安卓设备运行更加流畅。
|
JavaScript 前端开发
除了 `addEventListener` 方法,还有哪些方式可以为元素添加事件监听器?
【10月更文挑战第29天】虽然存在多种为元素添加事件监听器的方式,但 `addEventListener` 方法因其具有更好的兼容性、灵活性和可维护性,成为了现代JavaScript开发中添加事件监听器的首选方式。在实际项目中,应尽量使用 `addEventListener` 来实现事件绑定,以提高代码的质量和可维护性,并确保在不同浏览器中的一致性表现。
|
缓存 JavaScript Oracle
Node.js版本管理工具之NVM
Node.js版本管理工具之NVM
|
Go 定位技术 索引
Go 语言Map(集合) | 19
Go 语言Map(集合) | 19