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

算法与数据结构精讲:最大子段和(暴力 / 优化 / 分治)+ 线段树从入门到实战

前言

最大子段和是最经典的入门题之一;而线段树则是处理区间查询、区间更新的高级数据结构,是进阶必备。

本文将基于我提供的完整代码,分两大部分精讲:

  1. 最大子段和问题:暴力 O (n³) → 优化 O (n²) → 分治 O (nlogn)

  2. 线段树(Segment Tree):原理 + 构建 + 区间和查询 + 完整代码实现

所有代码均可直接复制运行,复杂度分析、边界处理、核心思路全部讲透。


第一部分:最大子段和问题

一、问题描述

给定一个整数数组(可包含负数),找到连续子数组,使其和最大,返回这个最大和。

数组:{-2, 11, -4, 13, -5, -2} 最大子段:11 + (-4) + 13 = 20 输出:20

二、解法 1:暴力枚举(O (n³))

思路:枚举所有起点 i、终点 j,再循环累加 i~j 的和,记录最大值。

// 暴力破解 O(n³) int MaxSum1(const int* ar, int n) { assert(ar != nullptr); if (n < 1) return 0; int sum = 0; int start = -1; int end = -1; // 枚举起点 i for (int i = 0; i < n; ++i) { // 枚举终点 j for (int j = i; j < n; ++j) { int thissum = 0; // 累加 i~j for (int k = i; k <= j; ++k) { thissum += ar[k]; } // 更新最大值 if (thissum > sum) { sum = thissum; start = i; end = j; } } } return sum; }

复杂度:三层循环

O(n³)缺点:效率极低,数据稍大就会超时。


三、解法 2:优化暴力(O (n²))

思路:去掉最内层循环,在 j 向后移动时直接累加,避免重复计算。

//优化 MaxSum1(O(n²)) int MaxSum2(const int* ar, int n) { assert(ar != nullptr); if (n < 1) return 0; int sum = 0; int start = -1; int end = -1; for (int i = 0; i < n; ++i) { int thissum = 0; // j 向后移动时直接累加 for (int j = i; j < n; ++j) { thissum += ar[j]; if (thissum > sum) { sum = thissum; start = i; end = j; } } } return sum; }

复杂度:两层循环

O(n²)优点:比暴力法快很多,实现简单。


四、解法 3:分治法(O (nlogn))—— 重点推荐

思路(经典分治思想)

  1. 把数组从中间分成左右两部分

  2. 递归求左半边最大子段和

  3. 递归求右半边最大子段和

  4. 计算跨中点的最大子段和(最关键)

  5. 取三者最大值即为答案

// 分治递归函数 int MaxSubSum(const int* ar, int left, int right) { int sum = 0; // 递归出口:只有一个数 if (left == right) { sum = ar[left] > 0 ? ar[left] : 0; } else { // 1. 找中点 int mid = (right - left) / 2 + left; // 2. 递归左右 int leftsum = MaxSubSum(ar, left, mid); int rightsum = MaxSubSum(ar, mid + 1, right); // 3. 求跨中点最大和:左半部分(从mid向左) int s1 = 0, lefts = 0; for (int i = mid; i >= left; --i) { lefts += ar[i]; if (lefts > s1) s1 = lefts; } // 右半部分(从mid+1向右) int s2 = 0, rights = 0; for (int i = mid + 1; i <= right; ++i) { rights += ar[i]; if (rights > s2) s2 = rights; } // 4. 三者取最大 sum = s1 + s2; if (sum < leftsum) sum = leftsum; if (sum < rightsum) sum = rightsum; } return sum; } // 分治法入口 int MaxSum3(const int* ar, int n) { assert(ar != nullptr); if (n < 1) return 0; return MaxSubSum(ar, 0, n - 1); }

复杂度:每次二分 + 线性遍历

O(nlogn)优点:效率高,体现高级算法思想。


五、测试运行

#define ArSize(ar) (sizeof(ar)/sizeof(ar[0])) int main() { int ar[] = { -2,11,-4,13,-5,-2 }; int n = ArSize(ar); int maxval = MaxSum3(ar, n); cout << maxval << endl; // 输出 20 return 0; }

第二部分:线段树(Segment Tree)原理与实现

一、什么是线段树?

线段树是一种二叉树结构,每个节点代表一个区间

核心用途

  • 快速查询区间和 / 区间最值 / 区间 gcd

  • 快速进行区间更新

  • 时间复杂度:构建 O (n),查询 O (logn)


二、线段树核心结构

  • 用数组模拟二叉树

  • 左孩子:2*node+1

  • 右孩子:2*node+2

  • 空间大小:4*n(足够安全)


三、完整代码实现(区间和查询)

1. 构建线段树(build)

递归将区间拆分到叶子节点,自底向上求和。

2. 区间查询(query)

判断当前区间是否在查询范围内,递归合并结果。

