当前课程知识点:数据结构(上) > 第一章 绪论(下) > (e)迭代与递归 > 01E-8 二分递归:Max2
减而治之和分而治之
都是我们算法设计中的
强有力武器
然而很遗憾
我们前面所举的例子
因为过于简单
这两个方法的优势
并没有得到体现
当然在后续的
章节的学习的过程中
我们可以很清楚地看到这一点
在此呢
我们不妨再举一个例子
来稍微看一下
至少在算法复杂度的常系数方面
在采用了分而治之的策略以后
是有可能有所改进的
我们来看这样一个问题
叫Max2 意思就是说
我们能否在一个
给定的数组区间中
找出最大的两个元素
比如说 我们习惯认为
x1是最大的 x2是次大的
当然在退化的情况下
它们两个可能本身就是相等的
但是一般而言
前者是不会小于后者的
这里呢 采用了
Python的习惯 也就是
我们用lo和hi来表示
这个有效的区间
而且分别用方括号和圆括号
来表示这个区间的
左边界和右边界
究竟是闭的 还是开的
所以准确地讲
这里包含的元素是lo
如果还有足够长的话
以及lo+1 lo+2
一直到最后的一个
并不是hi 而是hi-1
所以 从图示来讲呢
我们一般以后 会画成这样
一个是lo 一直包括后面的
lo+1 lo+2这种彩色的部分
一直到最后这个
你可以认为是hi-1
这个hi呢
并不是我们有效部分
甚至它可能根本就不存在
但是我们在算法设计
和分析的时候呢
不妨把它加在这
后面会看到 这是我们
常用的一种技巧和手段
它可以使得我们思考更加简便
也可以使得我们对程序的理解
更加的准确和精炼
那么这个算法本身并不难
我们这里考量的呢
是要求在整个这个算法过程中
进行元素比较操作的次数
就是比较大小操作的这样的次数
需要尽可能的少
比如说
这就是一个最简明的算法
我们看一下
它大概是由三趟循环构成
第一趟循环
是x1从lo开始 不断地到hi
逐个地扫描
扫描什么呢?
我们通过这样一个比较
每次都对x1
如果有必要做一个更新
使得它总是保存着
或者是指向当前
最大的那个元素
所以经过这样第一个循环之后
我们说x1
必然会在某个位置被发现
比如说在这
接下来呢
后面的两轮循环
实际上是分别在x1的
所有的前驱中
以及它的后缀中进行扫描
从而用相同的策略找出
在除了x1以外的
那些元素中的最大值
当然也就是全局的次大值
我们可以看到这里的比较的方法
都是一样的
这里就不再赘述
我们这里关心的是
它所需要的
刚才我们说的 比较次数
多少次比较呢?
我们可以关注这里头的小于号
这就是比较操作
它们的次数是多少呢?
第一轮循环
总共要比较n-1次
后面两轮的长度 或长或短
但是它们的总数很好判断出来
其实就是在原来的
n-1次比较中
除去了已经被挑选出来的
最大的元素x1
所以应该是n-2
所以我们累计起来
比较操作的总数应该是(2n-3)
而且这样一个算法
无论输入是如何组成的
它都需要做固定次数的循环
确切地讲
总数也是固定的(2n-3)
好情况和最坏情况都是一样的
那么能不能更少呢?
我们来试图
从一个方面进行改进
我们的改进思路
可以用这样一张图来表示
同样是刚才的n个元素
我们可以维护x1和x2两个指针
分别指向当前最大和次大的元素
我们整个的算法过程呢
只是一趟扫描
在经过了初始化之后
在这个扫描过程中
当扫到第i个元素的时候
我们都首先
将它和x2进行比较
如果它比x2大
再进而跟x1做比较
这样的话 我们就有可能会用
这个第i个元素来更新x2
甚至更新x1
在很多情况下
我们希望第i个元素
是小于x2的
在这样的情况下
x1和x2是保持不变的
从而减少一次比较
这样的一个过程
我们可以用
这段代码来具体的实现
我们可以来看一下
第一行 可以认为是一个初始化
因为它首先将x1指向lo
将x2指向lo+1
也就是 最开始的前面两个元素
并且对它们做一次比较判断
如果它们按照刚才的
那个约定是逆序的
我们就将它们交换好了
所以经过这样初始化以后
可以确认x1和x2
确实是当前已经扫描过的范围
也就是前两个元素中的
最大者和次大者
而接下来呢 这样一个循环
确实像刚才所说的那样
会从第三个元素开始 逐一地递增
每当指向一个元素Ai以后
我们都首先将Ai拿出来
和x2做比较
而只有当它胜出了x2之后
我们才会将x2更新为i
并且进而用这个更新的数值去和
最大的那个元素x1做比较
并且在确实有必要的时候
才将二者交换位置
从而实现当前最大元素
和次大元素的更新
同样提醒同学们注意
这里的两个隐藏的配对的
else else语句
它们虽然是看不见的
但是它们的语义意味着什么
希望同学们要能够了解
好了 关于这个算法的正确性
我们就分析到这
接下来 我们最主要的是
要来分析 这样的一个算法的性能
或者复杂度 是否有所改进
我们可以来看一下
同样 如果只关注
比较的次数的话
这个比较是确定的 是一次
可以暂时放在一边
最重要的是
这个循环中间的比较次数
这个时候 循环的长度是n-2
但是每步循环的内部
所需执行的比较次数却是不定的
这里有好和坏两种情况
最好的情况只执行这一次
就通过刚才所说的那个隐含的else
我们可以认为它是一个continue
直接又接着去执行下一次循环去了
而在最坏的情况下呢
有可能执行完了这一次
并且命中了if这个条件
从而进入到下一个if判断
换句话说 在最坏的情况下
我们每次循环
需要进行地不是一次
而是两次比较
所以我们把这个归纳下来
说 在最好的情况下
除了最初的初始化
需要一次比较之外
剩下的(n-2)步
各自只需要一次比较
累计而言 总共不过n-1次比较
我们说这个非常好
比我们刚才的2n-3
有很大的改进
几乎少了一半
但是我们刚才也说到了
在最坏的情况下
每次循环的内部 实际上
需要执行两次比较
我们可以看到
这样一合计以后
结果和刚才是完全一样的
换而言之 就最坏情况而言
这样的一个算法
这样一个结果
并没有实质的改进
那么能否从实质上
对这样的一个算法进行改进
使得它即便在最坏的情况下
也严格地少于2n-3次比较呢
我们说可以
为此我们就需要用到
刚才介绍的分而治之的策略
-选课之前
--写在选课之前
--宣传片
-考核方式
--考核方式
-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)深度优先搜索--作业
-第六章 图--本章测验