线段树/区间树(java实现版详解附leetcode例题)

目录

什么是线段树

线段树基础表示

创建线段树(Java版详解)

线段树的区间查询

leetcode上的线段树相关问题

leetcode303题.区域和检索-数组不可变

使用线段树解题

不使用线段树解题

leetcode307题.区域和检索-数组可修改

不使用线段树解题

线段树中的更新操作

使用线段树解题

更多线段树相关的话题

懒惰更新

二维线段树

动态线段树


什么是线段树

在介绍线段树前,我们先通过两个小问题引入一下为什么我们需要使用线段树。

最经典的线段树问题:区间染色

或者我们问一个更普遍的问题:在m次操作后,我们可以在[i,j]区间 中我们可以看见多少种颜色?

对于这种问题,我们可以用数组实现两种操作:

染色操作(更新区间) O(n)

查询操作(查询区间) O(n)

O(n)的时间复杂度在一些情况下是不适合的,所以我们要进一步寻找更优的算法。

另一经典问题:区间查询

以上两种经典问题的更新和查询都是O(n)的时间复杂度,这时候引入线段树就显得额外宝贵了。 

在用一个数组A创建线段树时,我们有一个前提,就是对于我们的线段树,我们是不考虑向线段树中添加元素或者删除元素的,比如我们墙的长度给出来那它就是固定的了,我们不再考虑再加长或者缩短这面墙。这样我们就保证了区间本身是固定的,所以我们用静态数组就好了。

根据数组A构造的线段树就是下图的样子:

我们可以看到,线段树每个结点都是一个区间,这个区间不是说把区间中的所有元素都存进这个结点,以线段树求和为例,每个结点存储的就是它所在区间的数值和。例如:A[4...7]存储的就是[4,7]这个区间中所有数字的

线段树基础表示

线段树不一定是满二叉树,我们上面举得数组A构成的线段树中有8个元素,8刚好是2的3次方,所以它恰好是一棵满二叉树。一般情况下,如果某个结点的区间元素个数是偶数可以平分,那么一个结点的左右孩子各自会存储一半的元素。否则,就左右孩子一个存的少一点一个存的多一点。

例如一个存储10个元素的数组A就和8个元素的数组A不一样:

我们可以看到,线段树的叶子节点不一定在最后一层,也可以在倒数第二层。

我们的线段树也不一定是满二叉树,也不一定是完全二叉树。

但我们的线段树一定是一棵平衡二叉树(最大深度和最小深度的差不超过1)。

平衡二叉树的优势是:它不会像二分搜索树一样退化成一个链表,一棵平衡二叉树的高度和结点的关系一定是一个log的关系,这使得在平衡二叉树上进行搜索查询是非常高效的。

线段树虽然不是一个完全二叉树,但是作为一棵平衡二叉树,我们仍然可以用数组来表示它。表示方法是什么呢?我们可以把线段树看作是一棵满二叉树,最后一层虽然有很多结点是不存在的,我们把它们看作是空就好了。满二叉树作为一棵完全二叉树,是可以用数组来表示的。

如果区间有n个元素,用数组表示需要有多少结点呢?

对于一棵满二叉树,层数和每一层的结点数的关系是 第h - 1层 : 2^(h - 1)。

h层是指从0层到h-1层共h层。

有了上图所给的结论,我们就能很好的分析所需要的结点数了。

当然,对于这4n的空间,我们并不是每一个都利用起来了,而且我们是一个估计值,线段树不一定是满二叉树,最后一层的很多地方就是空的,在最坏的情况下可能有一半的空间都是浪费的,如下图。

不过我们在这里不用过多的考虑这些浪费的情况,对现代计算机来说存储空间本身还是不叫问题的,我们做算法的原则一般还是需要用空间来换时间。当然这些浪费是可以避免的,我们在文章最后对线段树做更多拓展的时候会提到,有兴趣的朋友可以尝试不使用数组来存储而采用链式的结构来存储线段树。

我们现在是采用数组的方式来存储一棵线段树,我们先实现一个基础的代码。

