线段树(未进阶版)

       线段树,它是一种工具,能把一些对于区间(或者线段)的修改、维护,把时间复杂度降低O(logN)。如果一个数组下标分段,直到划分为一个元素,我们会发现可以构成一棵树。

05293f9371104621baa8c58e33aaec0e.png

这样看就是一颗满二叉树,但是不是每次都是满二叉树,因为会有奇偶的情况,所以其实是一个平衡二叉树。因为这里我们的树是使用数组实现的,就类似于堆排序中的树。

58bd6d6fa7d142dca265da16af0caaec.png

      它其实类似于归并排序的思想,每次都划分子区间,这个树区间左孩子和右孩子的计算公式和堆排序创建的树是一样的。线段树的数组长度可能很大,所以我们定义一个较大值来初始化树的数组长度。这里我们用一个简单的例子来举例。

c5828545d0094072953e2f0244c240d8.png

为了方便起见我们用三个元素举例:

9e59772a7146425e8f235110485391d5.png

d2989dc90608427aaa6f3f92b6e484ef.png

878f39049e4b40dab575fcc79637658d.png

       

#define TREE_MAX 1000

//构建树
//arr[6] = { 1,3,5,7,9,11 };
//tree[TREE_MAX] = {0};
//build_tree(arr, tree, 0, 5, 0)
//从0下标开始构建树,代码里关于node(L_N, R_N)都是树的下标
//L_N是树左节点的下标
//R_N是树右节点的下标
void build_tree(int* arr, int* tree, int left, int right, int node)
{
	if (left == right)
	{
		tree[node] = arr[left];
	}
	else
	{
		//注意,带有node的都是和树的下标有关的
		//左右区间中间值
		int mid = (left + right) >> 1;

		int L_N = 2 * node + 1;//左节点
		int R_N = 2 * node + 2;//右节点

		//划分区域
		//将左节点划分区域
		build_tree(arr, tree, left, mid, L_N);

		//将右节点划分区域
		build_tree(arr, tree, mid + 1, right, R_N);

		//合并
		tree[node] = tree[L_N] + tree[R_N];
	}
}

      我们来看看构建好的线段树。cca7fec0d236414f86300fa4630535fa.png

 

   之后因为是平衡二叉树,并不是完全二叉树,所以我们来把它补全,并看建好的树中的所有元素:

82274fd550b845a28c2edac73ae9a7b8.png

void update_tree(int* arr, int* tree, int left, int right, int node, int idex, int val)
{
	if (left == right)
	{
		tree[node] = val;
		arr[idex] = val;
	}
	else
	{
		int mid = (left + right) >> 1;
		int L_N = 2 * node + 1;
		int R_N = 2 * node + 2;
		if (idex <= mid)
		{//去左分支
			update_tree(arr, tree, left, mid, L_N, idex, val);

		}
		else
		{//去右分支
			update_tree(arr, tree, mid + 1, right, R_N, idex, val);
		}
		tree[node] = tree[L_N] + tree[R_N];
	}
}


//更新arr[4] 的值为6
update_tree(arr, tree, 0, sz - 1, 0, 4, 6);

       当然线段树肯定不知只有这一点将修改数组的效率提高,还可以完成区间求和:922e77763d07490a943142f8aaa82e4e.jpeg       此时需要判断范围覆盖,毕竟本就是为了提高效率,增加空间提升时间的。如果查询范围没有任何交集直接返回0;如果当前求和范围完全覆盖了树节点的范围,那么直接返回这个树节点的值。比如我们目前求arr[6] = { 1,3,5,7,6,11 }下标为[2-5]的区间和。

      注:原数组arr[6] = { 1,3,5,7,9,11 },我们利用update_tree(arr, tree, 0, sz - 1, 0, 4, 6)函数将原数组下标为4的值改为了6,所以更新为arr[6] = { 1,3,5,7,6,11 }。

       此时就要看覆盖的范围了,如果全部覆盖,则直接返回当前树节点即可:

