二叉树的基本操作(C++实现)

基本概念


二叉树由结点的有限集合构成。
这个有限集合要么是空集,要么是一个根节点及两棵互不相交、分别称为这个跟的左子树和右子树的二叉树组成的集合。

二叉树的特点


每个结点最多有两棵子树,所以二叉树中不存在度大于2的结点。
左子树和右子树是有顺序的,次序不能随意颠倒。
及时树中某结点只有一颗子树,也要区分它是左子树还是右子树。
 

二叉树的五种基本形态


1.空二叉树


2.只有一个根节点


3.根节点只有左子树


4.根节点只有右子树


5.根节点既有左子树,又有右子树


相关概念


边(edge):两个结点(node)的有序对(order pair),称为边
结点的度:     一个节点含有的子树的个数成为该结点的度
叶节点:        度为0的结点成为叶节点
路径(path)
路径长度(the length of the paths)
层数:根是第0层(其他结点的层数等于其父节点的层数加1)
深度:层数最大的叶节点的层数

二叉树相关结论

二叉树的存储结构

二叉树的顺序存储结构一般只适用于完全二叉树,通常我们表示二叉树时,都是使用链式存储结构的。

二叉链表

二叉树每个结点最多有两个孩子,所以它有一个数据域和两个指针域。
我们称这样的链表叫做二叉链表。
如图:

lchilddatarchild

其中data是数据域,lchild和rchild都是指针域,分别存放指向左孩子和右孩子的指针

二叉树的二叉链表结构定义

代码如下

/*定义二叉树的结构*/
typedef struct Node
{
    char data;/*数据域*/
    struct Node* lchild, * rchild; /*左子树和右子树*/
} *BiTree, BiNode;
/*整棵树和结点名称*/

二叉树的遍历

遍历(traversal):

系统地访问数据结构中的结点,使得每个结点都正好被访问一次

深搜有限遍历二叉树

以下是三种深度优先遍历的递归定义:

前序遍历:

规则:若二叉树为空,则操作返回,否则,先访问根节点,然后前序访问左子树,之后前序访问右子树

二叉树的前序遍历的记忆法则是“根左右",即先遍历根节点,再遍历左子树节点,再遍历右子树节点。

以上图为例,前序遍历的结果是【A, B, D, E, C, F, G】

在二叉树的前序遍历问题上,它的递归公式是:

它的终止条件是: node 是否为空,为空则返回。

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> res;
        preorder(res, root);
        return res;
    }

    void preorder(vector<int>& res, TreeNode* root){
        
        if(!root) return;
        res.emplace_back(root->val);
        preorder(res, root->left);
        preorder(res, root->right);
    }
};

解题思路:迭代(方法1)

在递归方法实现过程中,它的底层是基于系统栈的结构来实现的。因此,我们可以使用栈的数据结构来辅助实现基于迭代方式的前序遍历。

具体思路为:

初始化栈stack,初始化输出列表res
根节点入栈
while(栈不为空),在循环体内部:
栈顶元素出栈
栈顶元素添加到输出列表
如果栈顶元素的右子树节点不为空,将右子树节点入栈
如果栈顶元素的左子树节点不为空,将左子树节点入栈
返回输出列表res

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {

        vector<int> res;
        if(!root) return res;

        stack<TreeNode*> s;
        s.push(root);

        while(!s.empty()){
            TreeNode* node = s.top();
            s.pop();
            res.emplace_back(node->val);

            if(node->right) s.push(node->right);
            if(node->left) s.push(node->left);
        }

        return res;


    }
};

解题思路:迭代(方法2)


基于迭代方法的第二种思路如下:

初始化栈stack,初始化输出列表res
设置一个变量cur, 表示当前节点。并赋初始值为根节点root
while(栈不为空 或者 当前节点cur不为空),在循环体内部:
沿着当前节点的左分支一直走,直到为空。在这个过程中将遍历的节点都入栈,同时添加到输出列表
栈顶元素出栈
更新当前节点cur为栈顶元素的右子树节点。
返回输出列表res
以图中的二叉树为例,来一步步来展示这个过程:

初始时,当前节点cur = root ,即节点A

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {

        vector<int> res;
        if(!root) return res;

        TreeNode* cur = root;
        stack<TreeNode*> s;
        while(!s.empty() || cur){
            // 沿着当前节点cur的左分支一直走到底
            while(cur){
                s.push(cur);
                res.emplace_back(cur->val);
                cur = cur->left;
            }
            TreeNode* node = s.top();
            s.pop();
            cur = node->right;
        }
        return res;

    }
};

