【LeetCode & 剑指offer刷题】树题1:二叉树的遍历总结(前序、中序、后序、层序、 之字形层序、垂直遍历)
【LeetCode & 剑指offer 刷题笔记】目录(持续更新中...)
二叉树的遍历总结
(前序、中序、后序、层序、 之字形层序、垂直遍历)
三种递归遍历
//
前序遍历(根-左-右)
void
preorder
(
TreeNode
*
root
,
vector
<
int
>
&
path
)
{
if
(
root
==
nullptr
)
return
;
path
.
push_back
(
root
->
val
);
preorder
(
root
->
left
,
path
);
preorder
(
root
->
right
,
path
);
}
//
中序遍历(左-根-右)
void
inorder
(
TreeNode
*
root
,
vector
<
int
>
&
path
)
{
if
(
root
==
nullptr
)
return
;
inorder
(
root
->
left
,
path
);
path
.
push_back
(
root
->
val
);
inorder
(
root
->
right
,
path
);
}
//
后续遍历(左-右-根)
void
postorder
(
TreeNode
*
root
,
vector
<
int
>
&
path
)
{
if
(
root
==
nullptr
)
return
;
postorder
(
root
->
left
,
path
);
postorder
(
root
->
right
,
path
);
path
.
push_back
(
root
->
val
);
}
非递归遍历
前序遍历:(根 - 左 - 右)
根据前序遍历访问的顺序,优先访问根结点,然后再分别访问左结点和右结点。即对于任一结点,其可看做是根结点,因此可以直接访问,访问完之后,若其左结点不为空,按相同规则访问它的左子树;当访问其左子树时,再访问它的右子树。因此其处理过程如下:
对于任一结点P:
1)访问结点P,并将结点P入栈;
2)判断结点
P的左孩子
是否为空,
若为空
,则取栈顶结点并进行
出栈
操作,并将栈顶结点的
右孩子置为当前的结点P
,循环至1);
若不为空
,则将P的
左孩子置为当前的结点P
;
3)直到P为NULL并且栈为空,则遍历结束。
//
非递归前序遍历
class
Solution
{
public
:
vector
<
int
>
preorderTraversal
(
TreeNode
*
root
)
{
vector
<
int
>
path
;
if
(
root
==
nullptr
)
return
path
;
stack
<
TreeNode
*>
s
;
TreeNode
*
p
=
root
;
while(p || !s.empty())
{
if
(p
)
//
当左结点不为空时
{
path.push_back(p->val); //访问当前结点(父结点)
s
.
push
(
p
);
//
入栈
p
=
p
->
left
;
//
指向下一个左结点
}
else
//
当左结点为空时
{
p
=
s
.
top
();
s
.
pop
();
//
出栈
p
=
p
->
right
;
//
指向右结点
}
}
return
path
;
}
};
中序遍历:(左 - 根 - 右)
根据中序遍历的顺序,对于任一结点,优先访问其左孩子,而左孩子结点又可以看做一根结点,然后继续访问其左孩子结点,直到遇到左孩子结点为空的结点才进行访问,然后按相同的规则访问其右子树。因此其处理过程如下:
对于任一结点P,
1)若其左孩子不为空,则将P入栈并将P的左孩子置为当前的P,然后对当前结点P再进行相同的处理;
2)若其
左孩子为空,则取栈顶元素
并进行出栈操作,
访问该栈顶结点
,然后将当前的
P置为栈顶结点的右孩子
;
3)直到P为NULL并且栈为空则遍历结束
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
//
非递归中序遍历
class
Solution
{
public
:
vector
<
int
>
inorderTraversal
(
TreeNode
*
root
)
{
vector
<
int
>
path
;
if
(
root
==
nullptr
)
return
path
;
stack
<
TreeNode
*>
s
;
TreeNode
*
p
=
root
;
while
(p
||
!
s
.
empty
())
{
if
(p
)
//
当左结点不为空时
{
s
.
push
(
p
);
//
入栈
p
=
p
->
left
;
//
指向下一个左结点
}
else
//
当左结点为空时
{
p
=
s
.
top
();
path.push_back(p->val); //访问栈顶元素(父结点)
s
.
pop
();
//
出栈
p
=
p
->
right
;
//
指向右结点
}
}
return
path
;
}
};
后序遍历:(左 - 右 - 根)
要保证根结点在左孩子和右孩子访问之后才能访问,因此对于任一结点P,先将其入栈。
(1)
如果P不存在左孩子和右孩子
,则可以
直接访问
它;
(2)
或者P存在左孩子或者右孩子
,
但是
其左孩子和右孩子都
已被访问过了
,则同样可以
直接访问该结点
。
(3)
若非上述两种情况
,则将
P的右孩子和左孩子依次入栈
,这样就保证了每次取栈顶元素的时候,左孩子在右孩子前面被访问,左孩子和右孩子都在根结点前面被访问。
vector
<
int
>
postorderTraversal
(
TreeNode
*
root
)
{
vector
<
int
>
result
;
stack
<
TreeNode
*>
s
;
if
(
root
==
nullptr
)
return
result
;
TreeNode
*
p
;
//
当前结点指针
TreeNode
*
pre
=
nullptr
;
//
用于记录上一次访问的结点
s
.push(root); //根结点指针入栈
while(!s.empty())
//不为空时才会入栈,故p不可能为nullptr,无需像之前加p的判断
{
p
= s.top(); // 指向栈顶元素
bool
temp1
=
p
->
left
==
nullptr
&&
p
->
right
==
nullptr
;
//
如果当前结点为叶子结点
bool
temp2
=
pre
!=
nullptr
&&
(
pre
==
p
->
left
||
pre
==
p
->
right
);
//
或者当前结点的左结点和右结点都已被访问过了(若
pre=p->left
说明右结点为空,因为栈中按照根右左这样的顺序入栈,根左这种结构才能出现这种情况)
if
(!
temp1 && !
temp2
)//如果不是上面两种情况,直接入栈
{
//
先将右结点入栈,再将左结点入栈,这样可以保证之后访问时先访问左结点在访问右结点
if
(
p
->
right
)
s
.
push
(
p
->
right
);
//
右结点入栈
if
(
p
->
left
)
s
.
push
(
p
->
left
);
//
左结点入栈
}
else
{
result.push_back(p->val); //访问顺序:左、右、根
s
.
pop
();
pre
=
p
;
//
保存刚刚访问过的结点
}
}
return
result
;
}
如果只是产生后序遍历序列可以用以下方法:
(学习链表用于头部插入的技巧) 严格来说该方法不是按照后序遍历的顺序去访问各结点的
vector
<
int
>
postorderTraversal
(
TreeNode
*
root
)
{
list
<int> temp; //开辟临时链表
stack
<
TreeNode
*>
s
;
//
存储各结点指针
TreeNode
*
p
=
root
;
while
(p
||
!
s
.
empty
())
{
while
(p
)
//
右结点不为空时
{
s
.
push
(
p
);
temp
.
push_front
(
p
->
val
);
//
在头部插入元素,用链表比较好,和前序遍历相反
p
=
p
->
right
;
//
指向下一个右结点,和前序遍历相反
}
if
(!
s
.
empty
())
//
右结点为空时
{
p
=
s
.
top
();
s
.
pop
();
//
出栈
p
=
p
->
left
;
//
指向左结点,和前序遍历相反
}
}
vector
<
int
>
result
;
copy
(
temp
.
begin
(),
temp
.
end
(),
back_inserter
(
result
));
//
将
list
中元素复制到
vector
中
return
result
;
}
参考:
《更简单的非递归遍历二叉树的方法》
《 二叉树的非递归遍历》
leetcode: Preorder, Inorder, and Postorder Iteratively Summarization
其他遍历方式:层序遍历、 之字形层序遍历、垂直遍历
102
.
Binary Tree Level Order Traversal
Given a binary tree, return the
level order
traversal of its nodes' values. (ie, from left to right, level by level).
For example:
Given binary tree
[3,9,20,null,null,15,7]
,
3
/ \
9 20
/ \
15 7
return its level order traversal as:
[
[3],
[9,20],
[15,7]
]
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
//问题:按层次遍历,每层存于vector中
//方法一:递归法 O(n), O(1)
//实际上就是用的前序遍历的思想(前序遍历对每一层而言,靠左的先访问,满足层序遍历规律),区别在于每次递归传入了level信息
class
Solution
{
public
:
vector
<
vector
<
int
>>
levelOrder
(
TreeNode
*
root
)
{
vector
<
vector
<
int
>>
result
;
//
建立可以存放
vector
的空容器
traverse
(
root
,
1
,
result
);
//
从第一层开始遍历
return
result
;
}
void
traverse
(
TreeNode
*
root
,
int
level
,
vector
<
vector
<
int
>>&
result
)
{
if
(
root
==
NULL
)
return
;
//
递归的出口(包括递归子函数的出口)
if
(
level > result.size()
)
{
result
.
push_back
(
vector
<
int
>());
//在下一层时,增加空容器(因为事先不知道树的层数,故要一边遍历,一边增加容器大小)
}
result
[
level -
1
].
push_back
(
root
->
val
);
//
将元素值
push
进第
level
层的容器(索引从
0
开始)
traverse
(
root
->
left
,
level
+
1
,
result
);
traverse
(
root
->
right
,
level
+
1
,
result
);
//
最后一个语句
return
之后,整个递归函数才结束
}
};
/*
迭代法,O(n),O(1) 掌握
层序遍历二叉树是典型的广度优先搜索
BFS
的应用,但是这里稍微复杂一点的是,我们要把各个层的数分开,存到一个二维向量里面
用队列实现
(
1
)首先根结点入队
(
2
)访问队首元素,队首元素出队,若子结点不为空,子结点(下一层的所有结点)入队
(
3
)一层一层的访问,直至队列清空
*/
class
Solution
{
public
:
vector
<
vector
<
int
>
>
levelOrder
(
TreeNode
*
root
)
{
vector
<
vector
<
int
>
>
res
;
if
(
root
==
nullptr
)
return
res
;
queue
<TreeNode*> q;
q
.
push
(
root
);
//
根结点入队
while
(!
q
.
empty
())
{
vector
<
int
>
level
;
int
size
=
q
.
size
();
//当前层的结点数,会随着每层结点的push,长度会变化
for
(
int
i
=
0
;
i
<
size
;
++
i
)
//遍历该层结点,并将下一层结点入队
{
TreeNode
*
node
=
q
.
front
();
level
.push_back(node->val); //访问当前结点
q
.
pop
();
//
出队
//
将当前结点的左右子结点入队
if
(
node
->
left
)
q
.
push
(
node
->
left
);
if
(
node
->
right
)
q
.
push
(
node
->
right
);
//
下一层的结点排在上一层结点之后
}
res
.push_back(level);
}
return
res
;
}
};
103
.
Binary Tree Zigzag Level Order Traversal
Given a binary tree, return the
zigzag level order
traversal of its nodes' values. (ie, from left to right, then right to left for the next level and alternate between).
For example:
Given binary tree
[3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
return its zigzag level order traversal as:
[
[3],
[20,9],
[15,7]
]
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
//分析:广度优先遍历(??感觉遍历顺序是先序遍历,为深度优先遍历),用一个bool记录是从左到右还是从右到左,每一层结束就翻转一下
//用level-order遍历,用奇数层偶数层判断,偶数层时反向存数
class
Solution
{
public
:
vector
<
vector
<
int
>>
zigzagLevelOrder
(
TreeNode
*
root
)
{
vector
<
vector
<
int
>>
result
;
//
创建包含子容器的容器
traverse
(
root
,
1
,
result
);
return
result
;
}
//
递归函数的功能:按
Zigzag Level Order
扫描某一层(第
level
层)的元素,存在一个
vector
里
void
traverse
(
TreeNode
*
root
,
int
level
,
vector
<
vector
<
int
>>&
result
)
{
if
(
root
==
NULL
)
return
;
//
递归子函数和递归母函数的出口
if
(
level
>
result
.
size
())
result
.
push_back
(
vector
<
int
>());
//
增加子容器
//
每一个结点都可以看成本层的根节点,将当前层(第
level
层)的结点元素
push
进
vector
里
if
(
level
%
2
==
1
)
//
奇数层时,从左到右遍历
{
result
[
level
-
1
].
push_back
(
root
->
val
);
}
else
//下一层为偶数层时,反向存数
{
result
[
level
-
1
].
insert
(
result
[
level
-
1
].
begin
(),
root
->
val
);
//
在开头插入数据
}
//
从左到右存数
traverse
(
root
->
left
,
level
+
1
,
result
);
traverse
(
root
->
right
,
level
+
1
,
result
);
}
};
/*
方法二:迭代法
按level order遍历,偶数层时翻转一下(可以用一bool型变量,每一层反号一次)
*/
class
Solution
{
public
:
vector
<
vector
<
int
>>
zigzagLevelOrder
(
TreeNode
*
root
)
{
vector
<
vector
<
int
>
>
res
;
if
(
root
==
nullptr
)
return
res
;
queue
<
TreeNode
*>
q
;
q
.
push
(
root
);
//
根结点入队
bool
right_to_left
=
false
;
while
(!
q
.
empty
())
{
vector
<
int
>
level
;
int
size
=
q
.
size
();
//
当前层的结点数
for
(
int
i
=
0
;
i
<
size
;
++
i
)
//
遍历该层结点,并将下一层结点入队
{
TreeNode
*
node
=
q
.
front
();
level
.
push_back
(
node
->
val
);
//
访问当前结点
q
.
pop
();
//
出队
//
将当前结点的左右子结点入队
if
(
node
->
left
)
q
.
push
(
node
->
left
);
if
(
node
->
right
)
q
.
push
(
node
->
right
);
//
下一层的结点排在上一层结点之后
}
if(right_to_left) reverse(level.begin(), level.end()); //反序
res
.
push_back
(
level
);
right_to_left
= !right_to_left;
}
return
res
;
}
};
Binary Tree Vertical Order Traversal 二叉树的竖直遍历
Given a binary tree, return the vertical order traversal of its nodes' values. (ie, from top to bottom, column by column).
If two nodes are in the same row and column, the order should be from left to right.
Examples:
Given binary tree [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
return its vertical order traversal as:
[
[9],
[3,15],
[20],
[7]
]
Given binary tree [3,9,20,4,5,2,7],
_3_
/ \
9 20
/ \ / \
4 5 2 7
return its vertical order traversal as:
[
[4],
[9],
[3,5,2],
[20],
[7]
]
/*
掌握
问题:二叉树的垂直遍历
方法:
层序遍历,并给每个结点赋上列号
(对于每列元素而言,
层序遍历访问的先后顺序满足垂直遍历规律
)
把根节点给个序号0,然后开始层序遍历,
凡是左子节点则序号减1,右子节点序号加1,
这样我们可以通过序号来把相同列的节点值放到一起
*/
class
Solution
{
public
:
vector
<
vector
<
int
>>
verticalOrder
(
TreeNode
*
root
)
{
vector
<
vector
<
int
>>
res
;
if
(!
root
)
return
res
;
map
<
int
,
vector
<
int
>>
m
;
//
构建存储<序号,遍历序列>对的map
queue
<
pair<int, TreeNode
*>>
q
;
//构建存储<序号,结点>对的队列
q
.
push
({
0
,
root
});
//
根结点入队,根结点序号设为0
while
(!
q
.
empty
())
//
层序遍历
{
auto
a
=
q
.
front
();
m
[a.first
].
push_back
(
a.second->val
);
//
访问当前结点,将结点值
push
到相同列的容器中
q
.
pop
();
//
出队
//
将下一层结点入队
if
(
a
.
second
->
left
)
q
.
push
(
{a.first - 1, a.second->left}
);
//
左结点序号减一
if
(
a
.
second
->
right
)
q
.
push
(
{
a
.
first
+
1
,
a
.
second
->
right
}
);
//
右结点序号加一
//
下一层的结点排在上一层结点之后
}
for
(
auto
mi
:
m
)
//
将
map
中遍历序列按顺序
push
到结果容器中(
map
内部会自动排序,序号从小到大排列遍历序列)
{
res
.
push_back
(
mi.second
);
}
return
res
;
}
};
posted @
2019-01-05 19:25 wikiwen 阅读(
...) 评论(
...) 编辑 收藏