int query_tree(int* arr, int* tree, int left, int right, int node, int L, int R)
//L代表左区间, R代表右区间

b83c097cf647498d9d61dd693124018e.jpeg16c24fd57b2f4ab48a030c707d2623fb.jpeg       如果各位不相信效率提升了,我们可以把调查的区间打印出来,看其中一个完全对覆盖的区间[3-5]看其是否还往下面继续搜索:0e3c1936b65d4f0e914d412d9a2b806e.png

      此时我们就已经完成所有操作,接下来附上全部代码:

#include<stdio.h>
#include<stdlib.h>

//定义一个最大树数组的长度
#define MAX_LEN 1000

//构建树
void build_tree(int* arr, int* tree, int left, int right, int node)
{
	//当区间只有一个值(就是左右区间的值相等)
	if (left == right)
	{
		tree[node] = arr[left];
	}
	else
	{
		int mid = (left + right) >> 1;

		int L_N = node * 2 + 1;//找左孩子节点
		int R_N = node * 2 + 2;//找右孩子节点

		//划分区间
		//给左孩子节点划分区间
		build_tree(arr, tree, left, mid, L_N);

		//给右孩子节点划分区间
		build_tree(arr, tree, mid + 1, right, R_N);

		//当前节点为左右孩子节点值的和
		tree[node] = tree[L_N] + tree[R_N];
	}
}

void update_tree(int* arr, int* tree, int left, int right, int node, int idex, int val)
{
	if (left == right)
	{
		arr[idex] = val;
		tree[node] = val;
	}
	else
	{
		int mid = (left + right) >> 1;
		int L_N = node * 2 + 1;
		int R_N = node * 2 + 2;
		//因为是单个位置,我们需要判断区间
		if (idex <= mid)
		{
			//去左节点
			update_tree(arr, tree, left, mid, L_N, idex, val);
		}
		else
		{
			update_tree(arr, tree, mid + 1, right, R_N, idex, val);
		}
		//还是需要合并
		tree[node] = tree[L_N] + tree[R_N];
	}
}

//求和                                               首个树节点下标  求和左区间  求和右区间
int sum_tree(int* arr, int* tree, int left, int right,  int node,      int L,      int R)
{
	//这里需要判断是否覆盖
	if (right < L || left > R)
	{//没有公共部分
		return 0;
	}
	else if (left >= L && right <= R)
	{//完全覆盖
		return tree[node];
	}
	else
	{
		int mid = (left + right) >> 1;
		int L_N = node * 2 + 1;
		int R_N = node * 2 + 2;
		int sum1 = sum_tree(arr, tree, left, mid, L_N, L, R);
		int sum2 = sum_tree(arr, tree, mid + 1, right, R_N, L, R);
		return sum1 + sum2;
	}

}

int main()
{
	//线段树其实就是方便对数组的查询和修改
	//它会降低暴力遍历的时间复杂度,是一种很好用的数据结构
	//这里带有node的数据都是和tree数组下标有关的
	int arr[] = { 5,7,0,8,4,2 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int segment_tree[MAX_LEN] = { 0 };

	build_tree(arr, segment_tree, 0, sz - 1, 0);
	int i = 0;
	for (i = 0; i < 15; i++)
	{
		printf("tree[%-2d] = %-2d\n", i, segment_tree[i]);
	}

	printf("\n");

	//修改
	update_tree(arr, segment_tree, 0, sz - 1, 0, 2, 1);

	for (i = 0; i < 15; i++)
	{
		printf("tree[%-2d] = %-2d\n", i, segment_tree[i]);
	}

	//求和
	int ret = sum_tree(arr, segment_tree, 0, sz - 1, 0, 0, 2);
	printf("ret = %d\n", ret);

	return 0;
}

        本人能力有限,有不足之处请在评论区指出,希望各位能认真看一看。

 

  • 12
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值