[数据结构] 树(Tree)

树(Tree)

树的基本概念

树是一种非常重要的非线性数据结构,树的一个节点可能会生出多个分支。一般而言,一棵树会包含一个根节点,向下延伸出若干子节点,每个末端的节点被称为叶子节点。
在这里插入图片描述

有根树

有根树存在一个根节点Root,如下:
在这里插入图片描述

对于图中概念的一些补充:

  • 节点拥有的子节点个数叫做节点的
  • 具有相同深度的节点处于同一层,方便表示。
无根树

二叉树

二叉树是一种特殊的树

  • 所有节点的度都不超过2的树称为二叉树。
  • 因为每个二叉树的节点最多只会有两个子结点,它的两个子节点一般会被称为左、右儿子,两棵子树一般会被称为左、右子树。
  • 左、右儿子甚至根节点本身都有可能缺失(一个节点都没有可以称为空二叉树)。
满二叉树和完全二叉树

二叉树也有两个比较特殊的类型:满二叉树和完全二叉树
在这里插入图片描述

  • 满二叉树:所有层的节点全满。

    • 满二叉树的一些规律
      • n n n 层的节点个数为 2 n − 1 2^{n-1} 2n1
      • 深度为 n n n 的满二叉树节点数为 2 0 + 2 1 + 2 2 + ⋯ + 2 n − 1 = 2 n − 1 2^0 + 2^1 + 2^2 + \dots + 2^{n-1}= 2^n-1 20+21+22++2n1=2n1
  • 完全二叉树:除了最后一层以外,其他层的节点个数全满,而且最后一层的节点从左到右排满直到最后一个节点。

    • 完全二叉树的一些规律
      • 完全二叉树的节点个数不会少于 ( 2 n − 1 − 1 ) + 1 = 2 n − 1 (2^{n-1}-1)+1 = 2^{n-1} (2n11)+1=2n1
      • 完全二叉树的节点个数不会多于 2 n − 1 2^{n} - 1 2n1
      • 一棵完全二叉树,设当前节点为 t t t,其父节点为 t / 2 t/2 t/2,其左儿子为 2 t 2t 2t,其右儿子为 2 t + 1 2t+1 2t+1,借助该规律,我们可以将完全二叉树使用数组进行存储。
        在这里插入图片描述
  • 完全二叉树的存储

    • 完全二叉树由于它的特性,可以简单用数组来模拟其结构
    • 一般会以数组 [ 1 ] [1] [1]位置为根节点建立二叉树
    • 数组 [ t ] [t] [t]位置的左儿子和右儿子对应的位置分别为 [ 2 t ] [2t] [2t] [ 2 t + 1 ] [2t+1] [2t+1],父节点的位置为 [ t / 2 ] [t/2] [t/2]
    • 堆、线段树等数据结构的建立也会参考这个方式

完全二叉树的建立(使用数组),使用这种方法建立非完全二叉树,会导致空间的浪费:

void build(int t) {
    // 添加数据
    UpdateData(t);

    // 如果子节点存在
    Build(2 * t);
    Build(2 * t + 1);
}

为了解决这个问题,我们可以使用其他方法来完成一般二叉树的存储,可以用数组下标模拟节点编号,用多个数组来记录节点信息。为了方便,我们也可以使用结构体来存储这些信息:
在这里插入图片描述

// 使用结构体来实现上述操作
struct TreeNode {
    int value;
    int l, r, fa;
}a[100010];

当然,作为一种树形结构,使用指针显然是更合适的方法:

// 使用指针来实现上述操作
struct TreeNode {
    int value;
    TreeNode* l;
    TreeNode* r;
    TreeNode* fa;
};

TreeNode* root;

使用指针的一些操作:

  • 新建节点:
    struct TreeNode {
        int value;
        TreeNode *l, *r, *fa;   // 初始为 NULL
        TreeNode(int x){ value = x; }
    };
    
    TreeNode* treeNode = new TreeNode(x);
    
  • 根节点初始化:
    TreeNode* root;
    root = new TreeNode(v);
    
  • 插入节点:
    void Insert(TreeNode* fa, TreeNode* p, int flag){
        // flag = 0 插入到左边
        // flag = 1 插入到右边
        if (!flag)
            fa->l = p;
        else
            fa->r = p;
        p->fa = fa;
    }
    TreeNode* treeNode = new TreeNode(v);
    Insert(fa, treeNode, flag);
    
  • 删除节点
    // 删除节点
    
二叉树的遍历

二叉树的遍历可分为先序遍历、中序遍历和后序遍历,这三种方式以访问根节点的时间来区分。
先序遍历(Degree-Left-Right, DLR):根→左→右
中序遍历(Left-Degree-Right, LDR):左→根→右
先序遍历(Left-Right-Degree, LRD):左→右→根

在这里插入图片描述

在该图中,先序遍历的结果为 1 2 4 5 3 6 7,先序遍历代码如下:

void preOrder(TreeNode* treeNode) {
    cout << p->value << endl;
    if(treeNode->l)     preOrder(treeNode->l);
    if(treeNode->r)     preOrder(treeNode->r);
}

preOrder(root);

在该图中,中序遍历的结果为 4 2 5 1 6 3 7,中序遍历代码如下:

void inOrder(TreeNode* treeNode) {
    if(treeNode->l)     inOrder(treeNode->l);
    cout << p->value << endl;
    if(treeNode->r)     inOrder(treeNode->r);
}

inOrder(root);

在该图中,后序遍历的结果为 4 5 2 6 7 3 1,后序遍历代码如下:

void postOrder(TreeNode* treeNode) {
    if(treeNode->l)     postOrder(treeNode->l);
    if(treeNode->r)     postOrder(treeNode->r);
    cout << p->value << endl;
}

postOrder(root);

除了上述的几种遍历方式,还有层级遍历(BFS)方式对树进行遍历。层级遍历是借助队列(Queue)来实现的,其过程可以描述如下:
在这里插入图片描述

层级遍历的代码如下:

TreeNode* q[N];
void bfs(TreeNode* root) {
    int front = 1, rear = 1;
    q[1] = root;
    while (front <= rear) {
        TreeNode* p = q[front];     // 选取队列中最前面的节点
        front++;
        cout << p->value <<endl;
        if(p->l)    q[++rear] = p->l;
        if(p->r)    q[++rear] = p->r;
    }
}

bfs(root);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值