Presentation is loading. Please wait.

Presentation is loading. Please wait.

数据结构选讲 北京大学 陈瑜希.

Similar presentations


Presentation on theme: "数据结构选讲 北京大学 陈瑜希."— Presentation transcript:

1 数据结构选讲 北京大学 陈瑜希

2 竞赛中会涉及到的基本数据结构 线段树(树状数组) 伸展树 自动机 后缀数组 并查集

3 线段树的定义

4 线段树的定义

5 线段树的构建 function 以节点v为根建树、区间为[l,r] { 对节点v初始化 if (l!=r)
以v的左孩子为根建树、区间为[l,(l+r)/2] 以v的右孩子为根建树、区间为[(l+r)/2+1,r] }

6 线段树的特征 1、线段树的深度不超过logL。 2、线段树把区间上的任意一条线段都分成不超过2logL条 线段。
这些结论为线段树能在O(logL)的时间内完成一条线段的插入、删除、查找等工作,提供了理论依据

7 线段树的基本操作 给定data[0]…data[n-1] 每个指令为下面两种操作中的一种: 给定k,修改data[k]的值
询问一个区间[l,r]里的data[l]…data[r]的最 小值

8 线段树的定义

9 线段树的插入

10 线段树的插入 把data[5]的值修改为10

11 线段树的插入 function 修改以节点v为根的子树、区间为[l,r] { 如果修改点不属于[l,r] 跳出 修改节点v的值
if (l!=r) 以v的左孩子为根建树、区间为[l,(l+r)/2] 以v的右孩子为根建树、区间为[(l+r)/2+1,r] }

12 线段树的查询 查询data[1]…data[7]中的最小值

13 线段树的查询 function 在节点v查询区间[x,y] { if v所表示的区间与[x,y]的交集为空, 跳出
if v所表示的区间完全属于[x,y] 选取v else 在v的左孩子查询[x,y] 在v的右孩子查询[x,y] }

14 例题 (ioi 05 moutain ) 有一个轨道,开始高度都为0 给定一系列指令,每个指令为下面两种 操作中的一种:
改变[a,b]的斜率为d 求最大的k,使得[0,k]中任意高度不超过h

15

16 数据范围 指令数<100000 轨道长度< 斜率<

17 例题 由于操作数范围巨大,我们希望找到一个算法,使得每 一次插入或者查询的时间复杂度都控制在O(logN)以内
题目中涉及到线段的插入,线段最大值的查询,很容易 联想到线段树 每条线段上需要知道的信息:最高点的高度 由于斜率随时改变,如果直接记录某段最高点的高度, 改变了前面的高度,后面的也需要同时改变,这样会给 维护带来很多麻烦。 所以需要记录一些不太容易改变的量。即修改某条线段 不能影响到其它线段的值。

18 例题 我们对每条线段记录这些的信息: 1、这段是否被整段覆盖过,如果是,那么 这段的斜率是多少
2、这段的最高点比该段起点高出多少? (也可以是负的) max 3、这段终点比起点高出多少?height

19 例题 如何维护? function updata(p) {
p->height=p的左孩子->height+p的右孩子->height p->max=max{p的左孩子->max, p的左孩子->height+p的右孩子->max} }

20

21 例题 如何处理巨大的轨道长度? 方法1、离散化 优点:方法常见,实现简单 缺点:需预先知道所有的长度,是离线算 法 方法2、动态申请空间

22 动态申请空间 假设长度为10 开始只有一个根节点[0,9] 插入[1,7]后

23 动态申请空间 方法2、动态申请空间 优点:可以处理无法预先知道所有长度的 题目,是在线算法
缺点:时间与空间复杂度都是O(nlogc) 而 方法一的时间复杂度是O(nlogn) 空间复杂 度为O(n)

24 例题 (poj3739) 给定n1条平行于x轴的直线,n2条平行 于y轴的直线,n3个点(n1,n2,n3<1001) 且坐标范围<1001 统计包含点的正方形个数,要求正方形 的四条边都在给定的直线上

25 例题 题目设置了两个障碍: 目标是统计正方形的个数,不是长方形的 个数 要求正方形中有点 很自然的想法:枚举边长

26 例题 首先枚举边长k 如果知道了正方形的左边界L,也就可以知道 正方形的右边界L+k,那么x坐标在[L,L+k]中的 点在正方形中的x坐标就不影响了,二维问题 转化成了一维问题 现在的问题是:已知一条线段上所有点的位置, 如何快速满足下列条件的线段的个数: 长度为k 包含点 线段端点选自于给定的表中

