算法导论第四章分治策略实例解析(一)

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 一、第三章简单回顾    中间略过了第三章, 第三章主要是介绍如何从数学层面上科学地定义算法复杂度,以致于能够以一套公有的标准来分析算法。其中,我认为只要记住三个符号就可以了,其他的就看个人情况,除非你需要对一个算法剖根问底,不然还真用不到,我们只需有个印象,知道这玩意是用来分析算法性能的。

一、第三章简单回顾 

  中间略过了第三章, 第三章主要是介绍如何从数学层面上科学地定义算法复杂度,以致于能够以一套公有的标准来分析算法。其中,我认为只要记住三个符号就可以了,其他的就看个人情况,除非你需要对一个算法剖根问底,不然还真用不到,我们只需有个印象,知道这玩意是用来分析算法性能的。三个量分别是:确定一个函数渐近上界的Ο符号,渐近下届Ω符号,以及渐近紧确界Θ符号,这是在分析一个算法的界限时常用的分析方法,具体的就详看书本了,对于我们更多关注上层算法的表达来说,这些显得不是那么重要,我的理解是Ο可以简单看成最坏运行时间,Ω是最好运行时间,Θ是平均运行时间。一般我们在写一个算法的运行时间时,大多是以Θ符号来表示。参考下面这幅经典的图:

二、第四章两大板块

  第四章讲递归,也是数学的东西太多了,我准备这样来组织这章的结构:先用一个例子(最大子数组和)来讲解用到递归的一个经典方法——分治法,然后在引入如何解递归式,即引入解递归式的三种方法

1、由分治法引发的

 这一章提出了一个在现在各大IT公司在今天依然很喜欢考的一道笔试面试题:

求连续子数组的最大和
题目描述:
输入一个整形数组,数组里有正数也有负数。
数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。
求所有子数组的和的最大值。要求时间复杂度为O(n)。

例如输入的数组为1, -2, 3, 10, -4, 7, 2, -5,和最大的子数组为3, 10, -4, 7, 2,
因此输出为该子数组的和18。

要求时间复杂度是O(n),我们暂且不管这个,由浅入深地分析一下这道题,时间复杂度从O(n^2)->O(nlgn)->O(n)。

1)、第一,大部分人想到的肯定是暴力法,两个for循环,时间复杂度自然是O(n^2),如下:

 1 /************************************************************************/
 2 /*  暴力法                                                       
 3 /************************************************************************/
 4 void MaxSubArraySum_Force(int arr[], vector<int> &subarr, int len)
 5 {
 6     if (len == 0)
 7         return;
 8     int nMax = INT_MIN;
 9     int low = 0, high = 0;
10     for (int i = 0; i < len; i ++) {
11         int nSum = 0;
12         for (int j = i; j < len; j ++) {
13             nSum += arr[j];
14             if (nSum > nMax) {
15                 nMax = nSum;
16                 low = i;
17                 high = j;
18             }
19         }
20     }
21     for (int i = low; i <= high; i ++) {
22         subarr.push_back(arr[i]);
23     }
24 }

 

2)、第二,看了《算法导论》,你可能会想到分治法,看完之后你肯定会为该分治思想而惊叹,尤其是“横跨中点”的计算思想。简单说下该分治思想,其实很简单,最大和子数组无非有三种情况:左边,右边,中间。

时间复杂度分析:

  根据分治的思想,时间复杂度的计算包括三部分:两边+中间。由于分治的依托就是递归,我们可以写出下面的递推式(和合并排序的递推式是一样的):

