Day 03
树状数组
树状数组基本上是线段树的弱化但又快又好写版本。 维护一个比线段树更快的数据结构,支持对序列:
- 单点加
- 查询前缀和
/ 前缀最大值。
我们魔改一下线段树的结构,删掉每个结点的右儿子。
得到的这个结构仍然保留树的形态,不过我们发现每个位置只会成为唯一一个区间的右端点,因此我们实际上只有线性的
因此我们管这种数据结构叫做“树状数组”。
lowbit
观察:如果我们记
利用二进制补码的性质,我们可以得到
我们发现,树状数组的位置i刚好维护了
因此我们只需要一个
二叉搜索树
维护一个数据结构,它是一个集合,支持:
-
加入一个数
(insert) -
删除一个数
(delete) -
查询一个数的排名
(rank ,即比它小的数个数+1) -
查询第
k 小数(kth ,即从小到大排序后第k 个位置上的数) -
查询一个数的前驱
( pre) -
查询一个数的后继
( suc)
你只需要一个平均复杂度
我们依然使用一棵二叉树去维护这个集合。
这棵二叉树需要满足:一个结点的权值,大于它左儿子的权值,小于它右儿子的权值,如果有相同权值,那么在这个结点的
那么这个二叉树看上去就像是把一个排好序的数组“提起来”(换句话说,这个二叉树的中序遍历有序)
那么它为什么叫做“二叉搜索树”呢,因为这棵树可以非常方便地替我们完成“搜素一个值”这个任务。
从树根出发, 每次检查这个值与当前结点的权值的大小关系,如果相等那么找到了,否则如果值比当前结点小,那么向左走,不然向右走。复杂度
接下来我们开始分别实现这
-
插入,从根开始,如果能搜素到这个值那么直接
count[]++ ,不然在搜素到的那个空位置新建一个结点存放这个值。 -
删除,我们最好使用惰性删除,也就是把这个点删了但是这个空点还留在树上。
-
查询一个数的排名,我们在二叉搜索树上查找这个数,如果下一步递归到右儿子,那么将排名增加
sz[ 左儿子]+count[ 当前点] ,最后如果找到了,还需要加上sz[ 左儿子]+1 。 -
查询第
k 小,我们同样在二叉搜索树上从根开始搜素,不过这次变成了:从根开始,如果k <= sz[ 左儿子] ,那么向左儿子递归。如果0<k-sz[ 左儿子]<=count[ 当前点] ,那么当前点就是答案,否则把k 减去sz[ 左儿子]+count[ 当前点] ,然后向右递归。 -
查询前驱,其实就是
kth(rank(v)-1) -
查询后继,其实就是
kth(rank(v+1))
二叉搜索树和线段树
其实二叉搜索树可以看成某种意义上的线段树,这意味着它可以承担一些区间操作的任务,也可以打懒标记。
平衡树
二叉搜索树最坏时间复杂度显然是
因此我们想出了各种奇奇怪怪的方法让二叉搜索树保持平衡,来让它能够真正达到
这里给大家介绍一个非常简单但不是很实用的平衡树:替罪羊树。
它的思路很简单暴力:我们不平衡,那么我们每当整棵树不平衡到一个程度了,就把它整个推平重构成一棵完全二叉树。
这个所谓“不平衡到一个程度”,我们认为:其左或右儿子的大小
因为这个操作支持删除不是很容易,因此我们直接采用懒惰的方法,如果一个结点的
可以通过一些深刻的复杂度证明它的复杂度是
字符串
字符串当然就是由若干字符拼成的串。
关于字符串,我们讨论的其中一个就是字符串的“匹配”问题,也就是说,我给出两个字符串,你怎么知道其中一个串是另一个串的子串?它在多少地方出现了?这也是我们本次课研究的问题。其它的问题,我们会放到今后学习各种子串黑科技
概念:
- 字符集:就是字符串里所有字符构成的集合。
- 子串:就是字符串的一个区间。
- 前缀
/ 后缀:就是字符串一个从1 开始的区间和一个以n 结尾的区间。
哈希
什么是哈希?哈希是一种容易被卡的算法。
哈希是一种不能保证正确性的算法,但是这不代表你使用哈希会被随机扣分。
它的运作思路是:我们把一个字符串不一一对应地对应到一个数上,然后比较这两个数就可以得到这两个字符串的关系了。
那么,怎么对应呢?
字符串哈希
我们使用这样一种哈希方法:
将整个字符串视为一个
譬如,对于字符串
其中
那么,两个相同字符串的哈希值显然是相同的。
更进一步地,我们可以轻松地求出这个字符串所有子串的哈希值。
哈希冲突
一个
不过,一个更保险的方法是使用双值哈希。顾名思义,就是同时用两套
哈希表
维护一个数据结构,是一个集合,支持:
- 插入一个
(key,value) 对 - 删除一个
(key,value) 对 - 查询一个
key 对应的数是什么。
要求总共接近
我们采取一个极端措施:给插入的值对一个
但是因为这个数很小,所以很容易出现哈希冲突,怎么办呢?
答案是把这个
这样就可以做到一个几乎线性的查询复杂度,显著优于同台竞争的线段树和平衡树。
Trie树
我们现在有很多字符串,怎么判断哪些字符串出现过,哪些字符串没有出现过呢?
我们考虑一本字典,如果我们想要在一个字典中找到某个单词,我们的做法是:首先找它的第一个字母,翻到指定页数后开始找第二个字母,如法炮制。
发现这个过程很像一棵树,因此我们可以模仿这个过程建一棵树,我们称为
显而易见地,对于一个小写字母字符集而言,
01trie
不能使用哈希。
KMP算法
这个名字是三个人的首字母。
它涉及到一个比较深刻的东西,叫做
几个
-
s=‘abcabc’,s’ =‘abc’ -
s=‘((())(’,s’=‘(’ -
s=‘aacaaa’,s’=‘a’
| 最长 |
|||||
| 最长 |
那么,找到
我们枚举
所以问题就很显然了。我们下一步并不需要从头开始,而只需要从
欸,还没讲怎么求最长
| 最长 |
||||||
| 最长 |
STL
模板 template
相信大家至少用过std::queue<int>,并都对这个
实际上这个东西是
比如,
你不用管那些吓人的语法,我们将重点落在这里。
这就是我们的那个类型名的位置。
这个
容器 container
容器就是
容器分为序列容器
你可以简单理解为,数组,平衡树,和
它们是一个个的
std:: vector <int> vec;
从左到右分别是命名空间
然后你就获得了一个名叫
迭代器 iterator
迭代器像是
首先,我们造一个
这一行从
那么这个
如果想要访问