二叉树基础知识及其遍历方法

二叉树基础知识及其遍历方法

1. 二叉树简介

二叉树是一种树形数据结构,其中每个节点最多有两个子节点,通常称为左子节点右子节点。二叉树广泛应用于计算机科学,包括数据存储、搜索算法和表达式解析等场景。以下是一些常见的二叉树类型及其特征:

1.1 完全二叉树

  • 定义: 完全二叉树是一种每层都被完全填满的二叉树,除了最后一层,节点在左侧连续。
  • 特征:
    • 所有节点都被填满。
    • 最后一层的节点从左到右填充。
        1
       / \
      2   3
     / \  /
    4  5 6

1.2 满二叉树

  • 定义: 满二叉树是一种每个节点都有0或2个子节点的二叉树。
  • 特征:
    • 每个内部节点都有两个子节点。
    • 所有叶子节点位于同一层。
        1
       / \
      2   3
     / \  / \
    4   5 6   7

1.3 二叉搜索树 (BST)

  • 定义: 二叉搜索树是一种特殊的二叉树,其中每个节点的值大于其左子树的所有节点的值,并小于其右子树的所有节点的值。
  • 特征:
    • 支持高效的查找、插入和删除操作(平均O(log n)时间复杂度)。
        5
       / \
      3   8
     / \   \
    2   4   9

1.4 平衡二叉树

  • 定义: 平衡二叉树是一种特殊的二叉树,其左右子树的高度差不超过1。
  • 特征:
    • 保持高度平衡,以保证高效的操作时间。
        4
       / \
      2   6
     / \ / \
    1  3 5  7

2.二叉树遍历分类

2.1 深度优先遍历(DFS)

  • 特点:沿树的深度方向优先遍历到叶子节点后再回溯,递归或迭代均可实现。
  • 种类:
    1. 前序遍历(Preorder):根节点 -> 左子树 -> 右子树
    2. 中序遍历(Inorder):左子树 -> 根节点 -> 右子树
    3. 后序遍历(Postorder):左子树 -> 右子树 -> 根节点
  • 实现方式:
    • 递归:最主流,代码简洁。
    • 迭代:需要使用栈(模拟递归调用过程)。

2.2 广度优先遍历(BFS)

  • 特点:按照树的层次,从上到下逐层遍历,每一层从左到右依次访问节点。
  • 实现方式:
    • 迭代:使用队列(主流方法)。
    • 递归:也可以实现,但通常较复杂且不常用。

2.3 实现代码对比

DFS:前序、中序、后序遍历
  1. 递归实现
// 前序遍历
void preorder(TreeNode* root) {
    if (!root) return;
    cout << root->val << " ";
    preorder(root->left);
    preorder(root->right);
}

// 中序遍历
void inorder(TreeNode* root) {
    if (!root) return;
    inorder(root->left);
    cout << root->val << " ";
    inorder(root->right);
}

// 后序遍历
void postorder(TreeNode* root) {
    if (!root) return;
    postorder(root->left);
    postorder(root->right);
    cout << root->val << " ";
}
  1. 迭代实现
void preorderIter(TreeNode* root) {
    if (!root) return;
    stack<TreeNode*> st;
    st.push(root);
    while (!st.empty()) {
        TreeNode* node = st.top();
        st.pop();
        cout << node->val << " ";
        if (node->right) st.push(node->right); // 右子节点先入栈
        if (node->left) st.push(node->left);   // 左子节点后入栈
    }
}

// 中序遍历
void inorderIter(TreeNode* root) {
    stack<TreeNode*> st;
    TreeNode* curr = root;
    while (curr || !st.empty()) {
        while (curr) {
            st.push(curr);
            curr = curr->left;
        }
        curr = st.top();
        st.pop();
        cout << curr->val << " ";
        curr = curr->right;
    }
}

// 后序遍历
void postorderIter(TreeNode* root) {
    if (!root) return;
    stack<TreeNode*> st1, st2;
    st1.push(root);
    while (!st1.empty()) {
        TreeNode* node = st1.top();
        st1.pop();
        st2.push(node);
        if (node->left) st1.push(node->left);
        if (node->right) st1.push(node->right);
    }
    while (!st2.empty()) {
        cout << st2.top()->val << " ";
        st2.pop();
    }
}

BFS:层序遍历
  1. 迭代实现(主流):
void levelOrder(TreeNode* root) {
    if (!root) return;
    queue<TreeNode*> q;
    q.push(root);

    while (!q.empty()) {
        TreeNode* node = q.front();
        q.pop();
        cout << node->val << " ";
        if (node->left) q.push(node->left);
        if (node->right) q.push(node->right);
    }
}
  1. 递归实现(不常用):
void levelOrderHelper(TreeNode* node, int level, vector<vector<int>>& res) {
    if (!node) return;
    if (res.size() == level) res.push_back({});
    res[level].push_back(node->val);
    levelOrderHelper(node->left, level + 1, res);
    levelOrderHelper(node->right, level + 1, res);
}

void levelOrder(TreeNode* root) {
    vector<vector<int>> res;
    levelOrderHelper(root, 0, res);
    for (const auto& level : res) {
        for (int val : level) cout << val << " ";
    }
}