创建线段树(Java版详解)

//线段树的各自基本实现
public class SegmentTree<E> {private E[] data;private E[] tree;private Merger<E> merger;//构造函数传进来的是我们整个要考察的范围public SegmentTree(E[] arr, Merger<E> merger){this.merger = merger;data = (E[])new Object[arr.length];for(int i = 0; i < arr.length; i++){data[i] = arr[i];}tree = (E[])new Object[4 * arr.length];//从根节点开始buildSegmentTree(0, 0, data.length - 1);}//在treeIndex的位置创建表示区间[l...r]的线段树private void buildSegmentTree(int treeIndex, int l, int r){if(l  == r){tree[treeIndex] = data[l];return;}//左右子树对应的索引int leftTreeIndex = leftChild(treeIndex);int rightTreeIndex = rightChild(treeIndex);//左右子树对应的区间范围int mid = l + (r - l) / 2; //防止整型溢出//递归调用buildSegmentTree(leftTreeIndex, l, mid);buildSegmentTree(rightTreeIndex, mid + 1, r);/*因为我们整体代码采用的是泛型,所以tree[treeIndex]的具体实现是加减乘除还是其他什么是取决于用户的具体实现我们引入了一个接口融合器,否则直接写加减乘除还是怎样编译器会报错*/tree[treeIndex] = merger.merge(tree[leftTreeIndex], tree[rightTreeIndex]);}public int getSize(){return data.length;}public E get(int index){if(index < 0 || index >= data.length){throw new IllegalArgumentException("Index is illegal");}return data[index];}//返回完全二叉树的数组表示中,一个索引所表示的元素的左孩子private int leftChild(int index){return 2 * index + 1;}//返回完全二叉树的数组表示中,一个索引所表示的元素的右孩子private int rightChild(int index){return 2 * index + 2;}@Overridepublic String toString(){StringBuilder res = new StringBuilder();res.append('[');for(int i = 0; i < tree.length; i++){if(tree[i] != null){res.append(tree[i]);}else{res.append("null");}if(i != tree.length - 1){res.append(",");}}res.append(']');return res.toString();}
}
//融合器接口实现
public interface Merger<E> {E merge(E a, E b);
}
//Main函数
//线段树结构的数组表示,以线段树求和为例
public class Main{public static void main(String[]args){Integer []nums = {-2, 0, 3, -5, 2, -1};SegmentTree<Integer> segTree = new SegmentTree<>(nums, (a, b) -> a + b);System.out.println(segTree);}}

运行结果:

线段树的区间查询

线段树的查询还是蛮好理解的,只需要从根节点开始向下找相应的子区间,然后再把所有找到的子区间综合起来就好了 ,这个找的过程和树的高度相关,和我们需要查询的区间长度是无关的。因为线段树的高度是logn级别的,所以我们整个的查询也是logn级别的。

接下来我们来实现一下线段树的查询操作

//查询操作//返回待查询区间[queryL, queryR]的值public E query(int queryL, int queryR){//边界检查if(queryL < 0 || queryL >= data.length ||queryR < 0 || queryR >= data.length || queryL > queryR){throw new IllegalArgumentException("Index is illegal");}//递归函数,从根节点开始return query(0, 0, data.length - 1, queryL, queryR);}//设计递归函数//在以treeID为根的线段树中[l...r]的范围里,搜索区间[queryL...queryR]的值private E query(int treeIndex, int l, int r, int queryL, int queryR){if(l == queryL && r == queryR){return tree[treeIndex];}int mid = l + (r - l) / 2;int leftTreeIndex = leftChild(treeIndex);int rightTreeIndex = rightChild(treeIndex);//待查询区间落在右孩子那边if(queryL >= mid + 1){return query(rightTreeIndex, mid + 1, r, queryL, queryR);}//落在左孩子那边else if(queryR <= mid){return query(leftTreeIndex, l, mid, queryL, queryR);}//一部分落在左孩子那边,一部分落在右孩子那边E leftResult = query(leftTreeIndex, l, mid, queryL, mid);E rightResult = query(rightTreeIndex, mid + 1, r, mid + 1, queryR);//两边都找一下然后融合return merger.merge(leftResult, rightResult);}

