二叉树最大宽度_深度优先方式_20230520
- 前言
给定一颗二叉树,求解其最大宽度。定义每层宽度为该层最左和最右之间的长度,也即左右两个端点之间的所跨越的长度,两个端点直接可能会包含一些延伸到本层的空节点,这些空节点的长度由于占据了宽度符,所以也计入长度范围内。
此问题的常规解法包含广度优先和深度优先两种方式,每种方式都需要对二叉树结点重新进行线性化编号,类似Heap结点的编号规则,定义根节点的编号为index, 那么其直接左孩子结点的编号为2*index,同理其直接右孩子结点编号为2*index+1。
- 深度优先遍历
深度优先遍历,顾名思义,访问节点过程中,先从深度方向上对节点进行遍历,通过不断递归,最终访问所有的节点,要用深度优先方式求解此题,先看一个最基础的例子。以三个节点二叉树为例进行思路的阐述。
要求解树的宽度,先访问节点③,节点③的宽度为1;然后访问节点⑤,节点⑤的宽度也为1;最后对节点⑦进行访问,节点7的宽度为2. 在深度优先遍历过程中,需要比较根节点,左孩子节点以及右子树节点的宽度,选择三个中的最大值,赋予根节点,然后再不断的递归回退。
r
o
o
t
_
w
i
d
t
h
=
M
A
X
(
r
o
o
t
_
w
i
d
t
h
,
l
e
f
t
_
c
h
i
l
d
_
w
i
d
t
h
,
r
i
g
h
t
_
c
h
i
l
d
_
w
i
d
t
h
)
root\_width=MAX(root\_width,left\_child\_width,right\_child\_width)
root_width=MAX(root_width,left_child_width,right_child_width)
最后求得根结点代表的子树的最大宽度值为2。
如果把上述例子作为求解的最基础的实例,那么我们再将上述公式进行一般化处理,再看一个具体的例子。
如果采用递归遍历,按照前面的例子,完成结点③代表的左子树的最大宽度求解后,结点③代表的左子树的最大宽度值为2。
然后再对结点②代表的右子树的最大宽度进行求解,结点②本身的宽度为2,其左子树为空,那么默认其宽度为0,其右子树⑨的宽度为4,利用上述公式,以结点②为基础的右子树的最大宽度值为4。
再接着回退,对根节点①,其左结点③和右结点②进行宽度最大值的求解,最终根节点最大宽度更新为4。
在深度优先遍历过程中,面临的挑战之一为如何求解层中每个结点相对于左端点的最大宽度,深度优先遍历对每层中结点的访问次序并不是连续的,无法在遍历过程中,直接利用其前置结点的值。这种情况下,就需要对每次的最左侧的非空结点的位置进行储存记忆,同时在递归函数中需要追踪目前处理的结点在哪一层上,方便查询并求出宽度。
如果以上面的二叉树为例,那么我们需要记录第一层结点的①min_depth[1]的值为1,第二层结点③的min_depth[2]为2,第三层结点⑤的min_depth[3]为4,这三个值一旦确定,后续遍历过程中便不再进行更新,需要通过当前深度进行查询,然后和当前结点位置进行比较,最终求出其最大的宽度值。
以结点⑨为例,它本身的计算结点编号为7(2*3+1),它所在的层数 (深度)为3,通过数组查询min_depth[3]为4,那么结点⑨的最大宽度值:
M
a
x
_
w
i
d
t
h
o
f
n
o
d
e
⑨
=
7
−
4
+
1
=
4
Max\_width\ of\ node\ ⑨ = 7-4+1=4
Max_width of node ⑨=7−4+1=4
如果二叉树的深度比较小,那么可以采用普通的数组进行储存与查询,规定数组中的初值为-1,当深度遍历第一次访问到左节点的时候,更新其值为正确的值,后续对于相同的深度的节点则不再进行更新。
- 程序实现
基于上面的分析,则程序的实现就比较简单,主要是在递归回退过程中,对左子树、右子树和根的宽度进行比较,返回最大值即可,这种递归过程可以直观理解为层层返回,根据条件对值进行更改,最终在总的根节点上求出问题的答案。
由于需要追踪访问过程中的每个节点所在的层数(深度)和其线性化的编号,所以递归函数的设计,不仅仅需要节点,还需要记录层数的变量depth和线性化编号的变量index。
为了简化递归函数,我们定义记忆数组min_depth[]为全局变量,并在递归第一次到达当前层的时候,对数组的内容给予更新,同时利用init_array函数对其所有元素赋初值为-1。
3.1 头文件定义
/**
* @file max_width.h
* @author your name (you@domain.com)
* @brief
* @version 0.1
* @date 2023-05-19
*
* @copyright Copyright (c) 2023
*
*/
#ifndef MAX_WIDTH_H
#define MAX_WIDTH_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#define MAX_DEPTH 10
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int min_index[MAX_DEPTH];
typedef struct BiTNode
{
int val;
struct BiTNode *lchild;
struct BiTNode *rchild;
} BiTNode, *BiTree;
/*
利用先序遍历建立二叉树,如果值为-1,则默认为NULL
*/
void create_bitree(BiTree *T, FILE *fp);
/*
初始化min_depth数组所有元素为-1
*/
void init_array(int *arr,int n);
/**
* @brief
* By using DFS to find the solution to maximum width of binary tree
* it will return the maximum widhth of (root_width, left_child_width,right_child_widht)
* @param root Root node
* @param depth Indidctor of depth of current node
* @param index Linear sequence index of current node(similar to heap number)
* @return int Return the max width of whole tree
*/
int max_width_of_binary_tree(BiTree root, int depth, int index);
#endif
3.2 关键函数的实现
/**
* @file max_width.c
* @author your name (you@domain.com)
* @brief
* @version 0.1
* @date 2023-05-19
*
* @copyright Copyright (c) 2023
*
*/
#ifndef MAX_WIDTH_C
#define MAX_WIDTH_C
#include "max_width.h"
int max_width_of_binary_tree(BiTree root, int depth, int index)
{
if(root==NULL)
{
return 0;
}
int root_width;
int l_width=0;
int r_width=0;
if (min_index[depth]==-1)
{
min_index[depth]=index;
}
root_width = index - min_index[depth] + 1;
if(root->lchild)
{
l_width=max_width_of_binary_tree(root->lchild,depth+1,index*2);
}
if(root->rchild)
{
r_width=max_width_of_binary_tree(root->rchild,depth+1,index*2+1);
}
root_width=MAX(MAX(root_width,l_width),r_width);
return root_width;
}
void create_bitree(BiTree *T, FILE *fp)
{
int val;
// printf("Please input the integer\n");
fscanf(fp, "%d", &val);
if (val == -1)
{
*T = NULL;
}
else
{
*T = (BiTree)malloc(sizeof(BiTNode));
(*T)->val = val;
create_bitree(&((*T)->lchild), fp);
create_bitree(&((*T)->rchild), fp);
}
return;
}
void init_array(int *arr, int n)
{
int i;
for(i=0;i<n;i++)
{
*(arr+i)=-1;
}
}
#endif
3.3 测试函数
/**
* @file max_width_main.c
* @author your name (you@domain.com)
* @brief
* @version 0.1
* @date 2023-05-19
*
* @copyright Copyright (c) 2023
*
*/
#ifndef MAX_WIDTH_MAIN_C
#define MAX_WIDTH_MAIN_C
#include "max_width.c"
int main(void)
{
BiTree root;
int max_width;
FILE *fp;
fp = fopen("data.txt", "r");
create_bitree(&root, fp);
init_array(min_index,MAX_DEPTH);
max_width=max_width_of_binary_tree(root,1,1);
printf("The max_width is %d\n the action had been done\n",max_width);
getchar();
fclose(fp);
return EXIT_SUCCESS;
}
#endif
测试数据data.txt
1
3
5
-1
-1
7
-1
-1
2
-1
9
-1
-1
- 小结
利用深度优先遍历求解二叉树的最大宽度过程是不断对当前根、左孩子及有孩子的宽度求最大值的过程,求解过程部分属于后续遍历,此时左、右两个孩子的宽度都已经被函数栈弹回值l_width和r_width两个变量中,它们需要和根节点的宽度(root_width)进行比较,然后把最大值赋给给节点。
这个过程和线段树求解最大值的过程有点类似,有时候可以形象理解为,函数栈的值在弹出过程中,根据条件进行计算,然后再把新的值传递给上一层根节点,直至求解出最终的结果。
参考资料:
662. 二叉树最大宽度 - 力扣(Leetcode)