如上图二叉树的遍历主要思想就是通过递归方式进行遍历,同时如果要非递归遍历的话,一般情况下,深度优先遍历需要借助stack保存中间变量的方式进行遍历,广度优先遍历的话需要借助queue来保存每一层变量的方式进行遍历。
1、深度优先遍历-前序遍历
1.1、递归
递归的思路遍历:
1、递归结束的条件就是root==nullptr
2、注意这个函数有一个返回值,这个递归代码怎么具体写呢?
/* 这里需要定一个函数,因此输出函数是有个vector<int>返回值的,所以需要另外写一个函数*/
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;
preorder(root,result);
return result;
}
void preorder(TreeNode* root,vector<int> &result){
if(root == nullptr){
return;
}
result.push_back(root->val);
preorder(root->left,result);
preorder(root->right,result);
}
1.2、借助stack非递归
/*思路
1、借用stack
2、在压最左边栈的同时需要把其节点也输出。
3、然后再把指针指向右边节点,然后在循环。
*/
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;
if(!root){
return result;
}
stack<TreeNode*> s;
TreeNode *cur = root;
while(!s.empty()|| cur){
while(cur){
result.push_back(cur->val);
s.push(cur);
cur = cur->left;
}
/*退出则说明左边已经放完了,此时需要弹出栈顶元素,同时查看他的右节点,然后在继续循环*/
cur = s.top();
s.pop();
cur = cur->right;
}
return result;
}
2、深度优先遍历-中序遍历
2.1、递归
/* 这里需要定一个函数,因此输出函数是有个vector<int>返回值的,所以需要另外写一个函数*/
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;
preorder(root,result);
return result;
}
void preorder(TreeNode* root,vector<int> &result){
if(root == nullptr){
return;
}
preorder(root->left,result);
result.push_back(root->val);
preorder(root->right,result);
}
2.2、借助stack非递归
/*思路:
1、用栈的方式来处理,不用递归。
2、栈的思想就是,不需要提前把root节点压入栈,而是在循环中才压入。同时因为是中序遍历,所以最先压栈的是按理说是右,再是root,再是左。但是这里在循环过程中,需要从root开始判断压入栈的左子树是否为空,不为空则一直把左边节点压入栈,也就是按照树的最左边从root到业务节点压入栈。此时叶子节点左子树为空,则此时弹出stack top元素,写入result数组中,此时弹出的元素就是最左边的叶子节点数据。然后此时需要拿着这个节点看看他的右节点。如果不为空,则需要把他的右节点压栈,并且指针指向他右节点的左孩子。如果右节点为空,则继续弹出stack top元素,写入result中,此时弹出的则是数的左边那条线倒数第二个节点。如果右节点存在,则将右节点压入栈,同时下一个指针指向来右节点的左节点,如果左节点为空,则弹出stack top元素(此时弹出的就是这个右节点)。指针就系指向p->right。此时为空则继续弹出stack。。。。循环继续。直到stack为空&&p指针同时为空,则说明占用没有来数据同时把树已经遍历完成(p==nulptr)
*/
/*思路:非递归。
1、用一个栈,但是不会想广度优先算法一样先把root放在queue中
2、把左子树一直放在stack中,直到坐左边的节点
3、然后为空周,再把右子树放进去,循环判断。*/
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode *> s;
if(!root){
return result;
}
TreeNode *p = root;
/*注意里面不需要临时节点指针了。直接用p就行了。*/
while(!s.empty()||p){
/*这里是把最左边全部压入栈,直到最左边的叶子节点*/
while(p){
s.push(p);
p = p->left;
}
/*把左边全部压入栈,直到最左边的节点压完,此时弹出坐左边节点。*/
p = s.top();
s.pop();
result.push_back(p->val);
/*再看看这个最左边节点的右节点是否为空
为空,继续弹出剩下的最左边,否则将右节点对应的最左边全部压入栈,然后在重复上面的循环*/
p = p->right;
}
return result;
}
3、深度优先遍历-后序遍历
3.1、递归
/* 这里需要定一个函数,因此输出函数是有个vector<int>返回值的,所以需要另外写一个函数*/
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;
preorder(root,result);
return result;
}
void preorder(TreeNode* root,vector<int> &result){
if(root == nullptr){
return;
}
preorder(root->left,result);
preorder(root->right,result);
result.push_back(root->val);
}
3.2、借助stack非递归
/*思路:
1、非递归方式通过stack,从右边一次放入stack,直到没有,则指针指向左子树,然后在循环放入栈,放入结果中,然后再把结果数组反序即可。*/
vector<int> postorderTraversal(TreeNode* root) {
vector<int> result;
if(!root){
return result;
}
stack<TreeNode*> s;
TreeNode *cur = root;
while(!s.empty()||cur){
while(cur){
result.push_back(cur->val);//输出
s.push(cur);//压站
cur = cur->right;
}
/*最右边压站完成,弹出顶元素,然后指向他的左孩子,继续循环压站*/
cur = s.top();
s.pop();
cur = cur->left;
}
reverse(result.begin(),result.end());
return result;
}
4、广度优先遍历
4.1、递归
4.2、借助stack非递归
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> result;
if(!root){
return result;
}
queue<TreeNode *> q;
q.push(root);
while(!q.empty()){
int len = q.size();
vector<int> value;
for(int i = 0; i < len; i++){
TreeNode *node = q.front();
q.pop();
if(node){
value.push_back(node->val);
q.push(node->left);
q.push(node->right);
}
}
if(value.size()){
result.push_back(value);
}
}
return result;
}
5、总结
深度优先遍历一般都会用stack实现,而广度优先算法一般都会用queue来实现。同时掌握来这些之后,通过这些原理就会衍生出需要有趣的题。而且二叉树的广度优先遍历应用比较广。比如按层打印每个节点,按照二叉树的层次Z字型打印节点,打印从二叉树右侧看到的二叉树的最右边节点。打印从二叉树最左侧看到的二叉树的节点。求二叉树的深度,最大深度,最小深度等等。这些都是可以在掌握了二叉树遍历方式后可以解决的。