把我们刚实现的查询操作加入咱们线段树的基础代码中,并在main函数中创建样例运行。

//实现了查询操作的线段树基本操作
public class SegmentTree<E> {private E[] data;private E[] tree;private Merger<E> merger;//构造函数传进来的是我们整个要考察的范围public SegmentTree(E[] arr, Merger<E> merger){this.merger = merger;data = (E[])new Object[arr.length];for(int i = 0; i < arr.length; i++){data[i] = arr[i];}tree = (E[])new Object[4 * arr.length];//从根节点开始buildSegmentTree(0, 0, data.length - 1);}//在treeIndex的位置创建表示区间[l...r]的线段树private void buildSegmentTree(int treeIndex, int l, int r){if(l  == r){tree[treeIndex] = data[l];return;}//左右子树对应的索引int leftTreeIndex = leftChild(treeIndex);int rightTreeIndex = rightChild(treeIndex);//左右子树对应的区间范围int mid = l + (r - l) / 2; //防止整型溢出//递归调用buildSegmentTree(leftTreeIndex, l, mid);buildSegmentTree(rightTreeIndex, mid + 1, r);/*因为我们整体代码采用的是泛型,所以tree[treeIndex]的具体实现是加减乘除还是其他什么是取决于用户的具体实现我们引入了一个接口融合器,否则直接写加减乘除还是怎样编译器会报错*/tree[treeIndex] = merger.merge(tree[leftTreeIndex], tree[rightTreeIndex]);}public int getSize(){return data.length;}public E get(int index){if(index < 0 || index >= data.length){throw new IllegalArgumentException("Index is illegal");}return data[index];}//返回完全二叉树的数组表示中,一个索引所表示的元素的左孩子private int leftChild(int index){return 2 * index + 1;}//返回完全二叉树的数组表示中,一个索引所表示的元素的右孩子private int rightChild(int index){return 2 * index + 2;}//查询操作//返回待查询区间[queryL, queryR]的值public E query(int queryL, int queryR){//边界检查if(queryL < 0 || queryL >= data.length ||queryR < 0 || queryR >= data.length || queryL > queryR){throw new IllegalArgumentException("Index is illegal");}//递归函数,从根节点开始return query(0, 0, data.length - 1, queryL, queryR);}//设计递归函数//在以treeID为根的线段树中[l...r]的范围里,搜索区间[queryL...queryR]的值private E query(int treeIndex, int l, int r, int queryL, int queryR){if(l == queryL && r == queryR){return tree[treeIndex];}int mid = l + (r - l) / 2;int leftTreeIndex = leftChild(treeIndex);int rightTreeIndex = rightChild(treeIndex);//待查询区间落在右孩子那边if(queryL >= mid + 1){return query(rightTreeIndex, mid + 1, r, queryL, queryR);}//落在左孩子那边else if(queryR <= mid){return query(leftTreeIndex, l, mid, queryL, queryR);}//一部分落在左孩子那边,一部分落在右孩子那边E leftResult = query(leftTreeIndex, l, mid, queryL, mid);E rightResult = query(rightTreeIndex, mid + 1, r, mid + 1, queryR);//两边都找一下然后融合return merger.merge(leftResult, rightResult);}@Overridepublic String toString(){StringBuilder res = new StringBuilder();res.append('[');for(int i = 0; i < tree.length; i++){if(tree[i] != null){res.append(tree[i]);}else{res.append("null");}if(i != tree.length - 1){res.append(",");}}res.append(']');return res.toString();}
}
/融合器
public interface Merger<E> {E merge(E a, E b);
}
//线段树结构的数组表示,以线段树求和为例
public class Main{public static void main(String[]args){Integer []nums = {-2, 0, 3, -5, 2, -1};SegmentTree<Integer> segTree = new SegmentTree<>(nums, (a, b) -> a + b);//System.out.println(segTree);System.out.println(segTree.query(0, 2));//查询区间为-2 + 0 + 3System.out.println(segTree.query(0, 5));//查询区间为nums全加起来}}

运行结果:

leetcode上的线段树相关问题

leetcode303题.区域和检索-数组不可变

303. 区域和检索 - 数组不可变 - 力扣(LeetCode)

这里的不可变指的是不涉及线段树的更新操作,什么是线段树的更新操作我们一会儿会讲。

给定一个整数数组  nums,处理以下类型的多个查询:

计算索引 left 和 right (包含 left 和 right)之间的 nums 元素的  ,其中 left <= right

实现 NumArray 类:

