树和二叉树

一.树和森林

•树:一对多的结构(可1对0,1对1,一对多),有一个起点‘根结点’

•结点:树的一个数据元素

孩子:1对多里的‘多’

•子树:以某个孩子结点为根的一棵树

•叶子结点:没有孩子的结点

•森林:多棵树

二.二叉树

•二叉树:每个结点至多有两个孩子(可以1个或0个),分别称为左孩子和右孩子

•左孩子(若有)是左子树的根,右孩子(若有)是右子树的根

•高度(深度):最深的叶子结点所在层数

•二叉树的重要性质:

 •第i层至多有2的i-1次方个结点

•高度为h的树至多有2的h次方-1个结点

*key1:二叉树每个结点有0或1或2个孩子,叶子结点没有孩子

*key2:二叉树的高度指从根节点向下直至最深叶子结点的高度

1.由3个结点可以构成多少颗结构不同的二叉树?()

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

分析:根节点左右子树的结点数量存在1-1,0-2,或2-0三种可能,而2个结点构成的二叉树结构只可能有2种,因此答案为1+2+2=5。

2.一颗二叉树有1025个结点,则其高度为()

A.10        B.11        C.11至1025之间        D.10至1025之间

分析:高度为10的二叉树至多有,因此高度至少为11,1025个结点至少向下延申1025层,因此答案选C.

两种特殊的二叉树:

•满二叉树:

装满的二叉树,高为h=>有2的h次方-1个结点

•完全二叉树

只在最下一层的最右边空缺

 *key1:满二叉树是满的,完全二叉树只是完整了,但不一定满

1.一颗完全二叉树有1001个结点,则叶子结点个数为()

A.250        B.254        C.500        D.501

树/森林转换为二叉树

•树转换为二叉树:

        •每个结点只保留第一个孩子(老大)作为左孩子,剩下的孩子(老大的兄弟们)依次接到老大的右孩子链上

•森林转为二叉树:

        1.各树分别转为二叉树

        2.各树根用右孩子链相连

举例:

 

 *key1:森林向二叉树转化是确定且唯一的过程

1.将一颗树转换为二叉树后,其形态()

A.是唯一的        B.有多种        C.有多种,但根结点都没有右孩子        D.有多种,但根结点都没有左孩子

分析:是唯一的,且根结点没有右孩子

二叉树顺序存储实现(逻辑结构)

•顺序二叉树(底层是数组)

•顺序树中结点i的左右孩子分别是2i+1和2i+2(i从0开始计数)

•若结点为空,使用特殊值(如0)表示

二叉树链式实现

•链式树(二叉链表)

/*二叉树数据结构定义*/
typedef struct TreeNode{
    int data;
    struct    TreeNode *left;
    struct    TreeNode *right;
}TreeNode;

 三.二叉树的遍历

•遍历:按某种确定的次序逐个访问所有结点(时间复杂度为O(n))

•先序遍历:当前结点-左子树-右子树

•中序遍历:左子树-当前结点-右子树

•后序遍历:左子树-右子树-当前结点

• *层序遍历:逐层从左向右遍历各个结点

•注意:右孩子是右子树的根,左孩子是左子树的根

•注意:一棵树是用其根结点表示的,因从根节点出发我们足以访问整棵树

 二叉树先序/中序/后序遍历

*key1:二叉树三种遍历顺序中,左子树都先于右子树,区别在于访问根的次序

*key2:可以通过中序+先序/后序序列之一来还原二叉树结构,先+后则不行

1.一颗二叉树的先序遍历序列为3541982,中序遍历序列为5413892,请还原其结构

分析:中+先/后还原树结构的问题,关键点在于可以通过先/后序序列确定根结点,再通过中序遍历里根结点的位置划分左右子树,问题减小为分别还原左右子树,依次逐层往下直至还原完成。

2.为什么不一定能通过先序+后序序列还原二叉树?

分析:实际是问为何必需中序序列才可以保证还原,思考中序序列提供了什么重要信息?

提供了左右子树如何划分的重要信息,而仅仅依靠先+后无法划分左右子树

 四.哈夫曼树

哈夫曼树与哈夫曼编码

•先学如何构建一颗哈夫曼树,再学为什么要有哈夫曼树!

•初学时,一堆独立结点,结点有自己的权值

 

 

 •重复地让当前权值最小的两个根结点作为左右孩子,生成新的根结点,新结点权值为它们的权值之和,直至形成一颗二叉树

Why哈夫曼树?

上述过程有什么特点?

•越靠近根节点,权值越大

•初始结点全是后来的叶子结点

•叶子结点权值越大,离根结点越近=>路径越短

•有什么好处?如果把路径标上0和1.....

•每个叶子结点有一个唯一编码(不定长)

 

•思考:如果权值表示在文章中的出现频率,这种编码有什么优势?

•最大程度节省空间!越常用的字符码长越短

•这就是哈夫曼编码

题型:给一个字符-频率表,构造哈夫曼树来求哈夫曼编码表

五.代码实现

/*
二叉树
*/

/* 链式二叉树数据结构定义 */
typedef struct TreeNode {
    int data;
    struct TreeNode *left;
    struct TreeNode *right;
} TreeNode;

// 注意:一棵树用它的根结点来表示,因为只要有根结点,就能访问整棵树

// 二叉树先序(前序)遍历的递归实现
#include <cstdio>
void PreTraverse(TreeNode *node) {
    if (!node) {
        return;  // 递归出口
    }
    printf("%d\n", node->data);  // 先访问当前结点
    PreTraverse(node->left);     // 其次递归遍历左子树
    PreTraverse(node->right);    // 最后递归遍历右子树
}

// 二叉树中序遍历的递归实现
void MidTraverse(TreeNode *node) {
    if (!node) {
        return;  // 递归出口
    }
    MidTraverse(node->left);     // 先递归遍历左子树
    printf("%d\n", node->data);  // 其次访问当前结点
    MidTraverse(node->right);    // 最后递归遍历右子树
}

// 二叉树后序遍历的递归实现
void PostTraverse(TreeNode *node) {
    if (!node) {
        return;  // 递归出口
    }
    PostTraverse(node->left);    // 先递归遍历左子树
    PostTraverse(node->right);   // 其次递归遍历右子树
    printf("%d\n", node->data);  // 最后访问根结点
}

// 求树的高度(深度)的递归实现
int GetHeight(TreeNode *node) {
    if (!node) {
        return 0;
    }
    int lh = GetHeight(node->left);
    int rh = GetHeight(node->right);
    // 返回 1 + 左右子树中高度较大者,这里1是指当前结点贡献1个高度
    return 1 + (lh > rh ? lh : rh);
}

// 二叉树的层序遍历(利用队列)
#include <queue>
void LevelTraverse(TreeNode *node) {
    if (!node) {
        return;
    }

    std::queue<TreeNode *> q;  // 队列元素是TreeNode*
    q.push(node);
    while (!q.empty()) {
        TreeNode *cur = q.front();  // 获取当前队首
        printf("%d\n", cur->data);  // 访问之
        q.pop();                    // 队首离开队伍
        if (cur->left) {
            q.push(cur->left);  // 左孩子(若有)入队
        }
        if (cur->right) {
            q.push(cur->right);  // 右孩子(若有)入队
        }
    }
}

练习:

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阳光少年.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值