数据结构与算法:算法分析

 遇到的问题,都有解决方案,希望我的博客能为您提供一点帮助。

本篇参考《Data Structures and Algorithm Analysis in C++》 

“在程序设计中,不仅要写出能工作的程序,更要关注程序在大数据集上的运行时间。”

  • 本章讨论要点:本篇将探讨:估计程序运行时间、优化运行时间、分析盲目递归后果、讲解求幂和求最大公因数的高效算法。

一、数学基础 

1. 分析原理的数学思想

分析目标:通过比较函数的相对增长率,分析算法的资源消耗(如时间、空间复杂度)。
分析关键点:忽略常数因子和低阶项,关注输入规模 N, N→∞时的主导项。

  • 核心问题:直接比较两个函数在具体点的值(如 f(N)<g(N))没有意义,因为可能存在交叉点。
  • 关键方法:比较函数的渐近增长率​(即当 N→∞ 时的增长速度),忽略常数因子和低阶项。
  • 转折点(Breakpoint)​
    例如,1000N 和 N2 的转折点是 N=1000。当 N>1000 时,N2 的增长速度超过 1000N。

2. 渐进符号的定义

(1) 大O符号(O(f(N)))——上界

  • 定义:若存在正常数 c 和 n0​,使得当 N≥n0​ 时,T(N)≤c⋅f(N),则记 T(N)=O(f(N))。
  • 直观含义:描述函数 T(N) 的增长率不超过 f(N) 的增长率。
  • 例子
    1000N=O(N2),因为当 N≥1000 时,1000N≤1⋅N2。
    尽管 1000N 系数较大,但 N2 的增长率更高,最终会超过 1000N。

(2) Ω符号(Ω(g(N)))——下界

  • 定义:若存在正常数 c 和 n0​,使得当 N≥n0​ 时,T(N)≥c⋅g(N),则记 T(N)=Ω(g(N))。
  • 直观含义:描述函数 T(N) 的增长率不低于g(N) 的增长率。
  • 例子
    N2=Ω(1000N),因为当 N≥1,N2≥1000N 总成立(需适当选择 c 和 n0​)。

(3) Θ符号(Θ(h(N)))——紧确界

  • 定义:T(N)=Θ(h(N)) 当且仅当 T(N)=O(h(N)) 且 T(N)=Ω(h(N))。
  • 直观含义:描述函数 T(N) 的增长率与 h(N) 的增长率相等​(即上下界一致)。
  • 例子3N^{2}+2N+1=\Theta (N^{2})    因为其增长率完全由 N^{2} 主导。

(4) o符号(o(p(N)))——严格上界

  • 定义:若对任意正常数 c,存在 n0​,使得当 N≥n0​ 时,T(N)<c⋅p(N),则记 T(N)=o(p(N))。
  • 直观含义:描述函数 T(N) 的增长率严格小于 p(N) 的增长率。
  • 例子
    2N=o(n^{2}),因为无论 c 多小(如 c=0.1),当 N 足够大时,2N<0.1N^{2}
  • Big-O vs 小o:
    Big-O 允许“等于”,如 N^{2}=o(N^{2})
    小o 严格排除“等于”,如N^{2}=o(N^{3}),但 N2\neq o(N^{2})

3. 作用与用法

(1) 算法效率比较

  • 核心用途:通过渐进符号比较不同算法的增长率,判断哪个更高效。

  • 用 O 描述算法的最坏时间复杂度​(上界),如快速排序的最坏情况为 o(N^{2})
  • 用 Ω 描述算法的最好时间复杂度​(下界),如快速排序的最好情况为 Ω(NlogN)。
  • 用 Θ 描述算法的精确时间复杂度,如归并排序的时间复杂度为 Θ(NlogN)。
  • 例子:O(Nlog⁡N) 的排序算法(如归并排序)比 o(n^{2}) 的算法(如冒泡排序)更适合大规模数据。

(2) 设计优化方向

  • 上界分析(大O):确保算法在最坏情况下仍可接受。

  • 下界分析(Ω):证明问题的固有复杂度(如排序问题的下界为 Ω(NlogN))。

  • 紧确界(Θ):精确描述算法的平均性能。