  • NumArray(int[] nums) 使用数组 nums 初始化对象
  • int sumRange(int i, int j) 返回数组 nums 中索引 left 和 right 之间的元素的 总和 ,包含 left 和 right 两点(也就是 nums[left] + nums[left + 1] + ... + nums[right] )

示例 1:

输入:
["NumArray", "sumRange", "sumRange", "sumRange"]
[[[-2, 0, 3, -5, 2, -1]], [0, 2], [2, 5], [0, 5]]
输出:
[null, 1, -1, -3]解释:
NumArray numArray = new NumArray([-2, 0, 3, -5, 2, -1]);
numArray.sumRange(0, 2); // return 1 ((-2) + 0 + 3)
numArray.sumRange(2, 5); // return -1 (3 + (-5) + 2 + (-1)) 
numArray.sumRange(0, 5); // return -3 ((-2) + 0 + 3 + (-5) + 2 + (-1))

提示:

  • 1 <= nums.length <= 10^4
  • -105 <= nums[i] <= 10^5
  • 0 <= i <= j < nums.length
  • 最多调用 10^4次 sumRange 方法

使用线段树解题


class NumArray {private int[] tree;private int[] data;private int left(int idx){return 2*idx+1;}private int right(int idx){return 2*idx+2;}private void buildSegmentTree(int treeIdx,int l,int r){if(l==r){tree[treeIdx]=data[l];return;}int mid=l+(r-l)/2;int leftTreeIndex=left(treeIdx);int rightTreeIndex=right(treeIdx);buildSegmentTree(leftTreeIndex,l,mid);buildSegmentTree(rightTreeIndex,mid+1,r);tree[treeIdx]=tree[leftTreeIndex]+tree[rightTreeIndex];}public NumArray(int[] nums) {data=new int[nums.length];for (int i = 0; i < nums.length; i++) {data[i]=nums[i];}tree=new int[nums.length*4];buildSegmentTree(0,0,data.length-1);}private int query(int idx,int l,int r,int qL,int qR){if(l==qL&&r==qR)return tree[idx];int mid=l+(r-l)/2;int leftTree=left(idx);int rightTree=right(idx);if(qL>=mid+1)return query(rightTree,mid+1,r,qL,qR);if(qR<=mid)return query(leftTree,l,mid,qL,qR);int leftRes=query(leftTree,l,mid,qL,mid);int rightRes=query(rightTree,mid+1,r,mid+1,qR);return leftRes+rightRes;}public int sumRange(int left, int right) {if(left<0||left>=data.length||right<0||right>=data.length||left>right)throw new IllegalArgumentException("Idx is illegal.");return query(0,0,data.length-1,left,right);}
}

不使用线段树解题

//进行预处理
class NumArray {//sum[i]存储前i个元素和,sum[0] = 0//sum[i]存储nums[0...i - 1]的和private int[]sum;public NumArray(int[] nums) {//因为sum[0]存储的不是第一个元素的值,只是一个数字0,sum[1]才是第一个元素的值,所以有一个偏移量sum = new int[nums.length + 1];sum[0] = 0;for(int left = 1; left < sum.length; left++){sum[left] = sum[left - 1] + nums[left - 1];}}public int sumRange(int left, int right) {//从0到right元素的和减去从0到left - 1对应的和return sum[right + 1] - sum[left];}
}

这么一看,好像不用线段树的方法更方便哎,那我们干嘛还用线段树?题目一开头我们说了,这道题不涉及线段树的更新操作,线段树更适合解决动态的情况,这道题所有的数值都是固定的、静态的,所以不用使用线段树这么复杂的数据结构就能解决。

让我们再来一道题作为静态的对比。

leetcode307题.区域和检索-数组可修改

307. 区域和检索 - 数组可修改 - 力扣(LeetCode)

给你一个数组 nums ,请你完成两类查询。

