二叉树Binary Tree

数的定义与相关概念

树(Tree)是nn≥0)个结点的有限集。n=0时称为空树。在任意一颗非空树中:有且仅有一个特定的称为根(Root)的结点;n>1时,其余结点可分为mm>0)个互不相交的有限集T 1 {T}_{1}T 1 ​ T 2 {T}_{2}T 2 ​ T {m -1} ​ ,其中每一个集合本身又是一棵树,并且称为根的子树(Sub Tree)。

树的相关概念

  树的术语:    

        (1)路径:在两个节点之间,一系列的边和节点的组合。路径的长度是组成路径的边数。

        (2)深度:一个节点的深度是指从根节点到该节点的最长路径上的边数。根节点的深度为0。

        (3)层次:树的层次从根开始定义,根为第一层,根的子节点为第二层,以此类推。

        (4)高度:树的高度是从叶子节点开始自底向上逐层累加的路径上边的数量。根节点的高度就是树的高度。

        在C++中,树的存储结构主要有两种:顺序存储和链式存储。不同的存储结构对应着不同的表示方法,如孩子表示法、双亲表示法、孩子兄弟表示法等。

        1. 顺序存储:顺序存储通常用于完全二叉树。在完全二叉树中,除了最后一层外,其他层的节点数是满的,并且最后一层的节点都靠左排列。这种特性使得完全二叉树可以使用数组进行顺序存储,其中每个节点的索引与其在树中的位置相关。

        示例:创建一棵简单的完全二叉树,代码如下。

#include <iostream>
#include <vector>
 
using namespace std;
 
class TreeNode {
public:
    int value;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x) : value(x), left(nullptr), right(nullptr) {}
};
 
class BinaryTree {
private:
    vector<TreeNode*> nodes;
public:
    // 初始化树的根节点
    void initRoot(int value) {
        nodes.push_back(new TreeNode(value));
    }
    
    // 添加子节点
    void addChild(int parentIndex, int leftChildValue, int rightChildValue) {
        int nextEmptyIndex = nodes.size();
        if (leftChildValue != -1) {
            nodes.push_back(new TreeNode(leftChildValue));
            nodes[parentIndex]->left = nodes[nextEmptyIndex];
        }
        if (rightChildValue != -1) {
            nodes.push_back(new TreeNode(rightChildValue));
            nodes[parentIndex]->right = nodes[nextEmptyIndex + (leftChildValue != -1)];
        }
    }
    
    // 示例:创建一棵简单的完全二叉树
    void createExampleTree() {
        initRoot(1);
        addChild(0, 2, 3);
        addChild(1, 4, 5);
        addChild(2, 6, -1);
        addChild(3, 7, 8);
    }
    
    // 其他操作,如遍历、查找等...
};

  链式存储:链式存储通过节点和指针来表示树中的关系。每个节点包含数据域和指向其子节点的指针域。链式存储方式更加灵活,适用于各种类型的树。

        示例一:使用孩子表示法创建树,代码如下。

class TreeNode {
public:
    int value;
    vector<TreeNode*> children;
    TreeNode(int x) : value(x) {}
};
 
// 使用孩子表示法创建树
TreeNode* createTree() {
    TreeNode* root = new TreeNode(1);
    TreeNode* node2 = new TreeNode(2);
    TreeNode* node3 = new TreeNode(3);
    TreeNode* node4 = new TreeNode(4);
    TreeNode* node5 = new TreeNode(5);
    
    root->children.push_back(node2);
    root->children.push_back(node3);
    node2->children.push_back(node4);
    node2->children.push_back(node5);
    
    return root;
}

  上述代码展示了如何使用孩子表示法来创建一个树,其中每个节点都有一个指向其子节点的指针列表。这种方式可以很直观地表示一个节点的所有子节点,但是在查找父节点时不够高效,因为父节点的信息并未存储在当前节点中。

        在双亲表示法中,每个节点不仅包含数据域和指向其子节点的指针,还包含一个指向其父节点的指针。这使得我们可以方便地访问一个节点的父节点,但可能需要额外的空间来存储父节点的指针。

        示例二:使用双亲表示法创建树,代码如下:

class TreeNode {
public:
    int value;
    TreeNode* parent; // 指向父节点的指针
    vector<TreeNode*> children; // 子节点列表
    TreeNode(int x) : value(x), parent(nullptr) {}
};
 