#include<vector> #include<iostream> using namespace std; class SegmentTree { private: std::vector<int> tree; // 线段树数组 int n; // 原始数据长度 // 递归构建 void build(const vector<int>& ar, int node, int start, int end) { if (start == end) { tree[node] = ar[start]; return; } int mid = (end - start) / 2 + start; int leftChild = node * 2 + 1; int rightChild = node * 2 + 2; build(ar, leftChild, start, mid); build(ar, rightChild, mid + 1, end); // 父节点 = 左孩子 + 右孩子 tree[node] = tree[leftChild] + tree[rightChild]; } // 区间查询 [left, right] int query(int node, int start, int end, int left, int right) { // 无交集,返回0 if (right < start || left > end) { return 0; } // 完全包含,直接返回节点值 if (left <= start && end <= right) { return tree[node]; } // 部分交集,递归查询左右 int mid = start + (end - start) / 2; int leftChild = 2 * node + 1; int rightChild = 2 * node + 2; int leftsum = query(leftChild, start, mid, left, right); int rightsum = query(rightChild, mid + 1, end, left, right); return leftsum + rightsum; } public: // 构造函数:创建线段树 SegmentTree(const std::vector<int>& ar) { n = ar.size(); if (n < 1) return; tree.resize(4 * n, 0); // 开4倍空间 build(ar, 0, 0, n - 1); } // 对外接口:区间和查询 int rangeQuery(int left, int right) { if (left < 0 || right >= n || left > right) { return -1; } return query(0, 0, n - 1, left, right); } };

四、测试运行

int main() { std::vector<int> ar = { 1,3,5,7,9,11 }; SegmentTree segtree(ar); cout << segtree.rangeQuery(0, 5) << endl; // 1+3+5+7+9+11 = 36 cout << segtree.rangeQuery(0, 2) << endl; // 1+3+5 = 9 cout << segtree.rangeQuery(1, 4) << endl; // 3+5+7+9 = 24 return 0; }

输出结果


高频考点总结

最大子段和

  1. 暴力法:O (n³),简单但低效

  2. 优化暴力:O (n²),去掉重复累加

  3. 分治法:O (nlogn),面试标准解法

  4. 最优解法:动态规划(Kadane 算法)O (n)

线段树

  1. 用于区间查询、区间更新

  2. 空间开4*n

  3. 构建:递归自底向上求和

  4. 查询:判断区间交集,递归合并

  5. 复杂度:查询 O (logn)


http://www.cnnetsun.cn/news/1728894.html

相关文章:

  • 《为什么90%的数字孪生都是假的?》——没有空间数据的“孪生”,只是一个会动的PPT
  • 比亚迪3月销量突破30万辆,获中国新能源车企销量冠军
  • 《智慧仓储(营房)三维空间智能管控系统白皮书(封标级)》——构建“人-车-物”全域可感知、可追溯、可调度的空间智能保障体系
  • **MQTT协议实战:从零搭建轻量级物联网消息中间件系统**在当前万物互联的时代,**MQ
  • OZON平台选品指南:揭秘俄罗斯市场的潜力品牌与爆款趋势
  • Unity 2019.4实战:3D横板格斗游戏BeatEmUp从零开发全流程(附源码)
  • 别再到处找教程了!用PHPStudy Pro一键搞定DVWA靶场(附常见报错解决方案)
  • 228. 汇总区间
  • 别再只懂Diffusion了!Flow Matching如何用更简单的思路搞定生成模型?
  • D3KeyHelper秘籍:暗黑破坏神3智能操作助手的全面解析与实践宝典
  • Nature|把一千个中国人的基因组拼在一起
  • 终极指南:如何将EXE文件转换为DLL的完整教程
  • HAL库串口
  • 泥泞中的 RAG
  • 潘通色和标准色是?
  • 医药专利数据库推荐:聚焦靶点、原研药与核心专利
  • 从代码生成到解释:用 Copilot 逆向学习 Kotlin 的 3 种方法
  • 不止于算个数:手把手教你用C++分析惠斯通电桥实验的测量不确定度
  • 突破文件传输瓶颈:百度网盘秒传技术的深度解析与行业应用
  • MISSA-BP分类预测模型代码功能说明
  • linux——线程相关函数
  • DC-1靶场渗透实战:从信息收集到Linux提权全流程解析
  • 2025届最火的五大降重复率方案实际效果
  • opencv透视变换实战:从算法原理到图像矫正的完整实现
  • NextFaster 电商数据库设计深度解析:从集合到产品的完整架构指南
  • 瑞典隆德大学 AI 模型血检识别 5 种神经疾病
  • ClusterFuzz架构深度解析:可扩展模糊测试平台的10大核心组件揭秘
  • WarcraftHelper:突破魔兽争霸3性能瓶颈的5个实用优化技巧
  • Perplexica数据库选型指南:SQLite与PostgreSQL对比分析
  • MaaFramework多语言绑定技术深度解析:5种主流语言集成方案对比与实践指南