赞
踩
线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。它的主要优势是对于区间求和、区间求最大值、区间修改和单点修改的速度快,时间复杂度能达到
O
(
l
o
g
N
)
O(logN)
O(logN)。
若以常规的方法在数组中进行区间求和等操作,时间复杂度会达到
O
(
n
)
O(n)
O(n),若操作的次数量非常大,那么就很容易超时。线段树的优势就体现出来了
线段树的实现基于一维数组,用数组下标
2
∗
k
+
1
2 * k +1
2∗k+1 的元素代表左儿子,用下标
2
∗
k
+
2
2 * k +2
2∗k+2 的元素代表右儿子来进行树的模拟
对于本文有不理解的小伙伴,建议看B站的这个视频:线段树
模板题:操作格子
代码:
/** * @param node 当前结点 * @param l 当前结点对应的区间为l~r * @param r */ public static void build(int node, int l, int r) { if (l == r) { tree[node] = arr[l]; return; } int mid = (l + r) >> 1; int l_child = 2 * node + 1; int r_child = 2 * node + 2; build(l_child, l, mid); //构建左儿子 build(r_child, mid + 1, r); //构建右儿子 //子树构建好后,更新父结点元素 tree[node] = tree[l_child] + tree[r_child]; }
下面画个图理解建立好的树
可以看出:
代码:
/** * @param node 当前结点 * @param l 当前结点对应的区间为l~r * @param r * @param idx 需更新点的下标(原数组下标) * @param val 更新为什么值 */ public static void update(int node, int l, int r, int idx, int val) { if (l == r) { //l=r的时候,表示找到了idx对应的结点 tree[node] = val; //更新树的结点 arr[idx] = val; //更新原数组的值 return; } int mid = (l + r) >> 1; int l_child = 2 * node + 1; int r_child = 2 * node + 2; if (idx <= mid) { update(l_child, l, mid, idx, val); }else { update(r_child, mid + 1, r, idx, val); } //对应元素更新好后,更新父节点的值 tree[node] = tree[l_child] + tree[r_child]; }
例如,更新 4 号元素为 6,更新后的树如下图
代码:
/** * @param node 当前结点 * @param l 当前结点对应的区间为l~r * @param r * @param start 查询区间的范围为start~end * @param end * @return */ public static int query(int node, int l, int r, int start, int end) { if (start > r || end < l) { //不在查询的范围 return 0; } if (start <= l&& end >= r) {//在查询范围,直接返回 return tree[node]; } int mid = (l + r) >> 1; int l_child = node * 2 + 1; int r_child = node * 2 + 2; int l_sum = query(l_child, l, mid, start, end); //左子树的和 int r_sum = query(r_child, mid + 1, r, start, end); //右子树的和 //返回左子树加右子树的和 return l_sum + r_sum; }
例如查更新后的树的 2~5 号元素的区间和:
线段树的其他区间求最大值、区间修改的方式,与本文的方法类似,就不再赘述,有兴趣的小伙伴可以自行实现
public class 线段树 { static int[] arr = {1,3,5,7,9,11}; static int[] tree = new int[4 * arr.length]; public static void main(String[] args) { build(0, 0, arr.length - 1); for (int i = 0; i < 4 * arr.length; i++) { System.out.print(tree[i] + " "); } System.out.println(); update(0, 0, arr.length - 1, 4, 6); for (int i = 0; i < 4 * arr.length; i++) { System.out.print(tree[i] + " "); } int s = query(0,0,arr.length - 1, 2 , 5); System.out.println("\n" + s); } /** * @param node 当前结点 * @param l l和r表示当前的范围 * @param r */ public static void build(int node, int l, int r) { if (l == r) { tree[node] = arr[l]; return; } int mid = (l + r) >> 1; int l_child = 2 * node + 1; int r_child = 2 * node + 2; build(l_child, l, mid); build(r_child, mid + 1, r); tree[node] = tree[l_child] + tree[r_child]; } /** * @param node 当前结点 * @param l 当前结点对应的区间为l~r * @param r * @param idx 需更新点的下标(原数组下标) * @param val 更新为什么值 */ public static void update(int node, int l, int r, int idx, int val) { if (l == r) { //l=r的时候,表示找到了idx对应的结点 tree[node] = val; //更新树的结点 arr[idx] = val; //更新原数组的值 return; } int mid = (l + r) >> 1; int l_child = 2 * node + 1; int r_child = 2 * node + 2; if (idx <= mid) { update(l_child, l, mid, idx, val); }else { update(r_child, mid + 1, r, idx, val); } //更新父节点的值 tree[node] = tree[l_child] + tree[r_child]; } /** * @param node 当前结点 * @param l 当前结点对应的区间为l~r * @param r * @param start 查询区间的范围为start~end * @param end * @return */ public static int query(int node, int l, int r, int start, int end) { if (start > r || end < l) { //不在查询的范围 return 0; } if (start <= l&& end >= r) {//在查询范围,直接返回 return tree[node]; } int mid = (l + r) >> 1; int l_child = node * 2 + 1; int r_child = node * 2 + 2; int l_sum = query(l_child, l, mid, start, end); //左子树的和 int r_sum = query(r_child, mid + 1, r, start, end); //右子树的和 //返回左子树加右子树的和 return l_sum + r_sum; } }
测试截图:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。