python线段树编写,很冗长,不过不难理解。


前言

  • 以LC 729. 我的日程安排表 I 为例
  • 这道题不推荐使用这种解法 非常的慢,而且代码超级容易记错!
  • 你没看错 记错!

提示:以下是本篇文章正文内容,下面案例可供参考

一、题目及代码编写

(1)题目简单说一下
在这里插入图片描述
在这里插入图片描述
(2)字典树编写

  • 这里代码非常长 我们分开说 想要更直观学习线段树的朋友可以去看一下前几年黄浩杰大神关于线段树的讲解,用的是C语言很好理解,我只能说大神讲得非常棒!
    字典树的编写主要分成四部分,我们现在逐一来说:
  1. 数据结构的定义
      这里推荐用树的结构,这里有一个懒标记的定义,其实我刚开始看到这个懒标记也非常的头疼,找一个视频就会说,你先看一下我其他的基础视频讲解,很无语啊!在这里我提前说一嘴:节点处的懒标记就是更新过程中子树需要完成的数据操作(加减)被预先存放在一起,当查询到该节点的子树时,懒标记下放到子树完成子树的数据更新。
      可能,我自己说出这话的时候我都不相信这是我说的,推荐大家去B站搜一搜线段树,现在的大部分讲解在懒标记的概念上解释都非常到位,重点是:耐心一点。
      下面是数据结构的定义,注释写得很详细了。
class Node:
    def __init__(self):
        # 左右子树
        self.left = None
        self.right = None
        # 当前节点的值
        self.val = 0
        # 更新时,子树的增量标记
        self.add = 0
  
  1. 字典树的编写 主要有:向下动态开点和懒标记维护、更新操作、查询操作
      这里的动态开点,直观理解就行,没那么高大上,就是需要子树了,就给加上的意思,懒标记维护就是:结点往下走的过程中你要一直背着这个操作,画个图说明一下。
    在这里插入图片描述
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的二分函数进行一个汇总。

算法有兴趣的可以来看看 在自然数,且所有的数不大于30000的范围内讨论一个问题:现在已知n条线段,把端点依次输入告诉你,然后有m个询问,每个询问输入一个点,要求这个点在多少条线段上出现过; 最基本的解法当然就是读一个点,就把所有线段比一下,看看在不在线段中; 每次询问都要把n条线段查一次,那么m次询问,就要运算m*n次,复杂度就是O(m*n) 这道题m和n都是30000,那么计算量达到了10^9;而计算机1秒的计算量大约是10^8的数量级,所以这种方法无论怎么优化都是超时 因为n条线段是固定的,所以某种程度上说每次都把n条线段查一遍有大量的重复和浪费; 线段树就是可以解决这类问题的数据结构 举例说明:已知线段[2,5] [4,6] [0,7];求点2,4,7分别出现了多少次 在[0,7]区间上建立一棵满二叉树:(为了和已知线段区别,用【】表示线段树中的线段) 【0,7】 / \ 【0,3】 【4,7】 / \ / \ 【0,1】 【2,3】 【4,5】 【6,7】 / \ / \ / \ / \ 【0,0】 【1,1】 【2,2】 【3,3】 【4,4】 【5,5】 【6,6】 【7,7】 每个节点用结构体: struct line { int left,right; // 左端点、右端点 int n; // 记录这条线段出现了多少次,默认为0 }a[16]; 和堆类似,满二叉树的性质决定a[i]的左儿子是a[2*i]、右儿子是a[2*i+1]; 然后对于已知的线段依次进行插入操作: 从树根开始调用递归函数insert // 要插入的线段的左端点和右端点、以及当前线段树中的某条线段 void insert(int s,int t,int step)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值