(3) 实际应用技巧

  • 忽略常数项:1000N和 0.1N均视为 O(N)。

  • 关注最高阶项:对于 5N^{3}+2N^{2}+10,只需关注N^{3}

  • 避免误区:大O表示上界,不一定是精确增长率(Θ才是精确描述)。

 小结

  • 核心思想:通过渐进符号抽象出算法的增长率,指导工程师选择高效算法。

  • 符号关系
    O 是上界,Ω 是下界,Θ 是紧确界,o 是严格上界。

  • 关键原则

    • 小规模数据中常数项可能重要,但大规模数据中增长率主导性能。

    • O 用于最坏情况分析,Ω 用于最优情况,Θ 用于平均情况。

    • 严格区分 O 与 o(如 N=o(Nlog⁡N))。

4. 重要法则与数学工具

​4.1.加法法则

  • 规则:若 T1​(N)=O(f(N)),T2​(N)=O(g(N)),则 T1​(N)+T2​(N)=O(max(f(N),g(N)))。
  • 例子o(N^{2})+o(N)=o(N^{2})(保留最高阶项)。

4.2.​乘法法则

  • 规则:若 T1​(N)=O(f(N)),T2​(N)=O(g(N)),则 T1​(N)*T2​(N)=O(f(N)⋅g(N))。
  • 例子o(N)*o(N)=o(N^{2})

4.3.多项式复杂度规则

  • 规则:若 T(N)是 k 次多项式,则 T(N)=\Theta (N^{k})
    例子3N^{4}+2N^{3}+5N+10=\Theta (N^{4})
    作用:快速判断多项式算法的最高阶项。

4.4.​对数增长规则(底为2)

  • 关键结论:对任意常数 k,log^{k}N=o(N)
  • 意义:对数函数(如 logN、log^{2}N)的增长率远低于线性函数 N,因此含对数的算法复杂度(如 O(NlogN))通常优于纯多项式复杂度(如 o(N^{2}))。

5. 相对增长率的判定方法

lim_{N\rightarrow \infty }\frac{f(N)}{g(N)} 

(1) 极限法

通过计算极限  ​ 判断相对增长率:

  • 极限为0:f(N)=o(g(N))(如 N vs N^{2})。

  • 极限为常数 c≠0:f(N)=Θ(g(N))(如 2N^{2} vsN^{2})。

  • 极限为∞:g(N)=o(f(N))(如 2Nvs N!)。

(2) 洛必达法则
  • 适用条件:当 lim_{N\rightarrow \infty }f(N)AND lim_{N\rightarrow \infty }g(N)均为 ∞。

  • 规则lim_{N\rightarrow \infty }\frac{f(N)}{g(N)}=lim_{N\rightarrow \infty }\frac{f{}'(N)}{g{}'(N))}
    例子:比较 f(N)=log⁡N和 g(N)=N,log(N)/N,导数为 \frac{1}{N} vs 1,极限为0,故 log⁡N=o(N)。

二、计算模型

1、需要分析的问题主要是运行时间

       影响运行时间因素:程序运行时间受编译器、计算机、算法及输入等因素影响,编译器和计算机超出理论模型范畴,重点讨论算法和输入因素 。运行时间函数定义:定义平均运行时间函数 Tavg​(N) 和最坏情况运行时间函数 Tworst​(N) ,且 Tavg​(N)≤Tworst​(N) ,输入多样时函数可能有多个变量 。不同情形性能分析:最好情形性能分析意义不大,平均情形反映典型行为,最坏情形为性能保障 。强调本书分析算法而非程序,程序实现细节一般不影响大 O 结果,低效实现可能导致程序慢 。选择最坏情况分析原因:默认分析最坏情况运行时间,因其为所有输入提供界限,且平均情况界计算困难,“平均” 定义可能影响分析结果 。

 我们来看看

目前不理解没有关系,这个例子的目的是为了展示不同算法运行时间的差异