其中的Θ(n)为处理最大和在数组中间时的情况,经过计算(怎么计算的,请看本节第二部分:解分治法的三种方法,可以得到分治法的时间复杂度为Θ(nlgn)。代码如下:

 1 /************************************************************************/
 2 /*    分治法
 3     最大和子数组有三种情况:
 4     1)A[1...mid]
 5     2)A[mid+1...N]
 6     3)A[i..mid..j]
 7 /************************************************************************/
 8 //find max crossing left and right
 9 int Find_Max_Crossing_Subarray(int arr[], int low, int mid, int high)
10 {
11     const int infinite = -9999;
12     int left_sum = infinite;
13     int right_sum = infinite;
14 
15     int max_left = -1, max_right = -1;
16 
17     int sum = 0; //from mid to left;
18     for (int i = mid; i >= low; i --) {
19         sum += arr[i];
20         if (sum > left_sum) {
21             left_sum = sum;
22             max_left = i;        
23         }
24     }
25     sum = 0;  //from mid to right
26     for (int j = mid + 1; j <= high; j ++) {
27         sum += arr[j];
28         if (sum > right_sum) {
29             right_sum = sum;
30             max_right = j;
31         }
32     }
33     return (left_sum + right_sum);
34 }
35 
36 int Find_Maximum_Subarray(int arr[], int low, int high)
37 {
38     if (high == low) //only one element;
39         return arr[low];
40     else {
41         int mid = (low + high)/2;
42         int leftSum = Find_Maximum_Subarray(arr, low, mid);
43         int rightSum = Find_Maximum_Subarray(arr, mid+1, high);
44         int crossSum = Find_Max_Crossing_Subarray(arr, low, mid, high);
45 
46         if (leftSum >= rightSum && leftSum >= crossSum)
47             return leftSum;
48         else if (rightSum >= leftSum && rightSum >= crossSum)
49             return rightSum;
50         else 
51             return crossSum;
52     }
53 }

 

3)、第三,看了《算法导论》习题4.1-5,你又有了另外一种思路:数组A[1...j+1]的最大和子数组,有两种情况:a) A[1...j]的最大和子数组; b) 某个A[i...j+1]的最大和子数组,假设你现在不知道动态规划,这种方法也许会让你眼前一亮,确实是这么回事,恩,看代码吧。时间复杂度不用想,肯定是O(n)。和暴力法比起来,我们的改动仅仅是用一个指针指向某个使和小于零的子数组的左区间(当和小于零时,区间向左减小,当和在增加时,区间向右增大)。因此,我们给这种方法取个名字叫区间法。

 1 /************************************************************************/
 2 /*    区间法
 3     求A[1...j+1]的最大和子数组,有两种情况:
 4     1)A[1...j]的最大和子数组
 5     2)某个A[i...j+1]的最大和子数组                                                      
 6 /************************************************************************/
 7 void MaxSubArraySum_Greedy(int arr[], vector<int> &subarr, int len)
 8 {
 9     if (len == 0)
10         return;
11     int nMax = INT_MIN;
12     int low = 0, high = 0;
13     int cur = 0; //一个指针更新子数组的左区间
14     int nSum = 0;
15     for (int i = 0; i < len; i ++) {
16         nSum += arr[i];
17         if (nSum > nMax) {
18             nMax = nSum;
19             low = cur;
20             high = i;
21         }
22         if (nSum < 0) {
23             cur += 1;
24             nSum = 0;
25         }
26     }
27     for (int i = low; i <= high; i ++)
28         subarr.push_back(arr[i]);
29 }


第四,你可能在平常的学习过程中,听说过该问题最经典的解是用动态规划来解,等你学习之后,你发现确实是这样,然后你又一次为之惊叹。动态规划算法最主要的是寻找递推关系式,大概思想是这样的:数组A[1...j+1]的最大和:要么是A[1...j]+A[j+1]的最大和,要么是A[j+1],据此,可以很容易写出其递推式为:

sum[i+1] = Max(sum[i] + A[i+1], A[i+1])

化简之后,其实就是比较sum[i] ?> 0(sum[i] + A[i+1] ?> A[i+1]),由此,就很容易写出代码如下:

 1 /************************************************************************/
 2 /*  动态规划(对应着上面的贪心法看,略有不同)
 3     求A[1...j+1]的最大和子数组,有两种情况:
 4         1)A[1...j]+A[j+1]的最大和子数组
 5         2)A[j+1]
 6     dp递推式:
 7         sum[j+1] = max(sum[j] + A[j+1], A[j+1])
 8 /************************************************************************/
 9 int MaxSubArraySum_dp(int arr[], int len)
10 {
11     if (len <= 0)
12         exit(-1);
13     int nMax = INT_MIN;
14     int sum = 0;
15     
16     for (int i = 0; i < len; i ++) {
17         if (sum >= 0) 
18             sum += arr[i];
19         else
20             sum = arr[i];
21         if (sum > nMax)
22             nMax = sum;
23     }
24     return nMax;
25 }

  可以看出,区间法动态规划有几分相似,我觉得两种方法的出发点和终点都是一致的,只不过过程不同。动态规划严格遵循递推式,而区间法是寻找使区间变化的标识,即和是否小于零,而这个标识正是动态规划采用的。

  由于光这一部分就已经写得足够长了,为了方便阅读,所以本节第二部分:解递归式的三种方法 转 算法导论第四章编程实践(二)。

