【二叉树遍历总结】C++实现

说明:

  1. 总结了二叉树的3种遍历方式(先序、中序、后序)的不同实现
    • 递归方法实现
    • 迭代方法实现
  2. 用不同的数据结构定义二叉树
    • 结构体定义树结点
    • 用数组定义二叉树
  3. 文章内容为个人的学习总结,如有错误,欢迎指正

文章目录

  • 1. 二叉树结构体定义
    • 1.1 递归方法遍历
      • 1.1.1 先序遍历
      • 1.1.2 中序遍历
      • 1.1.3 后序遍历
    • 1.2 迭代方法遍历
      • 1.2.1 先序遍历
      • 1.2.2 中序遍历
      • 1.2.3 后序遍历
  • 2. 二叉树数组定义
    • 2.1 递归方法遍历
      • 2.1.1 先序遍历
      • 2.1.2 中序遍历
      • 2.1.3 后序遍历
    • 2.2 迭代方法遍历
      • 2.2.1 先序遍历
      • 2.2.2 中序遍历
      • 2.2.3 后序遍历
  • 3. 代码说明
  • 4. LeetCode相关题目

1. 二叉树结构体定义

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

1.1 递归方法遍历

1.1.1 先序遍历

//先序遍历
    void travelsal(TreeNode *T, vector<int>& vec){
        if(T==NULL) return;//递归返回条件
        vec.push_back(T->val);
        travelsal(T->left, vec);
        travelsal(T->right, vec);
    }
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> vec;
        travelsal(root, vec);
        return vec;
    }

1.1.2 中序遍历

//递归法中序遍历
    //递归遍历函数
    void travelsal(TreeNode* cur, vector<int>& result){
        if(cur == NULL) return; //结点为空时退出递归
        travelsal(cur->left, result);//递归处理左子树
        result.push_back(cur->val);
        travelsal(cur->right, result);
    }
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> result;
        if(root==NULL) return result;
        travelsal(root, result);
        return result;
    }

1.1.3 后序遍历

//递归法实现后序遍历
    //遍历函数
    void travelsal(TreeNode* cur, vector<int>& result){
        if(cur == NULL) return;//递归返回条件
        travelsal(cur->left, result);
        travelsal(cur->right, result);
        result.push_back(cur->val);
    }
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> result;
        if(root == NULL) return result;
        travelsal(root, result);
        return result;
    }

1.2 迭代方法遍历

1.2.1 先序遍历

//迭代实现先序遍历
    vector<int> preorderTraversal(TreeNode* root) {
        stack<TreeNode*> st; //定义栈,保存遍历过程中的结点
        vector<int> result; //最终的先序遍历序列
        if(root == NULL) return result;
        st.push(root);
        while(!st.empty()){//当栈非空时,依次处理栈中元素
            TreeNode* cur = st.top();//取栈顶元素
            st.pop();//弹出栈顶元素
            result.push_back(cur->val);//将结点值放入遍历序列中
            if(cur->right != NULL) st.push(cur->right);//先将右孩子结点入栈,这样才能保证出栈顺序是NLR
            if(cur->left != NULL) st.push(cur->left);
        }
        return result;
    }

1.2.2 中序遍历

//迭代法实现中序遍历
/*中序遍历时,访问的结点和处理的结点不同,因此不能简单套用先序遍历迭代法的逻辑
*/
    vector<int> inorderTraversal(TreeNode* root) {
        stack<TreeNode*> st;
        vector<int> result;
        TreeNode* cur = root; //通过cur访问结点
        
        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;
    }

1.2.3 后序遍历

  • 方法1:修改迭代先序遍历的代码顺序,得到NRL的序列,最后再整体翻转得到LRN
//迭代法实现后序遍历
    //方法1:修改迭代先序遍历的代码顺序,得到NRL的序列,最后再整体翻转得到LRN
    vector<int> postorderTraversal(TreeNode* root) {
        stack<TreeNode*> st;
        vector<int> result;
        if(root == NULL) return result;
        st.push(root);
        while(!st.empty()){
            TreeNode* cur = st.top();
            st.pop();
            result.push_back(cur->val);//中
            if(cur->left != NULL) st.push(cur->left);//这里要先将左孩子结点入栈,因为要得到NRL的序列,就要左孩子入栈,这样才能得到NRL的出栈序列
            if(cur->right != NULL) st.push(cur->right);
        }
        reverse(result.begin(), result.end());
        return result;
    }
  • 方法2:按照LRN的顺序进行遍历访问
