二叉树遍历求差值绝对值最大值

本文介绍了通过遍历二叉树来找出最大差值的方法,包括层序遍历、先序遍历(递归与循环实现)以及使用STL队列实现层序遍历。详细解释了如何初始化二叉树节点、链接节点,并提供了测试程序以验证解决方案的有效性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

来自阿里巴巴2015年笔试题,参考牛客网。

写一个函数,输入一个二叉树,树中每个节点存放了一个整数值,函数返回这颗二叉树中相差最大的两个节点间的差值绝对值。请注意程序效率。

这道题目可以通过遍历整个二叉树,求解二叉树中最大值和最小值,最后算出最大的差值。

可以定义一个二叉树,如下图。


初始化二叉树结点、链接结点、二叉树的结构的定义

struct BinTree
{
	int data;
	BinTree * lchild;
	BinTree * rchild;
};
BinTree * createnode(int val)
{
	BinTree * node=new BinTree;
	node->data=val;
	node->lchild=NULL;
	node->rchild=NULL;
	return node;
}
void connectnode(BinTree * f,BinTree * lchild,BinTree *rchild)
{
	if(f==NULL)
		return ;
	f->lchild=lchild;
	f->rchild=rchild;
}
对于二叉树可以按层遍历,图中二叉树按层遍历的结果为1 2 3 4 5 6 7。

二叉树按层遍历,可以利用STL队列。

int find_max_diff1(BinTree* head)//按层遍历
{
	queue<BinTree*> q;
	BinTree* p;
	if (NULL==head)
	{
		return -1;
	}
	int max1=INT_MIN;//初始化最大值
	int min1=INT_MAX;//初始化最小值
	q.push(head);
	while (!q.empty())
	{
		p = q.front();
		q.pop();
		if(p->data>max1)
			max1=p->data;
		if(p->data<min1)
			min1=p->data;
		if (p->lchild!=NULL)
		{
			q.push(p->lchild);
		}
		if (p->rchild!=NULL)
		{
			q.push(p->rchild);
		}
	}
	return (max1-min1);
}
当输入的head指针为NULL,表明二叉树为空,返回-1代表输入参数异常。

除了按层遍历外,使用的最多的要数先序遍历。先序遍历先输出二叉树的根结点,再分别对根节点的左子树与右子树进行相同的操作,既可以用递归的方式实现,也可以用循环的方式实现。

以递归的方式实现二叉树遍历:

先定义两个全局变量,min1、max1。通过函数递归的过程,来修改min1、max1的数值,最后函数find_max_diff2返回max1-min1。

int min1=INT_MAX;//全局变量
int max1=INT_MIN;
void midprint(BinTree * head)//递归先序遍历
{
	
	if(head==NULL)
		return;
	if(head->data> max1)
		max1=head->data;
	if(head->data<min1)
		min1=head->data;
	midprint(head->lchild);
	midprint(head->rchild);
}
int find_max_diff2(BinTree * head)
{
	if(head==NULL)//当输入空指针
		return -1;
	midprint(head);
	return max1-min1;
}

以循环的方式现实二叉树遍历。

int find_max_diff3(BinTree *head)//先序遍历循环
{
    stack<BinTree*> s;
    BinTree *p=head;
	if(head==NULL)
		return -1;
    int max1 = p->data;
    int min1 = p->data;
    while(p!=NULL||!s.empty())
    {
        while(p!=NULL)
        {
            s.push(p);
            p=p->lchild;
        }
        if(!s.empty())
        {
            p=s.top();
			//cout<<p->data<<" ";
            if(p->data > max1)
            {
                max1 = p->data;
            }
            if(p->data < min1)
            {
                min1 = p->data;
            }
            s.pop();
            p=p->rchild;
        }
 }
 return abs(max1 - min1);
 }
测试程序:

