数据结构:二叉树

1、二叉树得存储与遍历

1.1二叉树的存储

1.1.1二叉树的顺序存储

顺序存储指的是按照满二叉树的结点层次编号,依次存放二叉树中的数据元素。如下图所示

可以看到二叉树的顺序存储非常浪费空间,如左图,二叉树第五个结点虽然没有存储元素,但是这个结点依然要被占据。右图的单支树就是一种更加极端的情况。

1.1.2二叉树的链式存储

为了改进顺序存储浪费空间的问题,二叉树的存储可以使用链式存储,即使用二叉链表来表示:

左图的二叉树可以由右图的二叉链表表示。此时对每一个结点来说,它包含了左右两个指针域分别指向左孩子和右孩子。如果没有孩子,指针就指向空。接下来我们用代码实现: 

typedef struct BiNode 
{
    TElemType data;                          // 节点存储的数据
    struct BiNode* lchild, * rchild;         // 左右孩子指针
} BiNode, * BiTree;

这段代码定义了二叉树的结点。这是一个结构体类型,包含结点存储的数据和左右孩子指针。

使用二叉链表存储二叉树可以有效解决空间浪费问题,因为每个节点只需要两个指针,分别指向左孩子和右孩子。然而,这种存储方式存在一个明显的局限:它无法直接进行回溯操作,即在遍历过程中,我们不知道每个节点的父节点是谁。

为了解决这一问题,我们可以使用三叉链表来存储二叉树。三叉链表在节点中引入了一个额外的指针,用于指向该节点的父节点。这样,每个节点包含三个指针:左孩子右孩子父节点,从而在遍历过程中,可以直接回溯到父节点,简化了操作,提升了效率。通过以下图示可以更直观地理解

依然是之前的二叉树,用三叉链表可以将子结点与父结点联系起来。如上图,B结点分别有两个指针指向子节点C和D,同时还有一个指针指向A。下面用代码表示:

typedef struct TriTNode
{
    TelemType data;                        // 节点存储的数据
    struct TriTNode* lchild, * parent, * rchild; // 三个指针:左孩子指针、父节点指针、右孩子指针
} TriTNode, * TriTree;

与二叉链表存储的代码很相近,这里只添加了一个指向父结点的指针。 

1.2二叉树的遍历

我们先来回顾遍历的含义:

遍历指按某条搜索路线遍访每个结点且不重复(又称周游)。
遍历是树结构 插入、删除、修改、查找和排序运算的前提 ,是二叉树一切运算的基础和核心。
二叉树的遍历总共有三种:先序遍历,中序遍历和后序遍历
DLR — 先(根)序遍历,即先根再左再右
LDR — 中(根)序遍历,即先左再根再右
LRD — 后(根)序遍历,即先左再右再根
例如下面这课二叉树我们可以用三种遍历方式表示

先序遍历:ABDEC

中序遍历:DBEAC

后序遍历:DEBCA 

1.2.1遍历算法的实现

以该图为例,先序遍历ABDC

若二叉树为空则不操作,否则

1.先访问根结点A    2.先遍历左子树B,此时再以B为根结点遍历左子树,然而B的左子树为空,所以遍历B的右子树D   3.最后遍历A的右子树C

中序遍历:BDAC

若二叉树为空,则不操作,否则

1.中序遍历根结点A的左子树B   2.此时以B为根结点,中序遍历B结点的左子树,由于左子树为空,所以第一个结点为B,再遍历右子树D   3.遍历根结点A    4.再遍历A的右子树C

后序遍历:DBCA

若二叉树为空,则不操作,否则

1.后续遍历根结点A的左子树B   2.再以B为根结点,后续遍历左子树和右子树     3.遍历A的右子树C

4.最后遍历根结点A

接下来我们用编程实现先序遍历:

Status PreOrderTraverse(BiTree T)
{
if(T==NULL) return OK;
else
{ 
visit(T);
PreOrderTraverse(T->lchild); 
PreOrderTraverse(T->rchild); 
}
}

if(T == NULL) return OK;:这是递归的基准条件。若当前传入的树 T 为空(即为 NULL),表示该子树不存在,此时递归终止,直接返回 OK

  • visit(T);:访问当前节点。通常,这里会包含对节点值的操作,比如打印节点值或者执行其他逻辑。
  • PreOrderTraverse(T->lchild);:递归调用自身,先访问当前节点的左子树。
  • PreOrderTraverse(T->rchild);:递归调用自身,随后访问当前节点的右子树。
  • 递归的核心:递归的终止条件是当前子树为空。当遍历到某个节点时,程序会调用 visit(T) 访问该节点,然后递归地遍历它的左子树和右子树。

例题:求二叉树最大深度的递归遍历算法

int deepth(TreeNode* root) {
    if(root == NULL) 
        return 0;
    int left = deepth(root->left); 
    int right = deepth(root->right); 
    return 1 + max(left, right);
}
  • 递归计算左右子树的深度

    • int left = deepth(root->left);:递归计算左子树的深度,函数会沿着左子树的路径继续调用自身,直到叶节点。
    • int right = deepth(root->right);:递归计算右子树的深度,函数会沿着右子树的路径递归调用自身。
  • 返回当前节点的深度

    • return 1 + max(left, right);:当前节点的深度等于左右子树中较大深度的值加上 1。max(left, right) 表示选择左右子树中的最大深度,而加 1 表示把当前节点的深度也算入。

对于如下二叉树: 

      1
     / \
    2   3
   / 
  4

对于根节点 1,递归会计算左右子树:

  • 左子树深度为 2(节点 2, 4),右子树深度为 1(节点 3)。
  • 根节点的深度是 1 + max(2, 1) = 3

例题:写出求二叉树叶子结点个数的递归遍历算法

  • 基准条件

    • if(T == NULL) return 0;:如果当前树是空的(即节点为 NULL),返回 0,因为空树没有叶子节点。
  • 判断叶子节点

    • if (T->lchild == NULL && T->rchild == NULL) return 1;:如果当前节点没有左子树和右子树,即为叶子节点,则返回 1,表示找到一个叶子节点。
  • 递归计算左右子树的叶子节点

    • else return LeadCount(T->lchild) + LeadCount(T->rchild);:如果当前节点不是叶子节点,递归计算其左子树和右子树的叶子节点数量,将结果相加。
      1
     / \
    2   3
   / 
  4
  1. 对于根节点 1,不是叶子节点,递归计算左右子树:
    • 左子树有叶子节点 4,返回 1。
    • 右子树有叶子节点 3,返回 1。

最终,返回 1 + 1 = 2,表示这棵树中有 2 个叶子节点。

 

 

 

 

 

  • 9
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值