//迭代方法2:
    vector<int> postorderTraversal(TreeNode* root) {
        stack<TreeNode*> st;
        vector<int> result;
        if(root == NULL) return result;
        TreeNode* cur = root; //指针遍历结点
        TreeNode* pre = NULL; //用于保存上一次<处理>的结点
        while(cur!=NULL || !st.empty()){
            if(cur != NULL){
                st.push(cur);
                cur = cur->left;//一直遍历到最左最底
            }else{
                cur = st.top();//获取栈顶元素,对该结点继续判断
                // st.pop(); //但是此时先不要弹出该结点,因为还要对其进行判断
                //当前取到的元素一定是左孩子为空的,或左孩子已经被遍历到的
                if(cur->right != NULL && pre != cur->right){//若该结点有右孩子,且右孩子没有被处理过        
                    cur = cur->right;                
                } 
                else {
                    //处理该结点
                    st.pop();//将该结点从栈顶弹出
                    pre = cur;//将pre(保存上一次处理的结点)置为cur
                    result.push_back(cur->val);//将该结点值放入遍历序列中
                    cur = NULL; //更新cur,将cur置为NULL是为了避免重复访问cur的左孩子,因为在栈中的结点,其左孩子一定已经被遍历过了,且放入过栈中了
                }                    
            }
        }
        return result;  
    }

2. 二叉树数组定义

//用数组定义二叉树用-1表示空结点
//例如:
vector<int> tree = {1,-1,2,-1,-1,-1,3};

2.1 递归方法遍历

2.1.1 先序遍历

//result存储先序遍历的结果
void preorderTraversal(vector<int>& tree, int cur_index, vector<int>& result){
    if(cur_index >= tree.size()|| tree[cur_index]==-1) return; //递归返回条件
    result.push_back(tree[cur_index]);
    preorderTraversal(tree, cur_index*2+1, result); //遍历左子树
    preorderTraversal(tree, cur_index*2+2, result); //遍历右子树
}

2.1.2 中序遍历

void inorderTravelsal(vector<int>& tree, int cur_index, vector<int>& result){
    if(cur_index >= tree.size() || tree[cur_index]==-1) return;
    inorderTravelsal(tree, cur_index*2+1, result);
    result.push_back(tree[cur_index]);
    inorderTravelsal(tree, cur_index*2+2, result);
}

2.1.3 后序遍历

void postorderTravelsal(vector<int>& tree, int cur_index, vector<int>& result){
    if(cur_index >= tree.size() || tree[cur_index] == -1) return; //递归返回条件
    postorderTravelsal(tree, cur_index*2+1, result);
    postorderTravelsal(tree, cur_index*2+2, result);
    result.push_back(tree[cur_index]);
}

2.2 迭代方法遍历

与结构体定义的二叉树不同,在对数组定义的二叉树采用迭代方法进行遍历时,要通过下标确定左右孩子,因此在遍历时多设置了一个栈用于保存结点的下标。

2.2.1 先序遍历

//先序遍历
void preorder(vector<int>& tree, vector<int>& result){
    if(tree.size() == 0) return;

    stack<int> st_node; //保存结点
    stack<int> st_index; //保存结点下标    
    int cur_node = tree[0];
    int cur_index = 0;
    st_node.push(cur_node);
    st_index.push(cur_index);
    
    while(!st_node.empty()){
        cur_node = st_node.top(); st_node.pop();
        cur_index = st_index.top(); st_index.pop();
        result.push_back(cur_node); //N

        if(cur_index*2+2 < tree.size() && tree[cur_index*2+2] != -1) {            
            st_node.push(tree[cur_index*2+2]); //注意要先将右孩子入栈
            st_index.push(cur_index*2+2);
        }
        if(cur_index*2+1 < tree.size() && tree[cur_index*2+1] != -1){           
            st_node.push(tree[cur_index*2+1]);
            st_index.push(cur_index*2+1);
        }   
    }
}