int main()
{
	BinTree *node1=createnode(1);
	BinTree *node2=createnode(2);
	BinTree *node3=createnode(3);
	BinTree *node4=createnode(4);
	BinTree *node5=createnode(5);
	BinTree *node6=createnode(6);
	BinTree *node7=createnode(7);
	connectnode(node1,node2,node3);
	connectnode(node2,node4,node5);
	connectnode(node3,node6,node7);
	cout<<find_max_diff1(node1)<<endl;
	cout<<find_max_diff2(node1)<<endl;
	
	cout<<find_max_diff3(node1)<<endl;
	return 0;
}

测试结果:





<think>首先,用户的问题是关于在多次区间加值操作后,如何高效计算整个数组的最大公约数(GCD)。用户提到了“区间加值操作”,所以我需要理解这个操作:用户可能指的是对数组的某个子区间进行加法操作,比如给数组的某个范围内的所有元素加上一个值。 所以,问题描述是:有一个数组,初始时所有元素都是整数。然后,有多个操作,每个操作是对数组的一个子区间加上一个值(可能是正数或负数)。在这些操作之后,我需要计算整个数组所有元素的最大公约数(GCD)。 高效计算GCD的关键是:由于有多次区间加值操作,直接在每个操作后重新计算整个数组的GCD会很低效,因为每次都需要遍历整个数组。所以,我需要一种方法来维护或高效地更新GCD。 回想一下GCD的性质: - GCD是最大公约数,计算整个数组的GCD通常需要所有元素的值。 - 但是,在区间加值操作后,元素的值在变化,所以GCD可能会改变。 一个常见的高效处理区间操作的方法是使用数据结构,比如线段树(Segment Tree)或树状数组(Fenwick Tree),来支持区间更新和查询。 但GCD操作不是简单的和或最小值;GCD有独特的数学性质。具体来说,GCD操作是结合的,但不是可交换的,在某种意义下是可分配的,但需要小心。 对于数组的GCD,有一个重要性质:数组的GCD等于所有元素差值的GCD的某种组合。更精确地说,对于数组A,有: gcd(A[1], A[2], ..., A[n]) = gcd(A[1], gcd(A[2]-A[1], A[3]-A[2], ..., A[n]-A[n-1])) 或类似的形式。实际上,一个标准方法是: gcd(a, b, c) = gcd(a, gcd(b, c)),但更高效的是使用差分数组。 一个关键洞察是:使用差分数组。 设原数组为A[1..n],定义差分数组D[1..n],其中: D[1] = A[1] D[i] = A[i] - A[i-1] for i = 2 to n 然后,数组A的GCD可以基于D来计算。 但GCD不是线性操作,所以差分后GCD如何? 回忆:对于两个数,gcd(a, b) = gcd(a, b - a) 如果 b > a,等等。更一般地,gcd(a, b) = gcd(a, b - k*a) 对于任何整数 k。 所以,对于整个数组,数组的GCD等于第一个元素和所有相邻差值的GCD。 具体公式:gcd(A[1], A[2], ..., A[n]) = gcd(A[1], D[2], D[3], ..., D[n]),其中D[i] = A[i] - A[i-1] for i >=2。 是的,因为: - gcd(A[1], A[2]) = gcd(A[1], A[2] - A[1]) = gcd(A[1], D[2]) - 然后 gcd(A[1], A[2], A[3]) = gcd( gcd(A[1], A[2]), A[3]) = gcd( gcd(A[1], D[2]), A[3]) = gcd(A[1], D[2], A[3]) 但 A[3] 可以写成 A[1] + (A[2] - A[1]) + (A[3] - A[2]) = A[1] + D[2] + D[3],所以 gcd(A[1], D[2], A[3]) = gcd(A[1], D[2], A[1] + D[2] + D[3])。 由于 gcd(x, y, z) 和线性组合,实际上,gcd(A[1], D[2], A[3]) = gcd(A[1], D[2], D[3]),因为 A[3] = A[1] + D[2] + D[3],所以 gcd 不受线性组合影响,只要包括所有差分。 更正式地,数学上:gcd(a,b,c) = gcd(a, gcd(b,c)),但更好的是用这个性质:gcd(a1,a2,...,an) = gcd(a1, a2-a1, a3-a2, ..., an-a_{n-1})。 是的,这是一个标准结果:数组的GCD等于第一个元素和所有连续差值的GCD。 证明:设 g = gcd(A[1], A[2]-A[1], A[3]-A[2], ..., A[n]-A[n-1])。 那么 g 整除 A[1],也整除每个差值,所以 g 整除 A[2] = A[1] + (A[2]-A[1]),同样 g 整除 A[3] = A[2] + (A[3]-A[2]),依此类推,所以 g 整除所有 A[i]。 反之,如果 d 整除所有 A[i],则 d 整除 A[1] 和每个 A[i] - A[i-1],所以 d 整除 g。 因此,gcd(A) = gcd(A[1], D[2], D[3], ..., D[n])。 在差分数组中,我们有 D[1] = A[1],D[i] = A[i] - A[i-1] for i>1。 所以,gcd(A[1..n]) = gcd(D[1], D[2], ..., D[n]),因为 D[1] = A[1],而 D[2..n] 是差值。 在定义中,D[1] 是 A[1],D[2] 是 A[2]-A[1],等等,所以是的,整个差分数组 D[1..n] 的 GCD 就是原数组的 GCD。 现在,为什么这有用?因为当我们进行区间加值操作时,对差分数组的影响是局部的。 具体操作:假设我们给区间 [l, r] 加上一个值 c。也就是说,对每个 i 从 l 到 r,A[i] += c。 在差分数组 D 中: - D[l] += c (因为 A[l] 增加了 c,但 A[l-1] 不变,所以 D[l] = A[l] - A[l-1] 增加了 c) - D[r+1] -= c (如果 r+1 <= n,因为 A[r] 增加了 c,但 A[r+1] 不变,所以 D[r+1] = A[r+1] - A[r] 减少了 c) - 对于 l < i <= r,D[i] 不变,因为 A[i] 和 A[i-1] 都增加了 c,所以差值不变。 例如:数组 A = [a1, a2, a3, a4] D = [d1, d2, d3, d4] = [a1, a2-a1, a3-a2, a4-a3] 现在给 [2,3] 加 c:新 A = [a1, a2+c, a3+c, a4] 新 D: d1' = a1 (不变) d2' = (a2+c) - a1 = (a2 - a1) + c = d2 + c d3' = (a3+c) - (a2+c) = a3 - a2 = d3 (不变) d4' = a4 - (a3+c) = (a4 - a3) - c = d4 - c 所以确实,D[l] += c,D[r+1] -= c(如果 r+1 <= n),其他 D[i] 不变。 因此,区间加值操作在差分数组中只影响两个位置:D[l] 和 D[r+1](如果存在)。 现在,原数组的 GCD 是 gcd(D[1], D[2], ..., D[n])。 所以,为了高效计算这个 GCD,在多次区间加值操作后,我可以维护差分数组 D,并维护一个数据结构来高效查询整个 D 数组的 GCD。 查询整个数组的 GCD 是一个范围查询,但 GCD 操作是结合和交换的,所以我可以使用线段树或 Fenwick tree 来支持点更新和范围 GCD 查询。 具体来说: - 初始化:构建差分数组 D,然后构建一个线段树,其中每个叶子存储 D[i],内部节点存储其子节点值的 GCD。 - 当进行一个区间加值操作:更新 D[l] += c,然后更新线段树中位置 l 的值;如果 r+1 <= n,更新 D[r+1] -= c,然后更新线段树中位置 r+1 的值。 - 查询整个数组的 GCD:查询线段树的根节点,即 gcd(D[1..n])。 线段树支持点更新和范围查询,每次更新和查询的时间复杂度是 O(log n)。 构建初始线段树的时间是 O(n)。 现在,有 q 个操作,每个操作是区间加值,然后可能需要查询 GCD。 在用户问题中,似乎是所有操作后查询 GCD,但用户说“多次区间加值操作后”,所以可能是在所有更新后查询一次,或者可能需要在中间查询。但为了高效,数据结构支持实时查询。 在用户描述中,是“在对一个数组进行多次区间加值操作后”,所以可能是在所有更新后计算 GCD。 但为了高效,使用线段树是好的,因为每个更新只影响两个点。 现在,关于边界:当 l=1 或 r=n 时。 如果 l=1,则 D[1] += c(因为 D[1] = A[1],A[1] 增加 c) 如果 r=n,则 D[r+1] 不存在,所以只更新 D[l],不需要更新 D[r+1]。 在差分数组中,D 的长度是 n,索引从 1 到 n。 当更新 [l,r],如果 l=1,更新 D[1] += c 如果 r < n,更新 D[r+1] -= c 如果 r=n,没有 D[r+1],所以只更新 D[l]。 类似地,如果 l=1 且 r=n,则更新 D[1] += c,并且没有 D[r+1]。 现在,整个数组的 GCD 是 gcd(D[1..n])。 在实现中,线段树可以高效处理。 另一个选择是使用 Fenwick tree,但 Fenwick tree 通常用于和,GCD 不是和,但类似地,我们可以实现一个 Fenwick-like 结构用于 GCD,但标准方法是线段树。 GCD 是结合操作,所以线段树是直接的。 现在,在代码中,我需要计算 GCD,这可以用欧几里得算法。 现在,回答用户:我需要用中文解释这个方法。 结构: 1. 介绍问题:给定一个数组,进行多次区间加值操作(每次给一个子区间加一个值),然后整个数组的最大公约数(GCD)。 2. 解释挑战:直接在每个操作后重新计算 GCD 效率低,因为需要 O(n) 时间。 3. 提出高效方法:使用差分数组和线段树。 4. 解释差分数组:定义 D,其中 D[1] = A[1], D[i] = A[i] - A[i-1] for i>1。然后 gcd(A) = gcd(D[1..n])。 5. 解释区间加值对差分数组的影响:只改变 D[l] 和 D[r+1](如果存在),每个点更新。 6. 维护一个线段树:存储 D 数组,支持点更新和范围 GCD 查询。 7. 初始化:构建 D 和线段树,O(n) 时间。 8. 每个区间加值操作:更新两个点(或一个,如果边界),每个更新 O(log n) 时间。 9. 查询 GCD:查询整个范围的 GCD,O(1) 或 O(log n) 时间,取决于实现(线段树查询是 O(log n))。 10. 总结复杂度:有 q 个操作,总时间 O(n + q log n),相比暴力 O(q n) 更好。 11. 可能给出伪代码或代码示例。 用户提到“高效计算”,所以代码示例可能有用。 在系统指令中,有示例使用 Python 代码,所以我可以用 Python 实现一个简单的线段树用于 GCD。 但线段树实现可能有点长,我可以给出框架。 现在,关于引用:用户说“参考站内引用”,但提供的引用是空的:“参考站内引用:。”,所以可能没有具体引用,我需要基于一般知识。 在回答末尾,生成相关问题。 系统指令:生成相关问题,放在最后。 回答结构清晰,逐步解决。 所以,大纲: - 步骤1:理解问题和挑战 - 步骤2:引入差分数组概念 - 步骤3:解释区间加值对差分数组的影响 - 步骤4:提议使用线段树维护GCD - 步骤5:描述算法和复杂度 - 步骤6:伪代码或代码示例 - 步骤7:总结 - 然后相关问题 现在,代码示例:用Python实现一个简单的线段树用于GCD。 GCD函数: def gcd(a, b): if b == 0: return abs(a) # GCD是非负的 return gcd(b, a % b) 但为了效率,迭代版本更好。 在线段树中,每个节点存储其范围的GCD。 点更新:更新一个叶子,然后向上更新父节点。 范围查询:标准方法。 初始化:构建树。 在回答中,我可以给出一个简化版本。 最后,相关问题: 例如: 1. 如何处理动态数组大小(插入和删除)? 2. 如果需要在每次操作后查询GCD,如何优化? 3. 除了GCD,其他聚合函数如最小公倍数(LCM)是否可以用类似方法? 4. 差分数组还可以用于哪些其他区间操作问题? 等等。 现在,开始写回答。</think>### 高效计算区间加值后数组的最大公约数(GCD) 给定一个整数数组,并执行多次“区间加值”操作(每次操作给数组的一个子区间内所有元素加上一个值),在所有操作后,需要高效计算整个数组的最大公约数(GCD)。直接在每个操作后重新扫描整个数组计算 GCD 的时间复杂度为 $O(n)$ 每次操作,当操作次数 $q$ 很大时效率低下(总时间复杂度 $O(qn)$)。本文将介绍一种基于差分数组和线段树的高效方法,将每次操作的时间复杂度降至 $O(\log n)$,总时间复杂度优化为 $O(n + q \log n)$。 #### 步骤1: 理解问题与挑战 - **问题描述**:有一个长度为 $n$ 的整数数组 $A$,初始元素为 $A[1], A[2], \ldots, A[n]$。执行 $q$ 个操作,每个操作指定一个区间 $[l, r]$ 和一个值 $c$,将 $A[l]$ 到 $A[r]$ 的所有元素增加 $c$。所有操作后, $\gcd(A[1], A[2], \ldots, A[n])$。 - **挑战**:数组元素在操作中不断变化,直接计算 GCD 需要遍历整个数组,效率低。需要一种数据结构来维护中间状态,避免全数组扫描。 #### 步骤2: 引入差分数组概念 一个关键洞察是使用差分数组 $D$ 来简化 GCD 计算: - 定义差分数组 $D$: - $D[1] = A[1]$ - $D[i] = A[i] - A[i-1]$ 对于 $i = 2, 3, \ldots, n$ - **重要性质**:整个数组的 GCD 等于差分数组的 GCD: $$ \gcd(A[1], A[2], \ldots, A[n]) = \gcd(D[1], D[2], \ldots, D[n]) $$ 证明:设 $g = \gcd(D[1], D[2], \ldots, D[n])$。由于 $D[1] = A[1]$,且 $A[i] = A[1] + \sum_{k=2}^{i} D[k]$,$g$ 整除所有 $A[i]$。反之,任何整除所有 $A[i]$ 的数也整除所有 $D[i]$。因此,$g$ 是原数组的 GCD[^1]。 此性质将问题转化为维护 $D$ 数组的 GCD。 #### 步骤3: 区间加值对差分数组的影响 当执行一个区间加值操作(给 $[l, r]$ 加 $c$)时,差分数组 $D$ 的变化是局部的: - $D[l] \gets D[l] + c$(因为 $A[l]$ 增加 $c$,$A[l-1]$ 不变) - 如果 $r + 1 \leq n$,则 $D[r+1] \gets D[r+1] - c$(因为 $A[r]$ 增加 $c$,$A[r+1]$ 不变) - 对于其他位置 $i$($i \neq l$ 且 $i \neq r+1$),$D[i]$ 不变。 例如: - 初始数组 $A = [3, 6, 8, 12]$,差分数组 $D = [3, 3, 2, 4]$,$\gcd(A) = \gcd(3,6,8,12) = 1$。 - 操作:给 $[2,3]$ 加 $2$,新 $A = [3, 8, 10, 12]$。 - 新 $D$:$D[2] = 8 - 3 = 5$(原 $3+2=5$),$D[3] = 10 - 8 = 2$(不变),$D[4] = 12 - 10 = 2$(原 $4-2=2$)。所以 $D = [3, 5, 2, 2]$,$\gcd(D) = 1$。 此操作只修改 $D$ 的两个位置(或一个,如果 $r=n$),使更新高效。 #### 步骤4: 使用线段树维护 GCD 为了高效查询整个 $D$ 数组的 GCD,我们使用线段树(Segment Tree)。线段树支持: - **点更新**:修改 $D$ 中单个元素的值。 - **范围查询**:查询任意区间 $[L, R]$ 的 GCD(这里需要整个数组 $[1, n]$ 的 GCD)。 - **时间复杂度**:每次更新或查询 $O(\log n)$。 **线段树设计**: - **结构**:二叉树,每个节点对应一个区间 $[L, R]$,存储该区间的 GCD 值。 - **叶子节点**:存储单个 $D[i]$。 - **内部节点**:存储子节点 GCD 的 GCD,即 $\text{node.gcd} = \gcd(\text{left.gcd}, \text{right.gcd})$。 - **更新**:修改一个叶子后,递归向上更新父节点。 - **查询**:从根节点查询 $[1, n]$ 的 GCD。 #### 步骤5: 算法描述与复杂度 **算法流程**: 1. **初始化**: - 构建差分数组 $D$ 从原数组 $A$:$O(n)$ 时间。 - 构建线段树:递归构造,$O(n)$ 时间。 2. **处理每个操作**(共 $q$ 个): - 对于区间加值 $[l, r]$ 加 $c$: - 更新 $D[l] \gets D[l] + c$。 - 如果 $r + 1 \leq n$,更新 $D[r+1] \gets D[r+1] - c$。 - 在线段树上执行点更新(位置 $l$ 和 $r+1$ 如果存在):每个更新 $O(\log n)$。 3. **查询 GCD**: - 查询线段树根节点的值(即 $\gcd(D[1..n])$):$O(\log n)$ 时间。 **时间复杂度**: - 预处理:$O(n)$。 - 每个操作:$O(\log n)$(最多两次点更新)。 - 总时间:$O(n + q \log n)$。 - 空间复杂度:$O(n)$(存储线段树)。 相比暴力方法($O(qn)$),此方法在 $q$ 大时显著高效。 #### 步骤6: 代码示例(Python 实现) 以下是简化版的 Python 代码。使用迭代 GCD 计算,并实现线段树支持点更新和范围 GCD 查询。 ```python import math class SegmentTree: def __init__(self, data): self.n = len(data) self.size = 1 while self.size < self.n: self.size *= 2 self.tree = [0] * (2 * self.size) # 初始化线段树数组 # 构建叶子节点 for i in range(self.n): self.tree[self.size + i] = data[i] # 构建内部节点 for i in range(self.size - 1, 0, -1): self.tree[i] = math.gcd(self.tree[2*i], self.tree[2*i+1]) def update(self, index, value): # 点更新:位置 index 增加 value idx = self.size + index self.tree[idx] += value while idx > 1: idx //= 2 self.tree[idx] = math.gcd(self.tree[2*idx], self.tree[2*idx+1]) def query_gcd(self, l=0, r=None): # 查询整个数组的 GCD if r is None: r = self.size - 1 l += self.size r += self.size res = 0 while l <= r: if l % 2 == 1: res = math.gcd(res, self.tree[l]) l += 1 if r % 2 == 0: res = math.gcd(res, self.tree[r]) r -= 1 l //= 2 r //= 2 return res def main(): # 示例:初始数组 A,操作后查询 GCD A = [3, 6, 8, 12] # 初始数组 n = len(A) # 构建差分数组 D D = [0] * n D[0] = A[0] for i in range(1, n): D[i] = A[i] - A[i-1] # 初始化线段树 seg_tree = SegmentTree(D) print("初始 GCD:", seg_tree.query_gcd()) # gcd(3,3,2,4)=1 # 执行区间加值操作:给 [2,3] 加 2 (索引从0开始: l=1, r=2) l, r, c = 1, 2, 2 seg_tree.update(l, c) # 更新 D[l] += c if r + 1 < n: # 如果 r+1 不越界 seg_tree.update(r+1, -c) # 更新 D[r+1] -= c # 查询操作后 GCD print("操作后 GCD:", seg_tree.query_gcd()) # gcd(3,5,2,2)=1 if __name__ == "__main__": main() ``` **代码说明**: - 使用 Python 内置 `math.gcd` 计算 GCD(支持非负数,但差分值可能负,取绝对值处理)。 - 线段树查询默认返回整个数组 GCD。 - 实际应用中,需处理边界(如 $r=n$ 时忽略 $D[r+1]$ 更新)。 #### 步骤7: 总结 通过差分数组将区间加值操作转化为点更新,并利用线段树维护 GCD,此方法高效支持动态更新和查询。预处理 $O(n)$,每个操作 $O(\log n)$,适用于大规模数组和频繁操作场景。此技术可扩展到其他区间操作问题,如区间和或最小值查询[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值