27 例题 要考虑长度为k的线段条数并不容易 变通:线段中包含点,也可以转化为把点 延长到长度为k,要求线段的一个端点在 这个区间内 问题转化为:
每个点有一个权值(由最初给定的直线决定) 插入(删除)一些线段,线段覆盖了一些点 求被覆盖的点的权值和

28 树状数组 原数组是a,c是a的树状数组

29 可以发现这些规律 C1=a1 C2=a1+a2 C3=a3 C4=a1+a2+a3+a4 C5=a5 …… C8=a1+a2+a3+a4+a5+a6+a7+a8 C2n=a1+a2+….+a2n

30 树状数组 对于序列a,我们设一个数组C C即为a的树状数组 如何求k? C[i] = a[i – 2k + 1] + … + a[i]
k为i在二进制下末尾0的个数 C即为a的树状数组 如何求k?

31 以6为例 (6)10=(0110)2 xor 6-1=(5)10=(0101)2 (0011)2 and (6)10=(0110)2
2k=x &(x^(x-1)) 以6为例 (6)10=(0110)2 xor =(5)10=(0101)2 (0011)2 and (6)10=(0110)2 (0010)2

32 树状数组 void change(int k,int delta); { while (k<=n) c[k]+=delta,k+=lowbit(k); } int getsum(int k) int t=0; while (k>0) t+=c[k],k-=lowbit(k); return t;

33 树状数组 树状数组VS线段树 优点:代码简单、空间时间常数小 缺点:有哪些题线段树可以做,树状数 组不能做的?
树状数组能解决的问题大多为求和问题, 如果已知节点p的值和p的左孩子的值,能 推出p的右孩子的值,大多可以用树状数组 解决

34 伸展树 伸展树(Splay Tree)是二叉查找树的改进, 比一般二叉查找树再加了伸展操作
伸展操作的目的是使二叉查找树尽可能随 机,避免退化 对伸展树的操作的平摊复杂度是O(log2n)。 伸展树的空间要求、编程难度相对于其它 平衡二叉树来说非常低。

35 伸展操作Splay(x,S) 伸展操作Splay(x,S)是在保持伸展树有 序性的前提下,通过一系列旋转操作将 伸展树S中的元素x调整至树的根部的操 作。 在旋转的过程中,要分三种情况分别处 理: 1)Zig 或 Zag 2)Zig-Zig 或 Zag-Zag 3)Zig-Zag 或 Zag-Zig

36 伸展操作Splay(x,S) 情况1 Zig或Zag操作: 节点x的父节点y是根节点。

37 伸展操作Splay(x,S) 情况2 Zig-Zig或Zag-Zag操作:
节点x的父节点y不是根节点,且x与y同时是各 自父节点的左孩子或者同时是各自父节点的右 孩子。

38 伸展操作Splay(x,S) 情况3 Zig-Zag或Zag-Zig操作:
节点x的父节点y不是根节点,x与y中一个是其父 节点的左孩子而另一个是其父节点的右孩子。

39 合并操作 Join(S1,S2):将两个伸展树S1与S2合并。其中 S1的所有元素都小于S2的所有元素。
首先,找到伸展树S1中的最大元素x,再通过 Splay(x,S1)将x调整为S1的根。然后将S2作为x 节点的右子树。这样,就得到了新伸展树S。

40 分离操作 Split(x,S):以x为界,将伸展树S分离为两棵伸 展树S1和S2,其中S1中所有元素都小于x,S2 中的所有元素都大于x。
首先执行Find(x,S),将元素x调整为伸展树的 根节点,则x的左子树就是S1,而右子树为S2。

41 伸展树 伸展树不仅可以处理点的问题,也可以 处理线段的问题 处理点的问题,往往可以用stl里的set、 map解决

42 伸展树 处理线段的问题:在每个点记录以这个点 为根的子树的整体信息(如求最小值时, 就是记这个子树中所有点的最小值)
插入、删除、查找、求值等操作的维护, 都与线段树相近 但更加灵活,因为树的结构也是可变的 可以说,伸展操作是伸展树的精髓

