4.2【二叉树】

1.1. 概念

二叉树Binary Treen个结点的有限集合(算上根结点)。

它可以是 空集n=0,或可以是 由一个根结点以及两颗互不相交、分别称为左子树和右子树的二叉树组成。

1.2. 特点

二叉树与普通有序树不同,二叉树 严格区分 左子和右子,即使只有一个 子结点 也要区分左右。

二叉树的 树度数 最大2

1.3. 性质

  1. 二叉树的第k层上的 结点数 最多个2k-1

最多的情况,解释

最多的情况

层1:21-1 = 20 = 1

层2:22-1 = 21 = 2

层3:23-1 = 22 = 4

层4:24-1 = 23 = 8

层k:2k-1

  1. 深度为k的二叉树最多有2k-1个结点

公式解释

层1:20 = 1

层2:21 = 2

层3:22 = 4

等比数列求和:

Sn= 1*(1-2k) / 1-2 = 2k-1

  1. 在任意一颗二叉树中,树叶(终端结点) 的数目 比 度数为2的结点数目1

解释

N:结点的总数(算上 根结点的 总结点数)

N0:没有后继的结点个数(叶子结点个数)

N1:只有一个后继的结点个数(只有一个孩子的树)

N2:有两个后继的结点个数(只有俩孩子的树)

总结点 = 各结点 数目 之和 N = N0 + N1 + N2

总结点 = 所有类别子节点数目 + N = 0 × N0 + 1 × N1 + 2 × N2 + 1

联立以上两式可得: N0 = N2 + 1

(网易) 一棵二叉树有8个度为2的节点,5个度为1的节点,那么度为0的节点个数为 ( )

A. 不确定 B. 7 C. 8 D. 9 E. 6

1.4. 常见特例

1.4.1. 满二叉树

满二叉树:深度为k(k>=1)时结点个数为2k-1(叶子结点是满的)

1.4.2. 完全二叉树

完全二叉树:只有 最下面两层 有度数小于2的节点,且最下面一层的结点集中在最左边的若干位置上。

  • 倒数第二层,必须全是满的
  • 倒数第一层,集中在最左侧

1. 实现 完全 二叉树

二叉树的 存储结构 有两种,分为 顺序存储 链式存储

顺序 存储 普通二叉树,需要将其 提前 转换成 完全二叉树。

链式 存储 二叉树,无特殊要求 ?

1.1. 顺序存储(无代码、概念、做题)(知道即可)

二叉树的顺序存储,只能用顺序表存储。

想要顺序存储普通二叉树,就需要将其提前转换成完全二叉树。

普通二叉树转完全二叉树的方法很简单,只需给二叉树额外添加一些结点,将其"拼凑"成一个完全二叉树即可。

解决二叉树的转化问题,接下来就是如何顺序存储完全(满)二叉树。

左侧是普通二叉树,右侧是转化后的完全(满)二叉树。

存储 完全二叉树

完全(满)二叉树的顺序存储,仅需要从根结点开始,按照层次依次将树中结点存储到数组即可。

存储图 2 所示的完全二叉树:

存储由 普通二叉树 转化来的 完全二叉树

图 1 中的普通二叉树在数组中的存储状态如图所示:

-----分割线-----

如何从顺序表中去还原完全二叉树?:

当根结点号为 非0,比如1时:

将完全二叉树中结点 按照 层次从左到右依次编号123...),若结点i有左子,则其左子的结点编号为2*i,右子编号为2*i+1。()

  • 设 完全二叉树的 结点数n,某结点的编号i
  • i>1时(不是根结点时),有父节点,其编号为i/2
  • 2*i <= n时,有左子,其编号为2*i否则没有左子没左子一定没右子,其本身为叶节点
  • 2*i+1 <= n时,有右子,其编号为2*i+1,否则就没有右子。

1.2. 遍历:

1.2.1. 先序遍历

先序:根----->左----->右

A B D H I E J C F K G

1.2.2. 中序遍历

中序:左----->根----->右

H D I B E J A F K C G

1.2.3. 后序遍历

