容易记忆、理解的线段树

问题导入

如果有一个数组

int [] arr = {1, 3, 5, 7, 9, 11};

要进行一系列操作:
1、arr[i]加上一个数
2、求区间L-R的和

容易看出,第一个操作复杂度是O(1),第二个是O(n)

有没有更快的呢,答案是有的,利用线段树来实现复杂度为O(logn)

注:这个问题树状数组也可以完成,而且实现更加容易

线段树的构成

线段树
当需要查询区间2 - 5的和时,过程如图
线段树区间求和
当要修改arr[4]的值时,从下往上修改
线段树更新

构造线段树

为了方便用数组构造,我们将树当成是完全二叉树

构造线段树
可以看的出来,
当前节点的左儿子为 2 * node + 1,
右儿子为2 * node + 2
求树的左右儿子节点

代码实现

在代码中,

凡是带node的区间均是树的区间

凡是带node的区间均是树的区间

凡是带node的区间均是树的区间

数组则用start、end、L、R表示

建树

arr是原本的数组,tree是我们要建的线段树,node是指代表当前函数所求区间[start, end]中树的结点的位置,一开始传入build_tree(arr, tree, 0, 0 arr.length - 1)代表整个区间在树中的结点是0,剩余解析看注释

	public static void build_tree(int [] arr, int [] tree, int node, int start, int end) {
		if(start == end) {
			tree[node] = arr[start];
		}else{
			//先求左右结点对应的区间
			int mid = (start + end) / 2;
			int left_node  = 2 * node + 1;
			int right_node = 2 * node + 2;
			
			//分治的思想,先将左右结点建好,按照递归的性质,下面两行代码会一个递归到叶子结点
			//也就是第上面的if条件语句,然后开始由下往上赋值
			build_tree(arr, tree, left_node, start, mid); 
			build_tree(arr, tree, right_node, mid + 1, end);
			
			tree[node] = tree[left_node] + tree[right_node];
		}
	}
更新

可以看到其实参数跟建树类似,只是多了idx,val,分别是在原始数组中将idx位置的值改为val

	public static void update_tree(int [] arr, int [] tree, int node, int start, int end, int idx, int val) {
		if(start == end) {
			arr[idx] = val;
			tree[node] = val;
		}else {
			//同样的先求左右结点对应的区间
			int mid = (start + end) / 2;
			int left_node  = 2 * node + 1;
			int right_node = 2 * node + 2;
			
			//此时如果idx不在我们当前函数所求的区间中,就不需要递归了,其他步骤类似建树
			if(idx >= start && idx <= mid) {
				update_tree(arr, tree, left_node, start, mid, idx, val);
			}else {
				update_tree(arr, tree, right_node, mid + 1, end, idx, val);
			}
			
			tree[node] = tree[left_node] + tree[right_node];
		}
	}
查询

查询稍微复杂一点,参数多了我们所查询的区间L、R

	public static int query_tree(int [] arr, int [] tree,int node, int start, int end, int L, int R) {
		//如果当前函数所查询的区间完全偏离了要求的区间,返回0
		if(R < start || L > end) {
			return 0;
		}
		//类似的,如果包含,则直接放回,当前对应结点的值
		else if(L <= start && end <= R) {
			return tree[node];
		}
		else if(start == end) {
			return tree[node];
		}else {
			//与上面两个函数类似,不加累述
			int mid = (start + end) / 2;
			int left_node  = 2 * node + 1;
			int right_node = 2 * node + 2;
			
			int sum_left  = query_tree(arr, tree, left_node, start, mid, L, R);
			int sum_right = query_tree(arr, tree, right_node, mid + 1, end, L, R);
			
			return sum_left + sum_right;
		}
	}
所有代码
public class RMQ {

	static int MAX_LEN = 1000; //树的节点数,可以开大点
	
	public static void main(String[] args) {
		int arr[] = {1, 3, 5, 7, 9, 11}; //原始数组
		int size = arr.length;
		int [] tree = new int [MAX_LEN];
		
		build_tree(arr, tree, 0, 0, size - 1);
	}
	
	public static int query_tree(int [] arr, int [] tree,int node, int start, int end, int L, int R) {
		if(R < start || L > end) {
			return 0;
		}
		else if(L <= start && end <= R) {
			return tree[node];
		}
		else if(start == end) {
			return tree[node];
		}else {
			int mid = (start + end) / 2;
			int left_node  = 2 * node + 1;
			int right_node = 2 * node + 2;
			
			int sum_left  = query_tree(arr, tree, left_node, start, mid, L, R);
			int sum_right = query_tree(arr, tree, right_node, mid + 1, end, L, R);
			
			return sum_left + sum_right;
		}
	}
	
	public static void update_tree(int [] arr, int [] tree, int node, int start, int end, int idx, int val) {
		if(start == end) {
			arr[idx] = val;
			tree[node] = val;
		}else {
			int mid = (start + end) / 2;
			int left_node  = 2 * node + 1;
			int right_node = 2 * node + 2;
			
			if(idx >= start && idx <= mid) {
				update_tree(arr, tree, left_node, start, mid, idx, val);
			}else {
				update_tree(arr, tree, right_node, mid + 1, end, idx, val);
			}
			
			tree[node] = tree[left_node] + tree[right_node];
		}
	}
	
	public static void build_tree(int [] arr, int [] tree, int node, int start, int end) {
		if(start == end) {
			tree[node] = arr[start];
		}else{
			int mid = (start + end) / 2;
			int left_node  = 2 * node + 1;
			int right_node = 2 * node + 2;
			
			build_tree(arr, tree, left_node, start, mid);
			build_tree(arr, tree, right_node, mid + 1, end);
			
			tree[node] = tree[left_node] + tree[right_node];
		}
	}
}

可以看的出来,无论是构造、更新或者是查询,第一步都是先写

int mid = (start + end) / 2;
int left_node  = 2 * node + 1;
int right_node = 2 * node + 2;

之后都是分治,注意出口条件即可,很好记忆

注:以上均整理自B站正月点灯笼的视频

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值