当前课程知识点:数据结构(上) > 第二章 向量(上) > (c)无序向量 > 02C-7 唯一化
再来考察无序向量的
唯一化问题
也就是说,我们要把
其中重复的元素都剔除掉
使得每一组重复的元素
只保留一个拷贝
这样一个问题
在很多实际的应用中
都能够找到影子
比如说,在网络搜索的环境中
有很多个不同的结点
所分工完成的局部的搜索结果
可能会含有大量的重复的元素
我们需要将其中
重复的元素剔除掉
从而得到一份记忆完整
同时又不冗余的搜索报告
这样一个算法大致可以
通过这样的一个图示
来表示它的原理
也就是说,如果这是一个向量的话
我们总是把它分为三个部分
以当前的这个元素为界
当前这个元素自己是一部分
它的前驱所构成的
那个前缀是一部分
以及对称地,所有的
后继是一部分
每一次我们遇到一个
新的元素
都在它的前缀中去进行查找
我们说过,这是可以
通过find操作来完成的
如果能够找到雷同的元素
比如说,在某个位置上
出现了一个x
就可以把这个元素剔除掉
反之,经过查找以后
如果这个元素没有出现
那么我们就可以
大胆地把它保留下来
同时再去考察它的
下一个元素
这样的一个算法的思路
可以很好地兑现为这段代码
在这里,最主要的是
这样一个循环
控制变量i
实际上对应的
就是我们当前这个元素的秩
初始值是1
也就是说,初始的情况下
是从第一号元素
而不是第零号元素开始的
第一个考察的是它
每一次考察的是什么呢?
就像我们刚才所说的那样
在当前那个向量里
在零到i的这个区间中
这不就是它的前缀吗?
去查找第i个元素
如果查找之后
得到的那个秩是合法的
也就是说,它至少
小于(误)等于它的右边界
那么就可以认为
确实存在这么一个元素
这个时候我们要把
这个元素给删除掉
如果反过来经过查找以后
得到的是一个非法的秩
那么就说明查找失败
换而言之
比如说,这个x就不存在
这个时候我们说
可以坦然地将它保留
并且继续考虑下一个元素
这也就是为什么
我们简明地做一个
i++就可以了
当整个这个过程执行完之后
我们说,就得到了一个
去重之后的向量
这样一个过程
非常的形象,也很好理解
但是严格地说
我们如何来证明
这个算法是正确的呢?
也就是说,我们如何给出
这个算法正确性的
一个严格证明呢?
我们下面就来回答这个问题
这里我们再次以这个问题为例
展示一下如何通过挖掘算法
所具有的不变性和单调性
来证明一个算法最终的正确性
我们先来看这个算法的
不变性
我们会发现,在这个算法
运行的任何一个时刻
如果当前所对应的
是第i个元素V[i]的话
那么在它所对应的那个前缀中
所有的元素必然是彼此互异
也就是说,不包含重复元素
我们可以看到
在算法最初的时候
i 是等于1
所以它的前缀
只有V[0]一个元素
单个元素自然没有办法
构成什么重复的元素
所以这个是显而易见的
那么其余的一般情况下呢
我们可以用数学归纳法
来予以证明
假设当时的状态
是第i个元素e
它的前缀是
从0到i的区间
如果按照数学归纳法
我们假设在此前
确实不变性是成立的话
那么接下来,无非两种情况
也就是当前的这次对应的
查找成功或者失败
如果是失败,也就是说
在它的前缀中不含元素e
我们刚才的算法,给出的处理方法
是直接令i++
也就是无形中
我们已经指向了
它的下一个元素
而将刚才那个元素e
归入了新的这个前缀中
我们可以看到
既然e和此前的那些前缀
是互不重复的
所以将e
归入这样的一个区间以后
这个区间必然
依然是不含重复元素的
反之,如果e
即便出现在它的前缀中
按照刚才的算法流程
我们也会将它剔除掉
也就是通过删除操作
使得后继的元素
整体地向前移动
从而使得原先
它的直接后继
变为当前的这个元素
并且算法继续地运转下去
经过了这样一次迭代之后
当前的这个元素虽然换了
但是它的前缀并没有换
这个前缀所具有的
那样一个性质
也就是元素互异的这个性质
也依然会保持下来
所以我们说,在算法的
每一次迭代过程中
只要前一次迭代是
满足不变性的
那么后一次迭代中
这种不变性
必然会持续地保持下去
那么最终会到什么情况呢?
我们说最终
无非是覆盖整个向量
到那个时候
我们所说的当前的元素
其实就是最末尾的
那个哨兵元素
而它的前缀其实就是
整个这个向量
那么它的前缀中
不包含重复的元素
其实也就相当于
整体的向量中不包含
重复的元素
这正是我们这个算法的功能
唯一化所要求的
所以在最终这个不变性
必然会转化为
我们所需要的正确性
好了,那么我们还差第二步
就是来证明一下
这个算法同时是具有
某种单调性的
从而保证它必然会终止
我们可以看到,这个算法的主体
是由一个while循环构成的
我们说,虽然经过一次
while循环之后
对应的前缀的长度未必会增加
但是这个向量中
有效的元素必然会
单调地下降一个单元
具体来说,也就是
我们还没有处理
留待处理的
当前这个元素的后继
它们的个数必然会
严格单调地下降
直到最终,我们也可以看出
最多经过线性步
最终将至零
也就是所有的元素
都被检查并且处理过了
所以我们可以得出结论
这个算法也必然会终止
而且在终止之前
最多迭代循环O(n)轮
由此我们就很好地确立了
这个算法的正确性
这也是一个相对严格地证明
那么准确地说
刚才这个唯一化算法的
复杂度又是多少呢?
我们可以看到这个算法
确实是由while循环
作为主体构成的
而在while循环中
真正能够造成有效复杂度的
无非是find操作
和remove操作
其中find操作
是对于当前的
元素的整个前缀而言的
而remove操作恰好对称
是相对于当前
这个元素的后继而言的
所以换而言之
每一次while循环所需要的成本
也就是find和remove
两类操作的成本
累计起来也不会超过
整个向量的长度
也就是我们所说的
O(n)线性步
每一次while循环
只需要O(n)的时间
而刚才我们讲过while循环
最多会迭代O(n)
所以这个算法累计起来
最多不超过O(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)深度优先搜索--作业
-第六章 图--本章测验