后序:左----->右----->根

H I D J E B K F G C A

1.3. 根据 遍历结果,画出对应的二叉树(不强调 什么种类的 树)(一定要会)

例子:已知前序中序,画出对应二叉树(有序树,非完全)

前序:A B C E H F I J D G K 根----->左----->右

中序:A H E C I F J B D K G 左----->根----->右

思路

先看前序,前序中A肯定是根

去中序看AA前无结点,故A无左子。

回到前序,A后为B,根据前序特点根左右,B一定为A的左子或右子,故B为右子。

去中序看BAB之间为B的左子树(H E C I F J

回到前序, B后为C,根据前序特点根左右,C一定为B的左子或右子,故C为左子。(因为其在左子树)

去中序看CAC之间为C的左子树(H E)

回到前序, C后为E,根据前序特点根左右,E一定为C的左子或右子,故E为左子。(因为其在左子树)

去中序看EAE之间为E的左子树(H )

回到前序, E后为H,根据前序特点根左右,H一定为E的左子或右子,故H为左子。(因为其在左子树)

技巧

得到根结点的左右子后,去模块化(连续成组,最低两个)

前序:A B C E H F I J D G K 根----->左----->右

中序:A H E C I F J B D K G 左----->根----->右

ABCFD

ACFBD

1.4. 链式存储(递归 + 代码)

链式存储此二叉树,从根结点开始,将各个结点以及其左右子 使用链表 进行存储即可。

1.4.1. 定义结点 结构体

结点结构体由三部分构成:

  1. 指向左子结点的指针(Lchild)
  2. 结点存储的数据
  3. 指向右子结点的指针(Rchild)
typedef struct BinaryTreeNode
{
    int data;                      // 数据域
    struct BinaryTreeNode *Lchild; // 左子指针
    struct BinaryTreeNode *Rchild; // 右子指针
} BTN, *BT;

1.4.2. 创建二叉树

返回:创建成功后已存在根结点,返回其地址。

1.4.3. 先序遍历

1.4.4. 中序遍历

1.4.5. 后序遍历

1.4.6. 层次遍历

将每个根结点出列,用root来接收,打印完数据后,再判断有无左子,如果有左子,则让左子入列。

左子B入列。有右子C,让C入列。

所以是一层层的。

示意图:

1.4.7. 无 层次遍历代码:单文件

#include <stdio.h>
#include <stdlib.h>

typedef struct BinaryTreeNode
{
    int data;                      // 数据域
    struct BinaryTreeNode *Lchild; // 左孩子指针
    struct BinaryTreeNode *Rchild; // 右孩子指针
} BTN, *BT;

// 2. 创建二叉树
BT BTInit(int n, int i) // n 结点总数;  i 目前结点的编号,根节点就是1
{
    // 1. 开辟空间存放根结点
    BT root = (BT)malloc(sizeof(BTN));
    if (NULL == root)
    {
        printf("BTInit failed, root malloc err.\n");
        return NULL;
    }
    // 2. 初始化根结点成员
    // 2.1 对根结点进行赋值(编号)
    root->data = i; // 传过来的根结点编号

    // 下面是给左右子赋值,赋值的前提是左右子存在。那么就需要判断一下
    // 怎么判断?

    // 2.2 判断有无左右孩子

    // 2.2.1 有左子,让左子作为根重复本函数的操作
    if (2 * i <= n)
        root->Lchild = BTInit(n, 2 * i); // 因为根的编号是i,根的左子编号就是2*i
    // 2.2.2 没有左子,那么将root->Lchild置为NULL
    else
        root->Lchild = NULL;

    // 2.2.3 有右子,让右子作为根重复本函数的操作
    if (2 * i + 1 <= n)
        root->Rchild = BTInit(n, 2 * i + 1);
    // 2.2.4 没有右子,那么将root->Rchild置为NULL
    else
        root->Rchild = NULL;

    return root;
}

// 3. 先序遍历二叉树
// 根——左——右
void PreOrder(BT root)
{
    // 空树
    if (root == NULL)
        return;

    // 3.1 打印根结点数据(编号)
    printf("%d ", root->data); // 根

    // 3.2 如果有左子则将左子作为根进行打印
    if (root->Lchild != NULL)
        PreOrder(root->Lchild); // 左

    // 3.3 如果有右子则将右子作为根进行打印
    if (root->Rchild != NULL)
        PreOrder(root->Rchild); // 右
}

// 4. 中序遍历二叉树
// 左——根——右
void InOrder(BT root)
{
    // 空树
    if (root == NULL)
        return;

    // 4.1 如果有左子则将左子作为根进行打印
    if (root->Lchild != NULL)
        InOrder(root->Lchild); // 左

    // 4.2 打印根结点数据(编号)
    printf("%d ", root->data); // 根

    // 4.3 如果有右子则将右子作为根进行打印
    if (root->Rchild != NULL)
        InOrder(root->Rchild); // 右
}

// 5. 后序遍历二叉树
// 左——右——根
void PostOrder(BT root)
{
    // 空树
    if (root == NULL)
        return;

    // 5.1 如果有左子则将左子作为根进行打印
    if (root->Lchild != NULL)
        PostOrder(root->Lchild); // 左

    // 5.2 如果有右子则将右子作为根进行打印
    if (root->Rchild != NULL)
        PostOrder(root->Rchild); // 右

    // 5.3 打印根结点数据(编号)
    printf("%d ", root->data); // 根
}

int main(int argc, char const *argv[])
{
    // 适用于,根节点编号 非0情况
    BT root = BTInit(13, 1);

    printf("先序遍历:");
    PreOrder(root);
    printf("\n");

    printf("中序遍历:");
    InOrder(root);
    printf("\n");

    printf("后序遍历:");
    PostOrder(root);
    printf("\n");
}

1.4.8. 有 层次遍历代码:多文件

目录结构:

#include "BiTree.h"

// 2. 创建二叉树
BT BTInit(int n, int i) // n 结点总数;  i 目前结点的编号,根节点就是1
{
    // 1. 开辟空间存放根结点
    BT root = (BT)malloc(sizeof(BTN));
    if (NULL == root)
    {
        printf("BTInit failed, root malloc err.\n");
        return NULL;
    }
    // 2. 初始化根结点成员
    // 2.1 对根结点进行赋值(编号)
    root->data = i; // 传过来的根结点编号

    // 下面是给左右子赋值,赋值的前提是左右子存在。那么就需要判断一下
    // 怎么判断?

    // 2.2 判断有无左右孩子

    // 2.2.1 有左子,让左子作为根重复本函数的操作
    if (2 * i <= n)
        root->Lchild = BTInit(n, 2 * i); // 因为根的编号是i,根的左子编号就是2*i
    // 2.2.2 没有左子,那么将root->Lchild置为NULL
    else
        root->Lchild = NULL;

    // 2.2.3 有右子,让右子作为根重复本函数的操作
    if (2 * i + 1 <= n)
        root->Rchild = BTInit(n, 2 * i + 1);
    // 2.2.4 没有右子,那么将root->Rchild置为NULL
    else
        root->Rchild = NULL;

    return root;
}

// 3. 先序遍历二叉树
// 根——左——右
void PreOrder(BT root)
{
    // 空树
    if (root == NULL)
        return;

    // 3.1 打印根结点数据(编号)
    printf("%d ", root->data); // 根

    // 3.2 如果有左子则将左子作为根进行打印
    if (root->Lchild != NULL)
        PreOrder(root->Lchild); // 左

    // 3.3 如果有右子则将右子作为根进行打印
    if (root->Rchild != NULL)
        PreOrder(root->Rchild); // 右
}

// 4. 中序遍历二叉树
// 左——根——右
void InOrder(BT root)
{
    // 空树
    if (root == NULL)
        return;

    // 4.1 如果有左子则将左子作为根进行打印
    if (root->Lchild != NULL)
        InOrder(root->Lchild); // 左

    // 4.2 打印根结点数据(编号)
    printf("%d ", root->data); // 根

    // 4.3 如果有右子则将右子作为根进行打印
    if (root->Rchild != NULL)
        InOrder(root->Rchild); // 右
}

// 5. 后序遍历二叉树
// 左——右——根
void PostOrder(BT root)
{
    // 空树
    if (root == NULL)
        return;

    // 5.1 如果有左子则将左子作为根进行打印
    if (root->Lchild != NULL)
        PostOrder(root->Lchild); // 左

    // 5.2 如果有右子则将右子作为根进行打印
    if (root->Rchild != NULL)
        PostOrder(root->Rchild); // 右

    // 5.3 打印根结点数据(编号)
    printf("%d ", root->data);   // 根
}

// 6. 层次遍历
void UnOrder(BT root)
{
    // 6.1 创建一个队列,队列的数据域变成指向树结点的指针
    LQ *PQ = LQInit();
    if (root != NULL)
        LQPush(PQ, root); // 入队时候传递的是指针

    while (PQ->front != PQ->rear) // 出列出列出着出着就空了
    {
        root = LQPop(PQ);
        printf("%d ", root->data);

        // 只要左子不为空,就入列,之后出列的时候打印
        if (root->Lchild != NULL)
            LQPush(PQ, root->Lchild);
            
        // 只要右子不为空,就入列,之后出列的时候打印
        if (root->Rchild != NULL)
            LQPush(PQ, root->Rchild);
    }
    // 思想:先让根入队、再左子入队、再右子入队,这样依次入队,入队完之后每次循环出队一个。
    // 出着出着就空了
}
#ifndef _BINARY_TREE_H_
#define _BINARY_TREE_H_

#include <stdio.h>
#include <stdlib.h>
#include "LinkQueue.h"

typedef struct BinaryTreeNode
{
    int data;                      // 数据域
    struct BinaryTreeNode *Lchild; // 左孩子指针
    struct BinaryTreeNode *Rchild; // 右孩子指针
} BTN, *BT;

// 2. 创建二叉树
BT BTInit(int n, int i);

// 3. 先序遍历二叉树
// 根——左——右
void PreOrder(BT root);

// 4. 中序遍历二叉树
// 左——根——右
void InOrder(BT root);

// 5. 后序遍历二叉树
// 左——右——根
void PostOrder(BT root);

// 6. 层次遍历
void UnOrder(BT root);

#endif
#include "LinkQueue.h"

// 2. 创建一个空的链式队列
LQ *LQInit()
{
    // 2.1 开辟空间存放操作链式队列的结构
    // 想单独使用结构体指针必须要开辟空间
    LQ *PQ = (LQ *)malloc(sizeof(LQ));
    if (NULL == PQ)
    {
        printf("LQInit failed, PQ malloc err.\n");
        return NULL;
    }

    // 2.2 对结构体成员进行初始化(malloc开辟空间存放链表结点)
    PQ->front = PQ->rear = (LL)malloc(sizeof(LLN));
    if (NULL == PQ->front)
    {
        printf("LQInit failed, front&rear malloc err.\n");
        return NULL;
    }
    // 2.3 对链表结点进行初始化,即next置NULL
    PQ->front->next = NULL;
    // PQ->rear->next = NULL;

    return PQ;
}

// 3. 入列,尾插(知道终端结点的单向链表尾插)
int LQPush(LQ *PQ, LLNDataType data)
{
    // 3.1 创建一个新结点保存即将插入的数据
    LL PNew = (LL)malloc(sizeof(LLN));
    if (NULL == PNew)
    {
        printf("PQPush faild, PNew malloc err.\n");
        return -1;
    }

    // 3.2 初始化新结点
    PNew->data = data;
    PNew->next = NULL;

    // 3.3 新结点链接到链表的尾巴
    PQ->rear->next = PNew;

    // 3.4 移动队尾指针rear使其指向新的终端结点
    PQ->rear = PNew;

    return 0;
}

// 4. 打印链式队列(使用front队头指针进行遍历,打印结点的数据域)
// void LQPrint(LQ *PQ)
// {
//     LL H = PQ->front;
//     while (H->next) // while (H->next != NULL)
//     {
//         H = H->next;
//         printf("%d\t", H->data);
//     }
//     printf("\n");
// }

// 5. 数据出列(返回NULL)
LLNDataType LQPop(LQ *PQ)
{
    // 5.1 容错判断(空了没法出)
    if (PQ->front == PQ->rear)
    {
        printf("LQPop failed, LQ is empty.\n");
        return NULL;
    }

    // 5.2 PDel指向头结点
    LL PDel = PQ->front;

    // 5.3 队头指针向后移动
    PQ->front = PQ->front->next;

    // 5.5 释放老头结点
    free(PDel);
    PDel = NULL;

    // 5.6 返回出列数据
    return PQ->front->data;
}

// 6. 求队列长度
int LQLength(LL H)
{
    int len = 0;
    while (H->next != NULL)
    {
        H = H->next;
        len++;
    }
    return len;
}

// 7. 清空队列
void LQClear(LQ *PQ)
{
    while (PQ->front != PQ->rear)
        LQPop(PQ);
}
#ifndef _LINK_QUEUE_H_
#define _LINK_QUEUE_H_

#include <stdio.h>
#include <stdlib.h>
#include "BiTree.h" // 需要知道 二叉树结点 的结构体类型

// 1. 定义结构体
// 1.1 定义链式队列结点结构体
typedef struct BinaryTreeNode *LLNDataType; // 把队列的数据域变成指向树结点的指针,不能连续typedef
typedef struct LinkListNode
{
    LLNDataType data;
    struct LinkListNode *next;
} LLN, *LL;

// 1.2 定义操作链式队列的结构体
typedef struct LinkQueue
{
    LL front; // 队头结点指针(LLN * front)(struct LinkListNode *)
    LL rear;  // 队尾结点指针
} LQ;

// 2. 创建一个空的链式队列
LQ *LQInit();

// 3. 入列,尾插(知道终端结点的单向链表尾插)
int LQPush(LQ *PQ, LLNDataType data);

// 4. 打印链式队列(使用front队头指针进行遍历,打印结点的数据域)
//void LQPrint(LQ *PQ);

// 5. 数据出列(返回NULL)
LLNDataType LQPop(LQ *PQ);

// 6. 求队列长度
int LQLength(LL H);

// 7. 清空队列
void LQClear(LQ *PQ);

#endif
#include "LinkQueue.h"
#include "BiTree.h"

int main(int argc, char const *argv[]) 
{
    // 适用于,根节点编号 非0情况
    BT root = BTInit(13, 1);

    printf("先序遍历:");
    PreOrder(root);
    printf("\n");

    printf("中序遍历:");
    InOrder(root);
    printf("\n");

    printf("后序遍历:");
    PostOrder(root);
    printf("\n");

    printf("层序遍历:");
    UnOrder(root);
    printf("\n");

    return 0;
}
CC=gcc
CFLAG=-c -g -Wall -o 
OBJS=MainTest.o LinkQueue.o BiTree.o 

target:$(OBJS)
	$(CC) $^ -o $@

%.o:%.c 
	$(CC) $(CFLAG) $@ $<

.PHONY:clean
clean:
	rm *.o target -f

1.5. 练习题

  1. 深度为8的二叉树,其最多有( 28-1 ) 个节点,第8层最多有( 27 )个节点 (网易)
  2. 数据结构中,沿着某条路线,依次对树中每个结点做一次且仅做一次访问,对二叉树的节点从1开始进行连续编号,要求每个结点的编号大于其左、右孩子的编号,同一结点的左右孩子中,其左子的编号小于其右子的编号,可采用( )次序的遍历实现编号 (网易)

根要大于左右,即根要在后面被访问。

A. 先序 B. 中序 C. 后序 D. 从根开始层次遍历

  1. 一颗二叉树的 前序: A B D E C F, 中序:B D A E F C 问树的深度是 ( ) (网易)

画出树

A. 3 B. 4 C. 5 D. 6

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值