43 例题 给定N个数P1 P2 …PN (N<100001) 第i次操作 对i=1..N依次操作,并对每个i输出相应 的k
选取Pi …PN 中最小的一个数(相同的取位 置靠前的)设为Pk 对Pi …Pk 进行翻转操作,即变为Pk …Pi 对i=1..N依次操作,并对每个i输出相应 的k

44 例题 线段树?树状数组? set?map? 如何处理翻转操作?

45 例题 由于节点会位置会随时变动,无法找到像 线段树那样编写简单而又节约的方法 需要一个用指针或者用数组模拟指针,可 动态申请空间,并记录下
左孩子右孩子的位置(指针) 节点子树中的相对位置n,这个位置存的数 线段中的最小值min 线段被翻转的次数是奇数还是偶数 由于翻转而产生的位移 如果翻转次数为偶数,则节点的的实际位置为n+k,否则为k-n

46 例题 找最小值的位置: function findmin() { p=根节点 while (p不为叶子节点)
if (p->min==p的左孩子>min) p=p的左孩子 else if (p->min==p的右孩子>min) p=p的右孩子 else 返回p; } 返回p; }

47 例题 翻转[x,y] 用分离操作把[1,n]分离成:[1,x-1],[x,y], [y+1,n] 改变[x,y]这段根的标记
用合并操作把三段合并起来

48 伸展树 伸展树VS线段树 缺点:代码麻烦,时间空间常数大 优点:能处理线段树不能处理的问题 (如翻转等)

49

50 二维线段树 很容易有这样的问题,在二维中,线段 树还可用吗?

51 线段树的特征 1、线段树的深度不超过logL。 2、线段树把区间上的任意一条线段都分成不超过2logL条 线段。
这些结论为线段树能在O(logL)的时间内完成一条线段的插入、删除、查找等工作,提供了理论依据 而这种树结构不能保证第2条,所以会退化

52 二维线段树 需要采用树套树,即每个线段树的节点 [l,r]也保存一个线段树,保存横坐标在 [l,r]间的集体情况
所有性质与线段树类似,插入、删除、 查找等时间复杂度为O(log2n)。

53 例题 问题描述: 在一个长为D、宽为S、无限高的3维空间里, 开始丢N个俄罗斯方块(长方体)。 长方体会一直下落到碰到了已有长方体为止。
给定每个长方体的长、宽、高以及长方体的位 置坐标,问所有长方体下落完之后,整个空间 里最高点的高度。(N<= D,S<=1000)

54 例题 这个问题实际上是需要处理这么2个操 作: 询问给定区域里的最大高度 把给定区域全部赋值为某个大于当前高度 的已知高度。

55 例题 不妨先考虑下一维的算法,即需要处理 如下2个操作: 要处理这个问题,线段树的每个节点需 要记录2个值: 询问给定线段里的最大高度
把给定线段全部赋值为某个大于当前高度 的已知高度。 要处理这个问题,线段树的每个节点需 要记录2个值: 当前线段中某个点的最大高度max 整段覆盖当前线段的最大高度cover

56 例题 处理询问操作 function ask(p) { 若当前区间被询问区间整段覆盖,返回p->max 否则若当前区间与询问区间相交,
则返回max{p->cover,ask(p->left),ask(p->right)} }

57 例题 处理插入操作 function insert(p,k) {
若当前区间被插入区间整段覆盖,p->cover>?=k;p->max>?=k 否则若当前区间与插入区间相交, p->max>?=k 插入进当前区间的的左孩子和右孩子 } }

58 例题 缺点: 没有利用到行与行之间的连续性,时间复 杂度O(NSlogD)。会超时

59 例题 因此,考虑二维线段树: 一维中,我们对于每个区间记录2个信息,那么扩 展到二维,就要记录4个信息:
行被整段覆盖,列也被整段覆盖的最大高度cover,cover 行中的某点,列被整段覆盖的最大高度max,cover 行被整段覆盖,列中的某点最大高度cover,max 区域里的某点的最大高度max,max 在处理询问和插入操作时,需要先递归的分行, 再分列,对与行列分别操作,且操作方法和一维 是基本上相同的 时间复杂度为O(NLogslogd)。空间复杂度为(SD)。

60 例题 处理询问操作 function asky(sign=(max/cover),p) {
若当前区间被询问区间整段覆盖,返回p->sign,max 否则若当前区间与询问区间相交, 则返回max{p->sign,cover,ask(sign,p->left),ask(sign,p->right)} } function askx(p) 若当前区间被询问区间整段覆盖,asky(max) 则返回max{asky(cover),ask(p->left),ask(p->right)}

