IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 【从零开始】二叉树全攻略(00):遍历方式 -> 正文阅读

[数据结构与算法]【从零开始】二叉树全攻略(00):遍历方式

00 写在前面

二叉树的遍历分为两种,递归和迭代。

递归是不好想但是实现简单的那种,迭代则是符合思考模式却不好实现的那种。

这次总结一下递归,迭代和统一迭代三种方法。

01 二叉树遍历

首先需要知道的是二叉树的遍历顺序,包括前序、中序和后序。这三种遍历的区别在于根节点的位置。

前序遍历是中左右,中序遍历是左中右,后续遍历是左右中。

另外还需要复习一下二叉树节点的结构:

struct TreeNode{
    int val;
    TreeNode *left;
    TreeNode *right;
};

和链表差不多。

02 二叉树递归遍历

递归需要特别注意三个点:

  1. 函数的参数和返回值
  2. 递归终止的条件
  3. 每层的逻辑

前序遍历

先构造函数,输出遍历后的节点,不需要返回值

void PreOrder(TreeNode* cur,vector<int> &vec){
    if(cur==NULL)return;
    
    vec.push_back(cur->val);//中节点
    PreOrder(cur->left,vec);//左孩子
    PreOrder(cur->right,vec);//右孩子
}

首先把中节点存入,再递归遍历左孩子和右孩子。形成中左右的顺序。

这里不使用vector存也可以,直接输出。

中序遍历

中序遍历只需要修改一下遍历顺序即可。形成左中右的顺序。

void InOrder(TreeNode* cur,vector<int> &vec){
    if(cur==NULL)return; 
    
    PreOrder(cur->left,vec);//左孩子
    vec.push_back(cur->val);//中节点
    PreOrder(cur->right,vec);//右孩子
}

后序遍历

后序遍历也只需要修改一下遍历顺序即可。形成左右中的顺序。

void PostOrder(TreeNode* cur,vector<int> &vec){
    if(cur==NULL)return; 
    
    PreOrder(cur->left,vec);//左孩子
    PreOrder(cur->right,vec);//右孩子
    vec.push_back(cur->val);//中节点
}

03 二叉树的迭代遍历

递归遍历很好实现,只需要改动遍历顺序,而迭代实现起来就要复杂一些了。

迭代法实现的具体做法是使用栈来完成遍历操作。

我们可以在遍历时把节点存入,再按照想要遍历的顺序弹出。

前序遍历

PNG图像 27

前序遍历是中左右的顺序,因为要使用栈,所以进栈时应该是右左。

先将当前节点入栈,然后弹出,再将它的右子树和左子树入栈,弹出左子树,再将其右子树和左子树入栈…最后按顺序弹出。

vector<int> PreOrder(TreeNode *root){
    stack<TreeNode*>st;//遍历栈
    vector<int>result;//用于存放结果的vector
    
    if(root==NULL)return result;
    st.push(root);//先把根入栈
    while(!st.empty()){
        TreeNode *node=st.top();//中节点,记录后弹出,再找左右子树
        st.pop();
        result.push_back(node->val);
        if(node->right)st.push(node->right);//先入右子树,这样右子树后出。
        if(node->left)st.push(node->left);//后入左子树,这样左子树先出。
    }
}

中序遍历

中序遍历有一个不同的点,导致它不可以通过前序遍历改改顺序来实现。

我们仔细想想,中序遍历的顺序是左中右,这意味着我们一开始就要到达最左下角并将其弹出,而这时如果使用栈就会无法实现。

所以中序遍历需要额外的指针来进行遍历,栈只负责处理节点。

PNG图像 28

vector<int> InOrder(TreeNode *root){
    stack<TreeNode*>st;//遍历栈
    vector<int>result;//用于存放结果的vector
    
    TreeNode *cur=root;//遍历指针,先指向根
    while(cur!=NULL||!st.empty()){
        if(cur!=NULL){//先要走到最底层,将沿途节点存入栈
            st.push(cur);
            cur=cur->left;
        }
        else{//到达底层,开始弹出,并且将右子树入栈,如果没有就返回上一层父节点,因为父节点已经入栈了。
            cur=st.top();
            st.pop();
            result.push_back(cur->val);
            cur=cur->right;
        }
    }
    return result;
}

这里使用cur是否为空来进行分支判断的设计比较巧妙。