  1. 其中一类查询要求 更新 数组 nums 下标对应的值
  2. 另一类查询要求返回数组 nums 中索引 left 和索引 right 之间( 包含 )的nums元素的  ,其中 left <= right

实现 NumArray 类:

  • NumArray(int[] nums) 用整数数组 nums 初始化对象
  • void update(int index, int val) 将 nums[index] 的值 更新 为 val
  • int sumRange(int left, int right) 返回数组 nums 中索引 left 和索引 right 之间( 包含 )的nums元素的  (即,nums[left] + nums[left + 1], ...,nums[right]

示例 1:

输入:
["NumArray", "sumRange", "update", "sumRange"]
[[[1, 3, 5]], [0, 2], [1, 2], [0, 2]]
输出:
[null, 9, null, 8]解释:
NumArray numArray = new NumArray([1, 3, 5]);
numArray.sumRange(0, 2); // 返回 1 + 3 + 5 = 9
numArray.update(1, 2);   // nums = [1,2,5]
numArray.sumRange(0, 2); // 返回 1 + 2 + 5 = 8

提示:

  • 1 <= nums.length <= 3 * 10^4 
  • -100 <= nums[i] <= 100
  • 0 <= index < nums.length
  • -100 <= val <= 100
  • 0 <= left <= right < nums.length
  • 调用 update 和 sumRange 方法次数不大于 3 * 10^4 

我们可以看到这道题和303题唯一的区别就是多了一个update的更新操作,我们先用非线段树方法来试一试。

不使用线段树解题

//进行预处理
class NumArray {//sum[i]存储前i个元素和,sum[0] = 0//sum[i]存储nums[0...i - 1]的和private int[]sum;private int[]data;public NumArray(int[] nums) {data = new int[nums.length];for(int i = 0; i < nums.length; i++){data[i] = nums[i];}//因为sum[0]存储的不是第一个元素的值,只是一个数字0,sum[1]才是第一个元素的值,所以有一个偏移量sum = new int[nums.length + 1];sum[0] = 0;for(int left = 1; left < sum.length; left++){sum[left] = sum[left - 1] + nums[left - 1];}}public void update(int index, int val) {data[index] = val;for(int left = index + 1; left < sum.length; left++){sum[left] = sum[left - 1] + data[left - 1];}
}public int sumRange(int left, int right) {//从0到right元素的和减去从0到left - 1对应的和return sum[right + 1] - sum[left];}
}

我们可以看到,非线段树的方法只通过了12/16个样例,样例再大一点就超出运行时间了。 究其根本就是运行中存在大量的时间复杂度为O(n)的update操作,整体时间复杂度就是m * n 级别,是比较慢的。此时我们的数组就需要动态的改变了,线段树这种数据结构就要发挥作用了,接下来我们就要在我们的线段树中添加上update的操作,然后进一步解决307号这个问题。(线段树方法的题解放后文)

线段树中的更新操作

以下代码可以加入我们之前实现的线段树的基本操作。

//将index位置的值更新为epublic void set(int index, E e){if(index < 0 || index >= data.length){throw new IllegalArgumentException("Index is illegal");}data[index] = e;//递归set(0, 0, data.length - 1, index, e);}private void set(int treeIndex, int l, int r, int index, E e){if(l == r){tree[treeIndex] = e;return;}int mid = l + (r - l) / 2;int leftTreeIndex = leftChild(treeIndex);int rightTreeIndex = rightChild(treeIndex);if(index >= mid + 1){set(rightTreeIndex, mid + 1, r, index, e);}else{set(leftTreeIndex, l, mid, index, e);}tree[treeIndex] = merger.merge(tree[leftTreeIndex], tree[rightTreeIndex]);}

学会了线段树的更新操作后,我们就可以回过头来去解决307号问题的线段树解法。

使用线段树解题

class NumArray {class TreeNode{public int sum;public int start, end;public TreeNode left, right;public TreeNode(int start, int end){this.start = start;this.end   = end;}}TreeNode root = null;public NumArray(int[] nums) {root = buildTree(nums, 0, nums.length - 1);}public void update(int index, int val) {update(root, index, val);}public int sumRange(int left, int right) {return query(root, left, right);}private int query(TreeNode root, int left, int right){if(root.start == left && root.end == right)return root.sum;else{int mid = root.start + (root.end - root.start) / 2;if(right <= mid)return query(root.left, left, right);else if(left > mid)return query(root.right, left, right);elsereturn query(root.left, left, mid) + query(root.right, mid + 1, right);}}private void update(TreeNode root, int index, int val){if(root.start == root.end){root.sum = val;return;}else{int mid = root.start + (root.end - root.start) / 2;if(index <= mid)update(root.left, index, val);else update(root.right, index, val);root.sum = root.left.sum + root.right.sum;}}private TreeNode buildTree(int[] nums, int start, int end){if(start > end)return null;else if(start == end){TreeNode node = new TreeNode(start, end);node.sum      = nums[start];return node;}else{TreeNode node = new TreeNode(start, end);int mid = start + (end - start) / 2;node.left = buildTree(nums, start, mid);node.right = buildTree(nums, mid + 1, end);node.sum  = node.left.sum + node.right.sum;return node;}}
}

更多线段树相关的话题

我们点了一下线段树的标签,发现leetcode上关于线段树的问题还挺难的。如果你不去参加算法竞赛的话,线段树不是一个重点,请合理安排自己的时间。

当我们赋予线段树合理的意义后,我们可以非常高效的处理和线段或者区间相关的问题。

我们实现的三个方法:创建线段树、查询线段树和更新线段树都采用了递归的写法。

懒惰更新

我们之前的更新操作都是对线段树某个结点存储的值进行的更新,但是如果我们想对区间进行更新呢?

我们可以使用一个lazy数组记录未更新的内容,大家有个印象就好,如果感兴趣可以自己去查阅资料学习。

二维线段树

我们之前接触的都是上图所示的一维线段树,在一个坐标轴中的。可以分为前半段作为左孩子,右半段作为右孩子。

如果我们扩展到二维呢?

我们可以记录的是一个矩阵的内容,然后我们可以把矩阵分成四块,就可以有四个孩子,每个孩子就是一个更小的矩阵,直到在叶子结点的时候就只剩下一个元素,这就是二维线段树。

以此类推,我们还可以有三维线段树,那我们就可以分成八块.......

线段树本身就是一个思想,我们要学会把一个大的数据单元拆分成一个个小的数据单元,递归的表示这些数据,这本身就是树这种数据结构的实质。

动态线段树

我们上文说过,从数组方式存储开辟4n的空间免不了浪费,所以我们可以用链式的方式存储。

比如如果线段树的结点数非常大,比如一亿,那我们刚开始并不着急直接创造一个4*一亿的空间,而是动态的创建,用到哪里创哪里,如下图所示:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/296549.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

算法模板之队列图文详解

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;算法模板、数据结构 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 &#x1f4cb;前言一. ⛳️模拟队列1.1 &#x1f514;用数组模拟实现队列1.1.1 &#x1f47b;队列的定…

1859_续流二极管是什么以及其作用

Grey 全部学习内容汇总&#xff1a; GitHub - GreyZhang/g_hardware_basic: You should learn some hardware design knowledge in case hardware engineer would ask you to prove your software is right when their hardware design is wrong! 1859_续流二极管是什么以及其…

python 解决手机拍的书籍图片发灰的问题

老师给发的作业经常是手机拍的&#xff0c;而不是扫描&#xff0c;背景发灰&#xff0c;如果二次打印就没有看了&#xff0c;象这样&#xff1a; 如果使用photoshop 处理&#xff0c;有些地方还是扣不干净&#xff0c;不如python 做的好&#xff0c;处理后如下&#xff1a; 具体…

css 设备背景图片 宽高总是不能平铺

宽高总是宽大了 高就挤出去了&#xff1b;高设置了 宽度就变小了&#xff1b;疯掉的节奏。。。。。。 .center-bottom{background: url(/img/newpic/leftbg.png);background-repeat: no-repeat;width: 98%;height: 60%;background-position: center center;background-size: 1…

关于网络犯罪份子租用WikiLoader通过银行木马攻击意大利组织的动态情报

一、基本内容 近期Proofpoint研究人员发现了一种新的恶意软件&#xff0c;被称为WikiLoader。这个恶意软件在2022年12月首次被发现&#xff0c;并且由攻击者组织TA544传播&#xff0c;他们通常使用Ursnif恶意软件来攻击意大利组织。Proofpoint观察到了多个与此相关的活动&…

设计模式(4)--类行为(3)--解释器

1. 意图 给定一个语言&#xff0c;定义它的文法的一种表示&#xff0c;并定义一个解释器&#xff0c;这个解释器使用该表示来解释语 言中的句子。 2. 四种角色 抽象表达式(AbstractExpression)、终结表达式(TerminalExpression)、非终结表达式(NonterminalExpression)、上下文…

LTE之帧结构

一、基本时间单位 在LTE里&#xff0c;时间的基本单位都是采样周期Ts&#xff0c;值固定等于&#xff1a; Ts 1 / (15000 * 2048) 32.55ns 其中&#xff0c;15000表示子载波的间隔是15kHz&#xff0c;2048表示采样点个数。除了15kHz的子载波间隔之外&#xff0c;3GPP还定义…

【高数定积分求解旋转体体积】 —— (上)高等数学|定积分|柱壳法|学习技巧

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 &#x1f4ab;个人格言:"没有罗马,那就自己创造罗马~" 目录 Shell method Setting up the Integral 例题 Example 1: Example 2: Example 3: Computing…

安工程校园导游系统课程设计报告

第一章 概述 1.1 问题描述 根据题目内容,需要用无向网表示所在学校的景点平面图,即校内每个景点 为无向网中的各个结点,景点的编号、名称、简介等信息存储在结点中,图中的 边表示景点间的道路,存放景点之间的路径长度等信息。需要回答有关景点介绍、 游览路径等问题。具…

【LearnOpenGL基础入门——5】着色器

目录 一.简介 二.GLSL 三.数据类型 四.输入与输出 五.Uniform 六.更多属性 一.简介 着色器(Shader)是运行在GPU上的小程序。这些小程序为图形渲染管线的某个特定部分而运行。从基本意义上来说&#xff0c;着色器只是一种把输入转化为输出的程序。着色器也是一种非常独立…

深度学习 | 基础卷积神经网络

卷积神经网络是人脸识别、自动驾驶汽车等大多数计算机视觉应用的支柱。可以认为是一种特殊的神经网络架构&#xff0c;其中基本的矩阵乘法运算被卷积运算取代&#xff0c;专门处理具有网格状拓扑结构的数据。 1、全连接层的问题 1.1、全连接层的问题 “全连接层”的特点是每个…

关于巴西网络犯罪分子使用LOLBaS和CMD脚本窃取银行账户的动态情报

一、基本内容 最近&#xff0c;一名未知身份的网络犯罪威胁行为者以使用西班牙语和葡萄牙语的用户为目标&#xff0c;破坏墨西哥、秘鲁和葡萄牙等地的网上银行账户。该攻击链主要利用社会工程学技术&#xff0c;利用葡萄牙和西班牙用户的电子邮件&#xff0c;发送带有欺骗性的…