2.2.2 中序遍历

//中序遍历
void inorder(vector<int>& tree, vector<int>&result){
    if(tree.size() == 0) return;

    stack<int> st_node;
    stack<int> st_index;
    int cur_node = tree[0];
    int cur_index = 0;
    while((cur_index<tree.size() && tree[cur_index]!=-1) || !st_node.empty()){
        if(cur_index < tree.size() && tree[cur_index] != -1){
            st_node.push(tree[cur_index]);
            st_index.push(cur_index);
            cur_index = cur_index*2+1; //左
        }else{            
            cur_index = st_index.top(); st_index.pop();
            cur_node = st_node.top(); st_node.pop();
            
            result.push_back(cur_node); //中            
            cur_index = cur_index*2+2; //右
        }        
    }    
    return;
}

2.2.3 后序遍历

  • 方法1:修改先序遍历代码的顺序,获得NRL序列,再对result进行翻转得到LRN
void postorder_1(vector<int>& tree, vector<int>& result){
    if(tree.size() == 0) return;

    stack<int> st_node;
    stack<int> st_index;
    int cur_index = 0;
    int cur_node = tree[0];
    st_node.push(cur_node);
    st_index.push(cur_index);
    while(!st_node.empty()){
        cur_node = st_node.top(); st_node.pop();
        cur_index = st_index.top(); st_index.pop();
        result.push_back(cur_node);

        if(cur_index*2+1 < tree.size() && tree[cur_index*2+1] != -1){
            st_node.push(tree[cur_index*2+1]);
            st_index.push(cur_index*2+1); //注意要先将左孩子结点入栈,因为要得到的是NRL的出栈序列
        }
        if(cur_index*2+2 < tree.size() && tree[cur_index*2+2] != -1){
            st_node.push(tree[cur_index*2+2]);
            st_index.push(cur_index*2+2);
        }
    }
    reverse(result.begin(), result.end());
}
  • 方法2:按照LRN的顺序进行遍历
void postorder_2(vector<int>& tree, vector<int>& result){
    if(tree.size() == 0) return;

    stack<int> st_node;
    stack<int> st_index;
    int cur_node = tree[0];
    int cur_index = 0;
    int pre_node = -1;
    int pre_index = -1;

    while((cur_index<tree.size() && tree[cur_index]!=-1) || !st_node.empty()){
        if(cur_index<tree.size() && tree[cur_index]!= -1){
            st_node.push(tree[cur_index]);
            st_index.push(cur_index);
            cur_index = cur_index*2+1;
        }else{
            cur_node = st_node.top();//先不弹出
            cur_index = st_index.top();
            //判断是沿着该结点继续向下遍历,还是处理该结点
            if(cur_index*2+2 < tree.size() && tree[cur_index]!=-1 && pre_node != tree[cur_index*2+2]){
                //继续相下遍历
                cur_index = cur_index*2+2;
            }else{//处理该结点
                st_node.pop();
                st_index.pop();//将该结点和结点下标从栈中弹出
                pre_node = cur_node;//这个好像没啥用
                pre_index = cur_index; 
                result.push_back(cur_node);
                cur_index = tree.size()+1; //更新cur_index,这一步是为了防止重复方位cur_node的左孩子。因为在栈中的结点,其左孩子一定已经被遍历过了,且放入过栈中了
            }
        }
    }
}

3. 代码说明

  1. 用结构体定义的二叉树的三种遍历(包括递归方法和迭代方法),这些函数均通过LeetCode的测试(相关题目在4. 中给出)
  2. 用数组定义的二叉树的三种遍历(包括递归遍历和迭代遍历),这部分函数在本地VS Code进行测试,测试样例包括:
{1,-1,2,-1,-1,-1,3}
{1,2,-1,3}
{1,2,3,-1,5}
{1,2,3,4,5}

4. LeetCode相关题目

144. 二叉树的前序遍历
94. 二叉树的中序遍历
145. 二叉树的后序遍历

你可能感兴趣的:(刷题,c++,二叉树遍历)