后序遍历

后序遍历的顺序是左右中,如果想要优化结构不使用cur,可以这样实现:先实现先序遍历的中左右顺序,我们可以再调整为中右左,最后将数组反转就可以得到左右中的顺序了。

vector<int> PostOrder(TreeNode *root){
    stack<TreeNode*>st;//遍历栈
    vector<int>result;//用于存放结果的vector
    
    if(root==NULL)return result;
    st.push(root);
    
    while(!st.empty()){
        TreeNode * node=st.top();
        st.pop();
        result.push_back(node->val);
        if(node->left)st.push(node->left);//要想实现先右后左,进栈时需要先左后右。
        if(node->right)st.push(node->right);
        
    }
    reverse(result.begin(),result.end());
    return result;
}

04 二叉树的统一迭代

从上面的迭代遍历我们可以看出,迭代并不能像递归一样修改遍历顺序实现。

但是还是有一种方法可以统一二叉树迭代的写法的,那就是使用空指针。

中序遍历

看一个中序遍历的例子,正确顺序为14256

PNG图像 29

先把根节点入栈,然后在遍历中先弹出目前节点,再按照右中左的顺序入栈(因为中序是左中右)

这里的null是在中节点添加后入栈的,代表此时中节点已经被访问但是没有进行处理。

按此顺序处理后我们可以看到此时栈里面的顺序就是最后的结果了,当然到这里还没有结束。

遇到空节点时,取出非空的栈顶元素,然后计入结果。

vector<int> InOrder(TreeNode * root){
    vector<int>result;
    stack<TreeNode*>st;
    
    if(root==NULL)return result;
    st.push(root);//根节点入栈
    
    while(!st.empty()){
        TreeNode *node=st.top();
        if(node!=NULL){
            st.pop();//先弹出防止重复操作
            if(node->right)
                st.push(node->right);//右孩子入栈
            st.push(node);//中节点入栈
            st.push(NULL);//添加null标记
            if(node->left)
                st.push(node->left);//左孩子入栈
            
        }
        else{//遇到空节点的操作
            st.pop();//首先把空节点弹出
            node=st.top();//取正确的栈顶
            st.pop();
            result.push_back(node->val);
        }
        
    }
    return result;
}

用这种方法就可以统一前序中序和后序遍历了。

前序遍历

前序遍历是中左右,按照入栈顺序为右左中。

vector<int> InOrder(TreeNode * root){
    vector<int>result;
    stack<TreeNode*>st;
    
    if(root==NULL)return result;
    st.push(root);//根节点入栈
    
    while(!st.empty()){
        TreeNode *node=st.top();
        if(node!=NULL){
            st.pop();//先弹出防止重复操作
            if(node->right)
                st.push(node->right);//右孩子入栈
            if(node->left)
                st.push(node->left);//左孩子入栈
            
            st.push(node);//中节点入栈
            st.push(NULL);//添加null标记
            
        }
        else{//遇到空节点的操作
            st.pop();//首先把空节点弹出
            node=st.top();//取正确的栈顶
            st.pop();
            result.push_back(node->val);
        }
        
    }
    return result;
}

后序遍历

后序遍历是左右中,按照入栈顺序为中右左。

vector<int> InOrder(TreeNode * root){
    vector<int>result;
    stack<TreeNode*>st;
    
    if(root==NULL)return result;
    st.push(root);//根节点入栈
    
    while(!st.empty()){
        TreeNode *node=st.top();
        if(node!=NULL){
            st.pop();//先弹出防止重复操作

            
            st.push(node);//中节点入栈
            st.push(NULL);//添加null标记
            
            if(node->right)
                st.push(node->right);//右孩子入栈
            if(node->left)
                st.push(node->left);//左孩子入栈
            
        }
        else{//遇到空节点的操作
            st.pop();//首先把空节点弹出
            node=st.top();//取正确的栈顶
            st.pop();
            result.push_back(node->val);
        }
        
    }
    return result;
}
if(node->left)
            st.push(node->left);//左孩子入栈
        
    }
    else{//遇到空节点的操作
        st.pop();//首先把空节点弹出
        node=st.top();//取正确的栈顶
        st.pop();
        result.push_back(node->val);
    }
    
}
return result;

}

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-05-10 12:08:32  更:2022-05-10 12:09:30 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 3:54:29-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码