目录
相关文章
|
10天前
|
算法 前端开发 数据处理
小白学python-深入解析一位字符判定算法
小白学python-深入解析一位字符判定算法
26 0
|
3天前
|
存储 算法 Java
解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用
在Java中,Set接口以其独特的“无重复”特性脱颖而出。本文通过解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用。
15 3
|
5天前
|
搜索推荐 算法
插入排序算法的平均时间复杂度解析
【10月更文挑战第12天】 插入排序是一种简单直观的排序算法,通过不断将未排序元素插入到已排序部分的合适位置来完成排序。其平均时间复杂度为$O(n^2)$,适用于小规模或部分有序的数据。尽管效率不高,但在特定场景下仍具优势。
|
7天前
|
机器学习/深度学习 人工智能 算法
揭开深度学习与传统机器学习的神秘面纱:从理论差异到实战代码详解两者间的选择与应用策略全面解析
【10月更文挑战第10天】本文探讨了深度学习与传统机器学习的区别,通过图像识别和语音处理等领域的应用案例,展示了深度学习在自动特征学习和处理大规模数据方面的优势。文中还提供了一个Python代码示例,使用TensorFlow构建多层感知器(MLP)并与Scikit-learn中的逻辑回归模型进行对比,进一步说明了两者的不同特点。
26 2
|
9天前
|
存储 缓存 监控
深入解析:Elasticsearch集群性能调优策略与最佳实践
【10月更文挑战第8天】Elasticsearch 是一个分布式的、基于 RESTful 风格的搜索和数据分析引擎,它能够快速地存储、搜索和分析大量数据。随着企业对实时数据处理需求的增长,Elasticsearch 被广泛应用于日志分析、全文搜索、安全信息和事件管理(SIEM)等领域。然而,为了确保 Elasticsearch 集群能够高效运行并满足业务需求,需要进行一系列的性能调优工作。
27 3
|
5天前
|
缓存 前端开发 安全
前端开发者必备:HTTP状态码含义与用途解析,常见错误码产生原因及解决策略
前端开发者必备:HTTP状态码含义与用途解析,常见错误码产生原因及解决策略
32 0
|
5天前
|
前端开发 算法 JavaScript
无界SaaS模式深度解析:算力算法、链接力、数据确权制度
私域电商的无界SaaS模式涉及后端开发、前端开发、数据库设计、API接口、区块链技术、支付和身份验证系统等多个技术领域。本文通过简化框架和示例代码,指导如何将核心功能转化为技术实现,涵盖用户管理、企业店铺管理、数据流量管理等关键环节。
|
8天前
|
存储 缓存 数据处理
深度解析:Hologres分布式存储引擎设计原理及其优化策略
【10月更文挑战第9天】在大数据时代,数据的规模和复杂性不断增加,这对数据库系统提出了更高的要求。传统的单机数据库难以应对海量数据处理的需求,而分布式数据库通过水平扩展提供了更好的解决方案。阿里云推出的Hologres是一个实时交互式分析服务,它结合了OLAP(在线分析处理)与OLTP(在线事务处理)的优势,能够在大规模数据集上提供低延迟的数据查询能力。本文将深入探讨Hologres分布式存储引擎的设计原理,并介绍一些关键的优化策略。
37 0
|
17天前
|
机器学习/深度学习 算法 数据安全/隐私保护
基于MSER和HOG特征提取的SVM交通标志检测和识别算法matlab仿真
### 算法简介 1. **算法运行效果图预览**:展示算法效果,完整程序运行后无水印。 2. **算法运行软件版本**:Matlab 2017b。 3. **部分核心程序**:完整版代码包含中文注释及操作步骤视频。 4. **算法理论概述**: - **MSER**:用于检测显著区域,提取图像中稳定区域,适用于光照变化下的交通标志检测。 - **HOG特征提取**:通过计算图像小区域的梯度直方图捕捉局部纹理信息,用于物体检测。 - **SVM**:寻找最大化间隔的超平面以分类样本。 整个算法流程图见下图。
|
2天前
|
存储
基于遗传算法的智能天线最佳阵列因子计算matlab仿真
本课题探讨基于遗传算法优化智能天线阵列因子,以提升无线通信系统性能,包括信号质量、干扰抑制及定位精度。通过MATLAB2022a实现的核心程序,展示了遗传算法在寻找最优阵列因子上的应用,显著改善了天线接收功率。

推荐镜像

更多