#include <iostream>
#include <vector>
#include <chrono>
#include <algorithm>
#include <climits>
#include <cstdlib>using namespace std;
using namespace chrono;// 1. 暴力解法 O(n³)
int maxSubArrayBruteForceCubic(vector<int>& nums) {int max_sum = 0;int n = nums.size();for (int i = 0; i < n; ++i) {for (int j = i; j < n; ++j) {int current_sum = 0;for (int k = i; k <= j; ++k) {current_sum += nums[k];}max_sum = max(max_sum, current_sum);}}return max_sum;
}// 2. 优化暴力解法 O(n²)
int maxSubArrayBruteForce(vector<int>& nums) {int max_sum = 0;int n = nums.size();for (int i = 0; i < n; ++i) {int current_sum = 0;for (int j = i; j < n; ++j) {current_sum += nums[j];max_sum = max(max_sum, current_sum);}}return max_sum;
}// 3. 分治法 O(n log n)
int maxCrossingSum(vector<int>& nums, int l, int m, int h) {int sum = 0, left_sum = INT_MIN;for (int i = m; i >= l; --i) {sum += nums[i];left_sum = max(left_sum, sum);}sum = 0;int right_sum = INT_MIN;for (int i = m+1; i <= h; ++i) {sum += nums[i];right_sum = max(right_sum, sum);}return max({left_sum + right_sum, 0});
}int maxSubArrayDivideAndConquerHelper(vector<int>& nums, int l, int h) {if (l == h) return max(0, nums[l]);int m = l + (h - l)/2;return max({maxSubArrayDivideAndConquerHelper(nums, l, m),maxSubArrayDivideAndConquerHelper(nums, m+1, h),maxCrossingSum(nums, l, m, h)});
}int maxSubArrayDivideAndConquer(vector<int>& nums) {if (nums.empty()) return 0;return maxSubArrayDivideAndConquerHelper(nums, 0, nums.size()-1);
}// 4. Kadane算法 O(n)
int maxSubArrayKadane(vector<int>& nums) {int max_current = 0, max_global = 0;for (int num : nums) {max_current = max(num, max_current + num);max_global = max(max_global, max_current);}return max_global;
}// 生成测试数据
vector<int> generateTestData(int size) {vector<int> data;srand(time(nullptr));for (int i = 0; i < size; ++i) {data.push_back(rand() % 200 - 100); // 生成-100到99的随机数}return data;
}int main() {vector<int> test = generateTestData(100); // 生成100个元素的测试数据// 运行测试并计时auto start = high_resolution_clock::now();int result1 = maxSubArrayBruteForceCubic(test);auto end = high_resolution_clock::now();auto time1 = duration_cast<microseconds>(end - start).count();start = high_resolution_clock::now();int result2 = maxSubArrayBruteForce(test);end = high_resolution_clock::now();auto time2 = duration_cast<microseconds>(end - start).count();start = high_resolution_clock::now();int result3 = maxSubArrayDivideAndConquer(test);end = high_resolution_clock::now();auto time3 = duration_cast<microseconds>(end - start).count();start = high_resolution_clock::now();int result4 = maxSubArrayKadane(test);end = high_resolution_clock::now();auto time4 = duration_cast<microseconds>(end - start).count();// 输出结果表格cout << "算法\t\t\t时间复杂度\t运行时间(μs)\t结果" << endl;cout << "暴力解法\t\tO(n³)\t\t" << time1 << "\t\t" << result1 << endl;cout << "优化暴力解法\tO(n²)\t\t" << time2 << "\t\t" << result2 << endl;cout << "分治法\t\t\tO(n log n)\t" << time3 << "\t\t" << result3 << endl;cout << "Kadane算法\t\tO(n)\t\t" << time4 << "\t\t" << result4 << endl;return 0;
}

2、运行时间的计算 

2.1.基本法则


2.1.1.法则1:For循环的时间计算
核心规则
  • 运行时间 = 循环内部语句运行时间 × 迭代次数。
  • 忽略常数项:循环初始化、条件判断等操作的常数时间可忽略(属于低阶项)。
示例代码
for (i = 0; i < n; ++i) {a[i] = 0;  // 单次操作时间为 O(1)
}
  • 分析
    循环内部语句时间为 O(1),迭代次数为 n。
    总时间 = O(1)×n=O(n)。
常见场景
  • 遍历数组、链表等线性结构的时间复杂度通常为 O(n)。

​2.1.2.法则2:嵌套循环的时间计算
核心规则
  • 运行时间 = 最内层语句运行时间 × 所有外层循环次数的乘积。
  • 从内向外分析:逐层计算每层循环的迭代次数,最终相乘。
示例代码
for (i = 0; i < n; ++i) {          // 外层循环:n次for (j = 0; j < n; ++j) {      // 内层循环:n次a[i] += a[j] + i + j;      // 单次操作时间为 O(1)}
}
  • 分析
    最内层语句时间为 O(1),总迭代次数为 n*n=o(n^{2})
    总时间 = o(1)*n^{2}=o(n^{2})
常见场景
  • 双重嵌套循环常见于矩阵操作、暴力搜索等,时间复杂度为 O(n2)。

​2.1.3.法则3:顺序语句的时间计算
核心规则
  • 运行时间 = 各语句运行时间的总和,但最终取最大值(主导项)。
  • 关键原则:仅保留最高阶项,忽略低阶项和常数。
示例代码
// 第一个循环:O(n)
for (i = 0; i < n; ++i) {a[i] = 0;
}// 第二个循环:O(n^2)
for (i = 0; i < n; ++i) {for (j = 0; j < n; ++j) {a[i] += a[j] + i + j;}
}
  • 分析
    第一个循环时间为 O(n),第二个循环时间为 O(n2)。
    总时间 = max(o(n),o(n^{2}))=o(n^{2})(取最大值)。
常见场景
  • 若算法包含多个独立步骤,时间复杂度由最耗时的步骤决定。

​2.1.4.法则4:If/Else语句的时间计算
核心规则
  • 运行时间 = 条件判断时间 + max(S1运行时间, S2运行时间)。
  • 保守估计:无论条件是否满足,取两个分支中时间较长者。
示例代码
if (condition) {        // 条件判断时间为 O(1)// S1:O(n^2)for (i = 0; i < n; ++i) {for (j = 0; j < n; ++j) {// ...}}
} else {// S2:O(n)for (i = 0; i < n; ++i) {// ...}
}
  • 分析
    条件判断时间为 O(1),S1时间为 O(n^2),S2时间为 O(n)。
    总时间 = O(1)+max(O(n^2),O(n))=O(n^2)。
常见场景
  • 条件分支中的时间复杂度由最坏情况分支决定。

2.1.5.综合应用示例
代码片段
void example(int n) {// 步骤1:O(n)for (int i = 0; i < n; ++i) {// ...}// 步骤2:O(n^2)for (int i = 0; i < n; ++i) {for (int j = 0; j < n; ++j) {// ...}}// 步骤3:条件判断 + 分支if (condition) {     // O(1)// S1:O(n)for (int k = 0; k < n; ++k) {// ...}} else {// S2:O(1)// ...}
}
时间复杂度分析
  1. 步骤1:O(n)
  2. 步骤2:O(n^2)
  3. 步骤3:O(1)+max(O(n),O(1))=O(n)
  4. 总时间:O(n)+O(n^2)+O(n)=O(n^2)

 3、最坏情况下分析的局限性

3.​1. 核心问题:高估实际运行时间

  • 现象:最坏情形分析(Worst-Case Analysis)可能给出过于悲观的时间复杂度,导致理论结果远大于实际需求。
  • 原因
    • 最坏情形对应的输入在实际中极少出现​(例如恶意构造的输入)。
    • 算法的平均性能(Average-Case)可能显著优于最坏情形,但平均分析复杂度高尚未解决

3.​2. 解决方向

  • 收紧分析(Tighten the Analysis)​
    通过更精细的观察(如利用输入的特殊性质或算法隐藏的优化逻辑),缩小理论与实际的差距。

    • 示例:快速排序的最坏时间复杂度为 O(N^2),但实际中通过随机化选择枢轴,可达到平均 O(NlogN)。
  • 接受局限性
    若无法改进分析,需明确最坏情形是已知的最佳理论结果,尽管它可能不够精确。

3.​3. 复杂算法的分析挑战

  • 案例1:希尔排序(Shellsort)​

    • 代码量仅约20行,但其时间复杂度分析至今未完全解决
    • 已知某些增量序列的最坏情形为 O(N^3/2),但精确分析仍开放。
  • 案例2:不相交集算法(Union-Find)​

    • 同样简短(约20行代码),但其时间复杂度的严格证明曾耗费数十年,最终借助均摊分析(Amortized Analysis)才得以解决。

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

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

相关文章

Redis数据持久化机制 + Go语言读写Redis各种类型值

Redis&#xff08;Remote Dictionary Server&#xff09;作为高性能的键值存储系统&#xff0c;凭借其丰富的数据类型和原子性操作&#xff0c;成为现代分布式系统中不可或缺的组件。 1、Redis支持的数据类型 Redis支持的数据类型可归纳为以下9类&#xff1a; String&#x…

排序--归并排序

一&#xff0c;引言 归并排序作为七大排序中一种&#xff0c;本文将讲解其排序原理和代码实现。 二&#xff0c;逻辑讲解 来看一组动图&#xff1a; 首先先进行大逻辑的讲解&#xff0c;在一个乱序的数组中如图&#xff1a; 通过递归进行一次次分组如图&#xff1a; 分组逻…

React程序打包与部署

===================== 推荐超级课程: 本地离线DeepSeek AI方案部署实战教程【完全版】Docker快速入门到精通Kubernetes入门到大师通关课AWS云服务快速入门实战目录 为生产环境准备React应用最小化和打包环境变量错误处理部署到托管服务部署到Netlify探索高级主题:Hooks、Su…

Spring Data审计利器:@LastModifiedDate详解(依赖关系补充篇)!!!

&#x1f552; Spring Data审计利器&#xff1a;LastModifiedDate详解&#x1f525;&#xff08;依赖关系补充篇&#xff09; &#x1f50c; 核心依赖解析 使用LastModifiedDate必须知道的依赖关系 #mermaid-svg-qm1OUa9Era9ktbeK {font-family:"trebuchet ms",verd…

接口测试中数据库验证,怎么解决?

在接口测试中&#xff0c;通常需要在接口调用前后查询数据库&#xff0c;以验证接口操作是否正确影响了数据库状态。​这可以通过数据库断言来实现&#xff0c;PyMySQL库常用于连接和操作MySQL数据库。​通过该库&#xff0c;可以在测试中执行SQL语句&#xff0c;查询或修改数据…

游戏引擎学习第189天

今天的回顾与计划 在昨天&#xff0c;我们花了一些时间来优化调试数据的收集方法&#xff0c;并且在调试界面中增加了一些界面代码&#xff0c;使得我们可以悬停在不同的元素上&#xff0c;查看相关信息。今天的任务是对这些数据进行更多的操作&#xff0c;进行一些有趣的实验…

智能粉尘监测解决方案|守护工业安全,杜绝爆炸隐患

在厂房轰鸣的生产线上&#xff0c;一粒微小粉尘的聚集可能成为一场灾难的导火索。如何实现粉尘浓度的精准监控与快速响应&#xff1f;我们为您打造了一套"感知-预警-处置"全闭环的智能安全方案&#xff01; 行业痛点&#xff1a;粉尘管理的生死线 在金属加工、化工…

Java 实现将Word 转换成markdown

日常的开发中&#xff0c;需要将word 等各类文章信息转换成格式化语言&#xff0c;因此需要使用各类语言将word 转换成Markdown 1、引入 jar包 <dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version&g…

Axure设计之中继器表格——拖动行排序教程(中继器)

一、原理介绍 在Axure中实现表格行的拖动排序&#xff0c;主要依赖于中继器的排序事件。然而要实现拖动效果&#xff0c;就必须结合动态面板&#xff0c;因为动态面板可以设置拖动事件&#xff0c;之所以使用动态面板或许是因为它可以更灵活地处理位置变化。用户拖动行时&…

分布式渲染与云渲染:技术与应用的黄金搭档

一、核心概念&#xff1a;先区分再关联 分布式渲染是通过多台设备并行计算拆分渲染任务的技术&#xff08;如将一帧拆分为 64 个小块&#xff0c;64 台电脑同时渲染&#xff09;&#xff1b; 云渲染是基于云计算的渲染服务&#xff0c;本质是分布式渲染的商业化落地—— 用户无…

鼠标在客户区内按下左键和双击右键

书籍&#xff1a;《Visual C 2017从入门到精通》的2.6鼠标 环境&#xff1a;visual studio 2022 内容&#xff1a;【例2.44】鼠标在客户区内按下左键和双击右键 1.创建一个单文档程序 一个简单的单文档程序-CSDN博客https://blog.csdn.net/qq_20725221/article/details/1463…

VMware虚拟机 ubuntu22.04无法与共享粘贴板和拖拽文件的解决方案

VMware虚拟机 ubuntu22.04无法与共享粘贴板和拖拉文件的解决方案 卸载VMware tools安装open-vm-tools还无法拖拽文件 卸载VMware tools 确保卸载完vmware-tools # 进入vmware-tools安装目录/bin sudo vmware-uninstall-tools.pl sudo rm -rf /usr/lib/vmware-tools sudo apt-…

vue3 vue-router 传递路由参数

在 Vue 3 中&#xff0c;使用 vue-router 传递路由参数是非常常见的需求。 1. 使用动态路由参数&#xff08;params&#xff09; 动态路由参数是定义在路由规则中的占位符部分&#xff0c;例如 /user/:id。你可以通过 router.push 或 <router-link> 传递这些参数。 (1…

【Java SE】包装类 Byte、Short、Integer、Long、Character、Float、Double、Boolean

参考笔记&#xff1a;java 包装类 万字详解&#xff08;通俗易懂)_java包装类-CSDN博客 目录 1.简介 2.包装类的继承关系图 3.装箱和拆箱 3.1 介绍 3.2 手动拆装箱 3.3. 自动拆装箱 ​4.关于String类型的转化问题 4.1 String类型和基本类型的相互转化 4.1.1 String —…

【Qt】QByteArray详解

QByteArray 是 Qt 框架中用于处理原始字节数据的核心类&#xff0c;其实质可以概括为以下几点&#xff1a; 1. 底层数据结构 • 连续内存块&#xff1a;存储一段连续的字节数据&#xff08;char*&#xff09;&#xff0c;类似 std::vector<char>&#xff0c;但针对 Qt 框…

Stable Diffusion vue本地api接口对接,模型切换, ai功能集成开源项目 ollama-chat-ui-vue

1.开启Stable Diffusion的api服务 编辑webui-user.bat 添加 –api 开启api服务&#xff0c;然后保存启动就可以了 2.api 文档地址 http://127.0.0.1:7860/docs3. 文生图 接口 地址 /sdapi/v1/txt2img //post 请求入参 {enable_hr: false, // 开启高清hrdenoising_stre…

CentOS 7 部署RuoYi 项目

换源 备份现有的 YUM 源配置文件 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup 默认的 CentOS 官方镜像源替换为阿里云的镜像源&#xff0c;以提高下载速度和稳定性。 curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.co…

从 WPF 到 MAUI:跨平台 UI 开发的进化之路

一、引言 在软件开发领域&#xff0c;用户界面&#xff08;UI&#xff09;开发一直是至关重要的环节。随着技术的不断发展&#xff0c;开发者对于创建跨平台、高性能且美观的 UI 需求日益增长。Windows Presentation Foundation&#xff08;WPF&#xff09;和 .NET Multi - pl…

C++ stack容器总结

stack 基本概念 概念&#xff1a; stack是一种后进先出(Last In First Out, LIFO)的数据结构&#xff0c;它只有一个出口 栈中只有顶端的元素才可以被外界使用&#xff0c;因此栈不允许有遍历行为 栈中进入的数据称为----入栈&#xff08;PUSH&#xff09; 栈中出去的数据成…

【SDMs分析1】基于ENMTools R包的生态位分化分析和图像绘制(identity.test())

基于ENMTools包的生态位分化 1. 写在前面2. 生态位分化检验案例13. 生态位分化检验案例21. 写在前面 最近学了一个新的内容,主要是关于两个物种之间生态位分化检验的 R 语言代码。生态位分化是物种分布模型(SDM )研究中的关键部分,许多 SCI 论文都会涉及这一分析。该方法主…