61 例题 处理插入操作 function inserty(sign=(cover/max),p,k) {
若当前区间被插入区间整段覆盖,p->sign,cover>?=k;p->sign,max>?=k 否则若当前区间与插入区间相交, p->sign,max>?=k 插入进当前区间的的左孩子和右孩子 } } function insertx(p,k) 若当前区间被插入区间整段覆盖,inserty(cover,p,k) inserty(max,p,k) inserty(max,p,k)

62 例题 在n×n矩形里,有两种操作 1、同时给一个矩形内所有数增加一个 相同值 2、询问一个矩形内所有数之和

63

64 拓展 三维线段树、四维线段树。。。

65 例题 (uva 3294) 有一些竹子,给定它们的位置,数量L 以及可口度D
熊猫每次吃竹子都要比以前吃的更可口, 并且与上次吃竹子的位置的曼哈顿距离 不能超过这次竹子的数量 问熊猫最多能吃到多少次竹子

66 例题 由于要求以竹子的可口度为序,很容易 联想到动态规划。
先按照可口度排序,然后用f(i)表示熊 猫最后一次吃了第i个竹子的情况下, 最多吃了多少次竹子。则 f(i)=max{f(j)}+1 (j<i且|xi-xj|+|yi-yj|<=Li)。 这样做是O(n2)的。显然会超时。

67 例题 仔细观察这个方程,发现: |xi-xj|+|yi-yj|<=Li这个区域实际上是个正方形
如果我们能利用二维线段树,把每个点的f值插 入到它的坐标上,然后每次只要查询|xi-xj|+|yi- yj|<=Li这个正方形里的最大值,就可以在 O(log2n)的时间内算出每个f了。 不过这个正方形是立着的,为了方便计算,我 们首先把所有坐标都旋转45度,这样这些正方 形就变成了我们经常处理到的躺着的正方体。

68 例题 假设某个点的坐标为(x,y),则旋转后它 的坐标为(x+y,y-x),|xi-xj|+|yi-yj|<=Li 这块区域的坐标就是
以(xj+yj-Li,yj-li-xj)为左上角 以(xj+yj+li,yj+li-xj)为右下角的矩形区域。 这样这个问题就成为了标准线段树问题。

69 例题 如何处理巨大的坐标范围? 方法1、离散化 方法2、动态申请空间
可把坐标范围106降低到105,但是由于是二维的, 所以空间需要1010,依然行不通 方法2、动态申请空间 由于线段树的深度为O(log2N),而每进入一层线段 树都会多需要一个节点,而且这些节点有可能被 公用,也可能单独使用,所以最坏的情况下,会 出来O(Nlog2N)个节点 空间复杂度为O(Nlog2N),遗憾的是,对于 N=100000的情况,还是大了点 之所以会太大,是因为二维线段树的2维分别把一 个节点变成了logN个。如何改变这种浪费呢?

70 例题 处理这个问题,还可以考虑平衡树。使用平衡树并不会 产生多余的节点,每个点放入平衡树中,还有只有1个点。 如果能把第二维的线段树改为平衡树,空间复杂度可以 降低到O(NlogN),这样就可以承受了。 平衡树的一维处理需要记录这样的内容: 当前点的f, 以当前点位根的子树中f的最大值 它的纵坐标 左孩子和右孩子。 平衡树以它的纵坐标为序。至于如何插入和查询,应该 是大家很熟悉的了,这里就不再赘述了。 线段树套平衡树时,只需要把一个点插入到线段树上路 径上每个点的平衡树里。

71 总结 这个问题看似容易,但由于坐标范围巨 大,导致了时间足够但空间不够。从这 里我们发现,单单掌握线段树是不够的, 还需要合理运用各种数据结构,并分析 出它们各自的优势和缺陷,才能做好数 据结构问题。

72 总结 线段树、树状数组、伸展树都是acm程序 设计竞赛中的基本数据结构,它们的基本 操作希望大家能熟练掌握
数据结构题的难点往往不在于实现,而在 于对问题的具体分析,最重要的是要掌握 各种数据结构能解决什么样的题,这样为 进一步分析提供了方向 当然,题目千变万化,还需具体问题具体 分析,不断产生新的思路,这也是竞赛中 最有趣的地方

73


Download ppt "数据结构选讲 北京大学 陈瑜希."

Similar presentations


Ads by Google