2.4 总结

  1. DFS:前序、中序、后序,递归是主流,迭代需要用栈。
  2. BFS:层序遍历,迭代是主流,使用队列,递归方式较少用。
  3. 推荐:
    • 简单问题:DFS使用递归实现。
    • 大规模树或更高效:DFS使用迭代,BFS使用队列迭代。

3. DFS遍历不同递归方法

二叉树的遍历是指按照某种顺序访问树中每个节点的过程。最常用的遍历方法有前序遍历中序遍历后序遍历

3.1 前序遍历

  • 顺序: 访问根节点 ➔ 遍历左子树 ➔ 遍历右子树。
  • 代码实现:
/**
 * Definition for a binary tree node.
 */
struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode() : val(0), left(nullptr), right(nullptr) {}
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
    TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};

class Solution {
public:
    void traversal(TreeNode* root, vector<int>& result) {
        if (root == NULL) { return; }
        result.push_back(root->val); // 访问根节点
        traversal(root->left, result); // 遍历左子树
        traversal(root->right, result); // 遍历右子树
    }

    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> result;
        traversal(root, result);
        return result;
    }
};
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> res;
        if(root==nullptr){
            return res;
        }
        res.push_back(root->val);
        vector<int> left = preorderTraversal(root->left);
        res.insert(res.end(),left.begin(),left.end());
        vector<int> right = preorderTraversal(root->right);
        res.insert(res.end(),right.begin(),right.end());
        return res;
    }
};

3.2 中序遍历

  • 顺序: 遍历左子树 ➔ 访问根节点 ➔ 遍历右子树。
  • 代码实现:
class Solution {
public:
    void traversal(TreeNode* root, vector<int>& result) {
        if (root == NULL) return;
        traversal(root->left, result); // 遍历左子树
        result.push_back(root->val); // 访问根节点
        traversal(root->right, result); // 遍历右子树
    }

    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> result;
        traversal(root, result);
        return result;
    }
};
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        if(root==nullptr) return res;
        vector<int> left = inorderTraversal(root->left);
        res.insert(res.end() , left.begin() , left.end());
        res.push_back(root->val);
        vector<int> right = inorderTraversal(root->right);
        res.insert(res.end() , right.begin() , right.end());
        return res;
    }
};

3.3 后序遍历

  • 顺序: 遍历左子树 ➔ 遍历右子树 ➔ 访问根节点。
  • 代码实现:
class Solution {
public:
    void traversal(TreeNode* root, vector<int>& result) {
        if (root == NULL) return;
        traversal(root->left, result); // 遍历左子树
        traversal(root->right, result); // 遍历右子树
        result.push_back(root->val); // 访问根节点
    }

    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> result;
        traversal(root, result);
        return result;
    }
};
struct TreeNode{
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode() : val(0), left(nullptr), right(nullptr) {}
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
    TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> res;
        if(root==nullptr) return res;
        vector<int> left = postorderTraversal(root->left);
        res.insert(res.end(), left.begin(), left.end());
        vector<int> right = postorderTraversal(root->right);
        res.insert(res.end(), right.begin(), right.end());
        res.push_back(root->val);
        return res;
    }
};

3.4前中后序遍历区别

前中后序是遍历二叉树过程中处理每一个节点的三个特殊时间点,绝不仅仅是三个顺序不同的 List:

前序位置的代码在刚刚进入一个二叉树节点的时候执行;

后序位置的代码在将要离开一个二叉树节点的时候执行;

中序位置的代码在一个二叉树节点左子树都遍历完,即将开始遍历右子树的时候执行。

二叉树的所有问题,就是让你在前中后序位置注入巧妙的代码逻辑,去达到自己的目的,你只需要单独思考每一个节点应该做什么,其他的不用你管,抛给二叉树遍历框架,递归会在所有节点上做相同的操作

遇到一道二叉树的题目时的通用思考过程是:

1、是否可以通过遍历一遍二叉树得到答案?如果可以,用一个 traverse 函数配合外部变量来实现。

2、是否可以定义一个递归函数,通过子问题(子树)的答案推导出原问题的答案?如果可以,写出这个递归函数的定义,并充分利用这个函数的返回值。

3、无论使用哪一种思维模式,你都要明白二叉树的每一个节点需要做什么,需要在什么时候(前中后序)做

划重点:仔细观察,前中后序位置的代码,能力依次增强

前序位置的代码只能从函数参数中获取父节点传递来的数据。

中序位置的代码不仅可以获取参数数据,还可以获取到左子树通过函数返回值传递回来的数据。

后序位置的代码最强,不仅可以获取参数数据,还可以同时获取到左右子树通过函数返回值传递回来的数据。

所以,某些情况下把代码移到后序位置效率最高;有些事情,只有后序位置的代码能做。

4. 结论

二叉树是计算机科学中的基本数据结构之一,理解其各种形式和遍历方法是学习数据结构和算法的基础。通过学习和实现不同类型的树以及它们的遍历方式,能够为进一步深入研究树形结构和相关算法打下坚实的基础。希望这篇文章能够帮助你更好地理解二叉树及其遍历方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值