当前课程知识点:数据结构(上) > 第一章 绪论(下) > (e)迭代与递归 > 01-E-1: 迭代与递归
同学们好
我们接下来的一个话题是
迭代与递归
在上一节
我们实际上已经围绕
迭代式程序的复杂度分析
介绍了一般性的方法和技巧
概括而言 就是级数
或者说 叫级数求和
这一节 我们将把注意力
转向另一种重要的形式
也就是递归程序
确实 在我们此前学习
程序设计语言的时候
我相信大部分老师
都着重地介绍过这两种形式
并且曾经要求你
尽可能地要学会用递归
这种相对而言
更加高明的方法 来实现算法
没错 递归不仅可以让我们
更敏锐、更准确地抓住问题的要害
更可以给出 从形式上讲
更为简便的一些解决办法
因此呢 它也显得更加高明
但是 正如我们这一节
就要开始感觉到
并且在后面各节里头
我们将越来越多地感觉到的
从效率而言 递归的形式
并不是一个最佳方案
而反过来 貌似复杂
甚至有点笨拙的迭代式算法
往往在实际的运行效率上
反而会体现出更强的优势
因此 如果说
此前你所追求的境界
是要从简单的迭代
慢慢地学会利用递归
那么现在 我们又开始一个
螺旋式地上升过程
我们要慢慢从递归的程序转向
更加高效的一种迭代
这一节开始 我们已经要慢慢地
转向另一个话题 也就是
在已经能够逐步了解和评判
数据结构和算法的性能优劣之后
我们要慢慢地来学会
如何设计一个高效的DSA
那么在这里头呢 我们介绍的是一种
基本而最常用的技巧
也就是分而治之
它的思想就是说
当我遇到一个非常大的问题
比较棘手的时候
我们并不是希望能够一揽子
一蹴而就地把它解决
而是将它分解为
几个相对更小的部分 或者是子问题
递归式地进行求解
所以这种方式
divide and conquer
是我们这一节所要介绍的
一个新的主题
我们先从一个简单的例子开始
这个实例所要求的功能是
当我们任意给定n个整数之后
尽可能快地 把它的组合计算出来
我相信大家在此前
都应该编写过类似的算法
我们这里着重点
不在于这个算法本身
而是在于对它的分析
以及将其中所蕴含的算法策略
抽取出来
并且在此后推而广之
我们来看一下
如果输入是长度为n的
一个整数数组的话
那么为了计算它的总和
我们可以设置一个累加器
初始化为0
接下来做一个循环
每做一次循环 都将相应的一个元素
累加到这个累加器中去
最后将这个累加器返回
我们说它的复杂度是多少呢?
很容易看出来
无论这个数组的内容是多少
只要它的长度是n
无非就是先进行一次O(1)
包括最后返回一次操作 也是O(1)
我们甚至讲过 从渐近的意义上讲
这种没有循环的语句
都是可以直接忽略掉的
算法复杂度的实质部分
来自于中间这个循环
而这个循环
我们也很清楚地可以看到
总共进行了n步
每一步的时间是常数
当然我们也可以用代数的形式
把它规整地写出来
最后的结果 不出我们的意料是O(n)
我们也回来讨论一下
这里所需要的空间是多少呢?
我们说 这里貌似有两种答案
一种答案说 这个里的空间
总共是n个输入
再加上一个累加器
当然 如果要再加个1的话
可能还有一个循环变量
等等等等
总而言之 复杂度是O(n)
还有一种说法呢
它说 空间复杂度
不应该计入这个输入
而只应该计入
这里所借用的一个累加器
包括一个内部循环的一个控制变量
仅此而已
哪种说法是对的呢?
我们说更倾向于后者
此后 如果我们不加专门地说明
凡是谈论到空间复杂度的时候
我们都是在考量
除了输入本身所占的空间之外
我们所需要的另加的
用于计算所必须的
那些空间的总量
所以就这样一个准则而言
刚才这个版本的算法
应该只需要O(2)
也就是常数的空间复杂度
就这个特定的问题而言
这里所给的这段代码
或者叫算法 本身平淡无奇
但是我们说 它的价值在于
其背后蕴含着
某种典型的算法的规律
我们试图来理解一下
比如说 如果把输入参数中的n
看作是这个问题的规模
那么我们会发现
其中最重要的这个循环
每经过一次迭代
有一个数已经统计完毕
而相应的尚未参与统计的元素个数
也就是我们说剩余的
有效的问题的规模
就会相应地递减一个元素
没错 这种通过逐步蚕食
不断削减问题有效规模的策略
就是我们下面要介绍的主角之一
-选课之前
--写在选课之前
--宣传片
-考核方式
--考核方式
-OJ系统说明
--关于OJ
--1-注册与登录
--2-界面与选课
--3-提交测试
-关于课程教材与讲义
--课程教材与讲义
-关于讨论区
--关于讨论区
-微信平台
--html
-PA晋级申请
--PA晋级
-(a)计算
--演示
--(a)计算--作业
-(b)计算模型
-(b)计算模型--作业
-(c)大O记号
-(c)大O记号--作业
-(d)算法分析
-(d)算法分析--作业
-(e)迭代与递归
-(e)迭代与递归--作业
-(xc)动态规划
-- 演示
-(xc)动态规划--作业
-本章测验--作业
-(a)接口与实现
--02A-5 复制
-(a)接口与实现--作业
-(b)可扩充向量
-(b)可扩充向量--作业
-(c)无序向量
--02C-1 概述
--02C-3 插入
--02C-6 查找
--02C-8 遍历
-(c)无序向量--作业
-(d1)有序向量:唯一化
-(d1)有序向量:唯一化--作业
-(d2)有序向量:二分查找
-(d2)有序向量:二分查找--作业
-(d3)有序向量:Fibonacci查找
-(d3)有序向量:Fibonacci查找--作业
-(d4)有序向量:二分查找(改进)
-(d4)有序向量:二分查找(改进)--作业
-(d5)有序向量:插值查找
-第二章 向量(下)--(d5)有序向量:插值查找
-(e)起泡排序
--02E-2 改进
--02E-3 反例
-(e)起泡排序--作业
-(f)归并排序
-(f)归并排序--作业
-本章测验--作业
-(a)接口与实现
--03A-4 实现
-(a)接口与实现--作业
-(b)无序列表
--03B-2 查找
-(b)无序列表--作业
-(c)有序列表
--03C-3 查找
-(c)有序列表--作业
-(d)选择排序
--03D-1 构思
--03D-2 实例
--03D-3 实现
--03D-4 推敲
--03D-6 性能
-(d)选择排序--作业
-(e)插入排序
--03E-1 经验
--03E-2 构思
--03E-3 对比
--03E-4 实例
--03E-5 实现
-(e)插入排序--作业
-(xd)习题辅导:LightHouse
-本章测验--作业
- (a)栈接口与实现
--04A-1 栈
--04A-2 实例
--04A-3 实现
- (a)栈接口与实现--作业
-(c1)栈应用:进制转换
-第四章 栈与队列--(c1)栈应用:进制转换
-(c2)栈应用:括号匹配
-(c2)栈应用:括号匹配--作业
-(c3)栈应用:栈混洗
-第四章 栈与队列--(c3)栈应用:栈混洗
-(c4)栈应用:中缀表达式求值
-(c4)栈应用:中缀表达式求值--作业
-(c5)栈应用:逆波兰表达式
-第四章 栈与队列--(c5)栈应用:逆波兰表达式
-(d)队列接口与实现
--04D-1 接口
--04D-2 实例
--04D-3 实现
-第四章 栈与队列--本章测验
-(a)树
--05A-1 动机
--05A-2 应用
-(a)树--作业
-(b)树的表示
--05B-2 父亲
--05B-3 孩子
-第五章 二叉树--(b)树的表示
-(c)二叉树
-(c)二叉树--作业
-(d)二叉树实现
-(d)二叉树实现--作业
-(e1)先序遍历
-(e1)先序遍历--作业
-(e2)中序遍历
-第五章 二叉树--(e2)中序遍历
-(e4)层次遍历
-第五章 二叉树--(e4)层次遍历
-(e5)重构
-(e5)重构--作业
-本章测验--作业
-(a)概述
-(a)概述--作业
-(b1)邻接矩阵
-(b1)邻接矩阵--作业
-(c)广度优先搜索
--06C-2 策略
--06C-3 实现
--06C-5 实例
-(c)广度优先搜索--作业
-(d)深度优先搜索
--06D-1 算法
--06D-2 框架
--06D-3 细节
-(d)深度优先搜索--作业
-第六章 图--本章测验