前言
- 以LC 729. 我的日程安排表 I 为例
- 这道题不推荐使用这种解法 非常的慢,而且代码超级容易记错!
- 你没看错 记错!
提示:以下是本篇文章正文内容,下面案例可供参考
一、题目及代码编写
(1)题目简单说一下
(2)字典树编写
- 这里代码非常长 我们分开说 想要更直观学习线段树的朋友可以去看一下前几年黄浩杰大神关于线段树的讲解,用的是C语言很好理解,我只能说大神讲得非常棒!
字典树的编写主要分成四部分,我们现在逐一来说:
- 数据结构的定义
这里推荐用树的结构,这里有一个懒标记的定义,其实我刚开始看到这个懒标记也非常的头疼,找一个视频就会说,你先看一下我其他的基础视频讲解,很无语啊!在这里我提前说一嘴:节点处的懒标记就是更新过程中子树需要完成的数据操作(加减)被预先存放在一起,当查询到该节点的子树时,懒标记下放到子树完成子树的数据更新。
可能,我自己说出这话的时候我都不相信这是我说的,推荐大家去B站搜一搜线段树,现在的大部分讲解在懒标记的概念上解释都非常到位,重点是:耐心一点。
下面是数据结构的定义,注释写得很详细了。
class Node:
def __init__(self):
# 左右子树
self.left = None
self.right = None
# 当前节点的值
self.val = 0
# 更新时,子树的增量标记
self.add = 0
- 字典树的编写 主要有:向下动态开点和懒标记维护、更新操作、查询操作
这里的动态开点,直观理解就行,没那么高大上,就是需要子树了,就给加上的意思,懒标记维护就是:结点往下走的过程中你要一直背着这个操作,画个图说明一下。
class MySegment:
def __init__(self,size):
self.size = size
self.node = Node()
return
# 向下动态开点和懒标记维护
def pushDown(self,node: Node,leftNums: int, rightNums: int):
'''
node 当前节点
leftNums 左子树所含节点个数
rightNums 右子树所含节点个数
'''
# 动态开点
if node.left is None: node.left = Node()
if node.right is None: node.right = Node()
# 如果 增量标记为0 直接返回
if node.add == 0: return
# 如果 增量标记不为0 需要进行累加操作
node.left.val += node.add
node.right.val += node.add
node.left.add += node.add
node.right.add += node.add
# 完成加操作后 记着去掉add标记
node.add = 0
# 更新操作
def update(self,node: Node, start: int,end: int,L: int, R: int,adds: int):
'''
node 当前节点
start 节点所代表区间的左边界
end 节点所代表区间的有边界
L 所修改的区间左边界
R 所修改的区间的右边界
adds 修改区间的增量
'''
if L <= start and end <= R:
# 如果修改的区间 包裹着修改节点所代表的区间 直接对该节点的val和add进行操作
node.val += adds
node.add += adds
print(node.val)
return
mid = (start + end) // 2
# 有关pushDown的建立有点水 后两个数好像用处不是特别大
self.pushDown(node,mid - start + 1,end - mid)
# 进入左子树 对应的查询边界变为 [start,mid]
if (L <= mid): self.update(node.left,start,mid,L,R,adds)
# 进入右子树 对应的查询边界变为 [mid+1,end]
if (R > mid): self.update(node.right,mid+1,end,L,R,adds)
# 记住最后更新节点值
node.val = max(node.left.val, node.right.val)
# 查找操作
def query(self,node: Node,start: int,end: int,L: int, R: int):
'''
node 当前节点
start 节点所代表区间的左边界
end 节点所代表区间的有边界
L 所查询的区间左边界
R 所查询的区间的右边界
'''
# 如果所查询的区间包含了节点所表示区间 直接对该节点的val进行操作
if L <= start and end <= R:
return node.val
# 继续上面的套路 找中点
mid,ans = (start+end) // 2,0
# 这个点找不到 往下压一个点
self.pushDown(node,mid-start+1,end-mid)
# 同样递归判断 该去那个区间找值
if L <= mid: ans = self.query(node.left,start,mid,L,R)
if R > mid: ans = max(ans,self.query(node.right,mid+1,end,L,R))
return ans
(3)完成题目需要的主函数编写
class MyCalendar:
def __init__(self):
self.size = 10 ** 9
self.segTree = MySegment(size = self.size)
def book(self, start: int,end: int) -> bool:
if(self.segTree.query(self.segTree.node,0,self.size,start,end-1) != 0):
return False
else:
self.segTree.update(self.segTree.node,0,self.size,start,end-1,1)
return True
# Your MyCalendar object will be instantiated and called as such:
# obj = MyCalendar()
# param_1 = obj.book(start,end)
二、补充说明
不想在线段树的的具体结构上进行详细叙说,可以直接看一下黄浩杰大神的视频讲解,直观地理解线段树是什么?编写的思路。而后根据上述的python代码进行理解,其实写到后面我感觉这就是一堆公式啊!挺无语的,不过这效率委实不高。
三、总结
本篇blog主要是对python线段树的编写的一个model,因为网上大部分都是java、C语言的,有关python的太少了,几乎没有!其实没有也是有道理的,线段树用python编写太慢了,不过学习一下总是没有坏处的。我看到LC上线段树的五六道题都可以用二分法进行快速简便求解,下面将会针对python的二分函数进行一个汇总。