算法训练营Day14 第六章 二叉树part1

第一部分: 理论基础 

①二叉树的种类:做题过程中,最常见的二叉树包括满二叉树完全二叉树、二叉搜索树以及平衡二叉搜索树

        Ⅰ、满二叉树:如果⼀棵⼆叉树只有度为0的结点和度为2的结点,并且度为0的结点在同⼀层 上,则这棵⼆叉树为满⼆叉树。满二叉树的深度为k,则有2^{k-1}个节点。满二叉树如下图所示:

        Ⅱ、完全二叉树:除最底层节点可能没填满外,其余每层节点数都达到最大值,底层节点都连续集中在该层最左边的若⼲位置。若最底层为第 h 层,则该层包含 1~ 2^{h-1}个节点。

        Ⅲ、二叉搜索树:二叉搜索树满足以下条件,若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;若它的右子树不空,则右⼦树上所有结点的值均大于它的根结点的值; 二叉搜索树的左、右子树也分别为⼆叉搜索树。下面两棵树都属于二叉搜索树。

        Ⅳ、平衡二叉搜索树:又称为AVL(Adelson-Velsky and Landis)树,具有以下性质:它是 ⼀棵空树或它的左右两个子树的⾼度差的绝对值不超过1,并且左右两个子树都是⼀棵平衡⼆叉树。

②二叉树的存储方式:可以是链式存储也可以是线性存储的。链式存储就类似于链表结构,有一个val,两个指针分别指向左子节点和右子节点;线性存储可以放到数组中,对于数组中的第i个元素,他的左子树下表为2*i+1,右子树下标为2*i+2。

③二叉树的遍历方式:遍历方式可以分为深度优先遍历和广度优先遍历两大类。深度优先遍历中又可以分为先序遍历、中序遍历和后序遍历的方式。

④二叉树的定义:链式存储的⼆叉树节点的定义方式用代码表示如下:

class TreeNode{
public:
    int val;
    TreeNode* left;
    TreeNode* right;
    
public:
    TreeNode(int x): val(x), left(nullptr), right(nullptr) {}
};

第二部分: 递归遍历

递归方法是必须掌握的一种方法,递归法的步骤可以总结为以下三步(递归三部曲):

        Ⅰ、确定函数的参数与返回值;

        Ⅱ、确定递归终止条件;

        Ⅲ、确定单层递归逻辑。

明确了这三个步骤,再写递归的时候,思路就清晰很多了,下面以二叉树的前序遍历、中序遍历以及后序遍历为例,演示递归写法,代码如下:

首先是二叉树的前序遍历(递归方法)代码:

class Solution {
public:
    vector<int> result;
    vector<int> preorderTraversal(TreeNode* root) {
        if(!root) return result;
        result.push_back(root->val);
        preorderTraversal(root->left);
        preorderTraversal(root->right);
        return result;
    }
};

接下来,二叉树的中序遍历(递归方法)代码:

class Solution {
public:
    vector<int> result;
    vector<int> inorderTraversal(TreeNode* root) {
        if(!root) return result;
        inorderTraversal(root->left);
        result.push_back(root->val);
        inorderTraversal(root->right);
        return result;
    }
};

最后,是二叉树的后续遍历(递归方法)代码:

class Solution {
public:
    vector<int> result;
    vector<int> postorderTraversal(TreeNode* root) {
        if(!root) return result;
        postorderTraversal(root->left);
        postorderTraversal(root->right);
        result.push_back(root->val);
        return result;
    }
};

观察上面三种不同的遍历方法,代码是十分相似的,只是单层递归逻辑存在差异,顺序不同而已。

第三部分:迭代遍历

        迭代法需要通过stack这种数据结构来完成,前序遍历(中左右)和后序遍历(左右中)的写法比较接近,后序遍历可以在前序遍历的基础上稍作修改来完成,而中序遍历写法与上述两者有一定差异。

        前序遍历的迭代算法代码如下:

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        stack<TreeNode*> st;
        vector<int> result;
        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);             // 左(空节点不入栈)
        }
        return result;
    }
};

        后序遍历的迭代算法代码可以在前序遍历的基础上进行修改,给出代码:

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        stack<TreeNode*> st;
        vector<int> result;
        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;
    }
};

        中序遍历的迭代法则有所不同,其写法与上述两者存在一定的差异,代码如下:

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        TreeNode* cur = root;
        while (cur != NULL || !st.empty()) {
            if (cur != NULL) { // 指针来访问节点,访问到最底层
                st.push(cur); // 将访问的节点放进栈
                cur = cur->left;                // 左
            } else {
                cur = st.top(); // 从栈里弹出的数据,就是要处理的数据(放进result数组里的数据)
                st.pop();
                result.push_back(cur->val);     // 中
                cur = cur->right;               // 右
            }
        }
        return result;
    }
};

第四部分: 统一迭代 

        其实针对上述三种遍历方式,使用迭代法可以写出统一风格的代码!之前提到使用栈的话,无法同时解决访问节点(遍历节点)和处理节点(将元素放进结果集)不一致的情况那我们就将访问的节点放入栈中,把要处理的节点也放入栈中但是要做标记。如何标记呢,就是要处理的节点放入栈之后,紧接着放入一个空指针作为标记。 这种方法也可以叫做标记法。

       

        中序遍历可以写成:

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        if (root != NULL) 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); // 中节点访问过,但是还没有处理,加入空节点做为标记。

                if (node->left) st.push(node->left);    // 添加左节点(空节点不入栈)
            } else { // 只有遇到空节点的时候,才将下一个节点放进结果集
                st.pop();           // 将空节点弹出
                node = st.top();    // 重新取出栈中元素
                st.pop();
                result.push_back(node->val); // 加入到结果集
            }
        }
        return result;
    }
};

       

        前序遍历代码为,此时与中序遍历相比只是改变了两行代码的顺序:

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        if (root != NULL) 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);
            } else {
                st.pop();
                node = st.top();
                st.pop();
                result.push_back(node->val);
            }
        }
        return result;
    }
};

         

        后序遍历代码为,此时与中序遍历相比只是改变了两行代码的顺序:

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        if (root != NULL) st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();
            if (node != NULL) {
                st.pop();
                st.push(node);                          // 中
                st.push(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;
    }
};

        Day14打卡!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值