// 使用双亲表示法创建树
void createTreeWithParent(TreeNode*& root) {
    root = new TreeNode(1); // 根节点的父节点为null
    TreeNode* node2 = new TreeNode(2);
    TreeNode* node3 = new TreeNode(3);
    TreeNode* node4 = new TreeNode(4);
    TreeNode* node5 = new TreeNode(5);
    
    node2->parent = root;
    node3->parent = root;
    node4->parent = node2;
    node5->parent = node2;
    
    root->children.push_back(node2);
    root->children.push_back(node3);
    node2->children.push_back(node4);
    node2->children.push_back(node5);
}

 在双亲表示法中,我们可以沿着父节点的指针向上遍历树,直到找到根节点或者到达一个父节点为空的节点。这种表示法在需要频繁进行父子节点关系查询时比较有用。

        孩子兄弟表示法是一种结合了孩子表示法和双亲表示法的思想的方法。在这种表示法中,每个节点包含指向其第一个孩子节点的指针和指向其下一个兄弟节点的指针。这种表示法对于二叉树非常自然,并且可以很方便地表示任何类型的树。
        示例三: 使用孩子兄弟表示法创建树,代码如下:   

class TreeNode {
public:
    int value;
    TreeNode* firstChild; // 指向第一个孩子节点
    TreeNode* nextSibling; // 指向下一个兄弟节点
    TreeNode(int x) : value(x), firstChild(nullptr), nextSibling(nullptr) {}
};
 
// 使用孩子兄弟表示法创建树
void createTreeWithChildSibling(TreeNode*& root) {
    root = new TreeNode(1);
    TreeNode* node2 = new TreeNode(2);
    TreeNode* node3 = new TreeNode(3);
    TreeNode* node4 = new TreeNode(4);
    TreeNode* node5 = new TreeNode(5);
    
    root->firstChild = node2;
    node2->nextSibling = node3;
    node3->firstChild = node4;
    node3->nextSibling = node5;
}

                  

 在这种表示法中,通过firstChild可以访问到该节点的所有子节点,而通过nextSibling可以遍历该节点的所有兄弟节点。这种方法特别适合处理那些子节点之间没有顺序要求的树结构。

        每种存储结构都有其适用的场景和优缺点,例如顺序存储适合表示完全二叉树,而链式存储则更加灵活,能够表示任意结构的树。在实际应用中,需要根据具体需求和树的特点来选择适当的存储结构.

代码原文链接:https://blog.csdn.net/winterling/article/details/138783496

树的计算公式 (通用)

变量说明
度为 m mm 的结点数 = n_{m}
i : 第 i层 或 第 i  号结点;
h : 树的高度;
ceil 向上取整,floor 向下取整 

总结点数=n_{0}+n_{1}+n_{2}+......+n_{m}

总分支数=1\times n_{1}+2\times n_{2}+......+m\times n_{m}

度为m的树的第i层最多有m^{i-1}个结点(i\geq 1)

高度为hm叉树最多有m^{h-1}/m-1个结点

具有 n 个结点的 m叉树的最小高度为ceil(log_{m}(n(m-1)+1))

二叉树计算公式

非空二叉树中n_{0}=n_{2}+1

非空二叉树中 结点数为n

边数 = n − 1 边数=n-1,边数=n-1

高为h的二叉树最多有2^{h-1}个结点

满二叉树计算公式

编号问题 : 编号为 i的结点
双亲为 floor(i\div 2)
左孩子编号为 2i ;
右孩子编号为 2i+1

完全二叉树计算公式

编号问题 : 编号为 i 的结点 :
i\leqslant floor(n\div 2)则 i 为分支结点, 否则 i 就是叶子结点
按层编号, 当编号为 i 的结点为 叶子结点或只含左子节点 , 则编号大于 i 的都是叶子结点;
总结点数为奇数, 每个分支节点都要左右子结点;
总结点数为偶数, 编号最大者(i=n\div 2)只有左子节点;
n个结点的完全二叉树的高度为 h=ceil(log_{2}(n+1))

 或 h=floor(log_{2}(n+1))
若某节点编号为 i :
2i\leqslant ni的左子节点编号为 2 i 2i2i, 否则就是没有左子节点;
2i+1\leqslant n i的右子节点编号为 2 i + 1 2i+12i+1, 否则就是没有右子节点;
最后一个分支结点的编号为floor(n\div 2)

---------------------------------------------------------------------------------------------------------------------------------

参考资料:

C++树详解-腾讯云开发者社区-腾讯云 (tencent.com)icon-default.png?t=N7T8https://cloud.tencent.com/developer/article/2343741【数据结构与算法 C/C++】树/二叉树计算公式总结_二叉树各种计算公式总结-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/qq_45596525/article/details/124251206C++的数据结构(五):树和存储结构及示例_c树的结构和c++树的结构、-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/winterling/article/details/138783496编程学习中心 (xiaomawang.com)icon-default.png?t=N7T8https://online.xiaomawang.com/liveStudent/autonomous?appId=xmw0721063214897&classId=31623&courseId=20014&courseSystem=3&sessionId=20646&sessionName=%E7%AC%AC08-%E8%AF%BE-%E4%BA%8C%E5%8F%89%E6%A0%91%28%E4%B8%80%29&sessionType=0&sessionVersionId=27313&subjectId=8&zegoTaskId=L8CAAQma4f_aHarX

  • 21
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值