中序遍历:

二叉树的中序遍历的记忆法则是“左根右",即先遍历左子树节点,再遍历根节点,再遍历右子树节点。

以上图为例,中序遍历的结果是【D, B, E, A, F, C, G】

一、解题思路:递归

递归是我们实现前中后序遍历最常用的方法。

什么问题可以采用递归求解呢?

需要满足三个条件:

        1.一个问题的解可以分解为若干个子问题的解;
        2.这个问题与分解的子问题,除了数据规模不同外,求解思路相同
        3.存在递归终止条件。
那么在知道一个问题可以采用递归实现之后,如何写出递归代码呢?

关键在于能写出递归公式,找到终止条件。

在二叉树的中序遍历问题上,它的递归公式是:

preorder(node) = preorder(node->left) --> print node —> preorder(node->right)

它的终止条件是:node 是否为空,为空则返回。

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        
        vector<int> res;
        inorder(res, root);
        return res;
    }

    void inorder(vector<int>& res , TreeNode* root){
        if(!root) return; 
        inorder(res, root->left);
        res.emplace_back(root->val);
        inorder(res, root->right);
    }
};

在递归方法实现过程中,它的底层是基于系统栈的结构来实现的。因此,我们可以使用栈的数据结构来辅助实现基于迭代方式的前序遍历。

具体思路为:

初始化栈stack,初始化输出列表res
根节点入栈
while(栈不为空),在循环体内部:
栈顶元素出栈
栈顶元素添加到输出列表
如果栈顶元素的右子树节点不为空,将右子树节点入栈
如果栈顶元素的左子树节点不为空,将左子树节点入栈
返回输出列表res

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {

        vector<int> res;
        if(!root) return res;

        stack<TreeNode*> s;
        s.push(root);

        while(!s.empty()){
            TreeNode* node = s.top();
            s.pop();
            res.emplace_back(node->val);

            if(node->right) s.push(node->right);
            if(node->left) s.push(node->left);
        }

        return res;


    }
};

初始化栈stack,初始化输出列表res
设置一个变量cur, 表示当前节点。并赋初始值为根节点root
while(栈不为空 或者 当前节点cur不为空),在循环体内部:
沿着当前节点的左分支一直走,直到为空。在这个过程中将遍历的节点都入栈,同时添加到输出列表
栈顶元素出栈
更新当前节点cur为栈顶元素的右子树节点。
返回输出列表res

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {

        vector<int> res;
        if(!root) return res;

        TreeNode* cur = root;
        stack<TreeNode*> s;
        while(!s.empty() || cur){
            // 沿着当前节点cur的左分支一直走到底
            while(cur){
                s.push(cur);
                res.emplace_back(cur->val);
                cur = cur->left;
            }
            TreeNode* node = s.top();
            s.pop();
            cur = node->right;
        }
        return res;

    }
};

后序遍历:

二叉树的后序遍历的记忆法则是“左右根",即先遍历左子树节点,再遍历右子树节点,最后遍历根节点。

以上图为例,后序遍历的结果是【D, E, B, F, G, C, A】

一、解题思路:递归

递归是我们实现前中后序遍历最常用的方法。

什么问题可以采用递归求解呢?

需要满足三个条件:

        1.一个问题的解可以分解为若干个子问题的解;
        2.这个问题与分解的子问题,除了数据规模不同外,求解思路相同
        3.存在递归终止条件。
那么在知道一个问题可以采用递归实现之后,如何写出递归代码呢?

关键在于能写出递归公式,找到终止条件。

在二叉树的前序遍历问题上,它的递归公式是:

preorder(node) = preorder(node->left) --> preorder(node->right) —> print node

它的终止条件是:node 是否为空,为空则返回。

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> res;
        preorder(res, root);
        return res;
    }

    void preorder(vector<int>& res, TreeNode* root){
        
        if(!root) return;
        res.emplace_back(root->val);
        preorder(res, root->left);
        preorder(res, root->right);
    }
};
  • 所谓前序,中序,后续遍历命名的由来是我们访问二叉树,根节点的顺序。前序遍历就是优先访问根节点,中序遍历是第二个访问根节点,后续遍历就是访问完左右节点之后,最后访问根节点。注意访问二字。访问和获取是两个不同的概念,我们可以获取一个节点,但是不访问他。对应在计算机里的概念是,获取一个节点表示将他入栈,访问一个节点是他处于栈顶,我们即将让他出栈。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值