【2025年软考中级】第三章数据结构3.5 树与二叉树

树与二叉树

树和森林

树的性质(重点)

  1. 树总结点树=度之和+1=分支节点总和+1

    • 度之和为各分支节点度数之和(如0×n0+1×n1+2×n2+... n i n_i ni为度为i的节点数)。
  2. 第i层最多有m^(i-1)个结点(m为树的度,即节点的最大子节点数)。

  3. 高度为h的m次树的总结点最多为(m^h-1)/(m-1)

    • 推导:等比数列求和,首项1,公比m,共h项。
  4. 具有n个结点的m次树的最小高度为⌈log_m(n(m-1)+1)⌉*

    • 保证树高度最小时,各层节点数尽可能多,需满足(m^h-1)/(m-1) ≥ n

树的遍历

  1. 先根遍历

    • 顺序:先访问根节点,再递归遍历各子树。
    • 等价性:与森林的先根遍历、二叉树的先序遍历结果一致(需转换为对应结构)。
  2. 后根遍历

    • 顺序:先递归遍历各子树,再访问根节点。
    • 等价性:与森林的后根遍历、二叉树的中序遍历结果一致。
  3. 层次遍历

    • 顺序:按层从左到右访问节点,等价于图的广度优先遍历(BFS)。

树的存储结构

1. 双亲存储
typedef struct {
    elemtype data;  // 结点值
    int parent;     // 双亲位置(数组下标)
} PTree[Maxsize];  // 双亲存储结构类型
  • 优点:快速查找节点的双亲。
  • 缺点:查找子节点需遍历整个数组。
2. 孩子链存储
typedef struct node {
    elemtype data;
    struct node *sons[Maxsons];  // 指向孩子结点的指针数组
} TSonNode;
  • 空域计算:指针总数-分支数 = n×m - (n-1)(m为Maxsons,n为节点数)。
  • 求树高度的递归算法
    int treeheight(TSonNode *t) {
        TSonNode *p;
        int i, h, maxh = 0;
        if (t == NULL) return 0;
        for (i = 0; i < Maxsons; i++) {
            p = t->sons[i];
            if (p != NULL) {
                h = treeheight(p);
                if (maxh < h) maxh = h;
            }
        }
        return maxh + 1;  // 根节点高度+1
    }
    
3. 兄弟链存储(左孩子右兄弟)
typedef struct tnode {
    elemtype data;
    struct tnode *hp;  // 指向兄弟
    struct tnode *vp;  // 指向第一个孩子
} TsbNode;
  • 核心思想:用“左孩子、右兄弟”表示树结构,便于树与二叉树转换。
  • 空域计算:指针总数-分支数 = 2n - (n-1) = n+1(每个节点2个指针)。
  • 求树高度的递归算法
    int treeheight2(TsbNode *t) {
        TsbNode *p;
        int h, maxh = 0;
        if (t == NULL) return 0;
        p = t->vp;  // 指向第一个孩子
        while (p != NULL) {
            h = treeheight2(p);
            if (maxh < h) maxh = h;
            p = p->hp;  // 遍历兄弟
        }
        return maxh + 1;
    }
    

二叉树

(完全)二叉树的性质

  1. n0 = n2 + 1(叶子节点数=度为2的节点数+1)。
  2. n = n0 + n1 + n2 = 2n0 + n1 - 1(n为总节点数,n1为度为1的节点数)。
  3. **第i层最多有2(i-1)个结点**,深度为k的二叉树最多有2k - 1个结点。
  4. 完全二叉树深度为⌊log2(n)⌋ + 1(n为节点数)。
  5. 层序编号规则(编号从1开始):
    • 若i≤n/2,i为分支节点;否则为叶子节点。
    • 左孩子编号2i,右孩子编号2i+1,父节点编号⌊i/2⌋。

二叉树遍历

1. 先序遍历(根-左-右)
void preOrder(btNode *b) {
    if (b != NULL) {
        printf("%c", b->data);  // 访问根
        preOrder(b->lchild);   // 递归左子树
        preOrder(b->rchild);   // 递归右子树
    }
}
  • 结果类似前缀表达式,第一个节点为根。
2. 中序遍历(左-根-右)
void inOrder(btNode *b) {
    if (b != NULL) {
        inOrder(b->lchild);    // 递归左子树
        printf("%c", b->data);  // 访问根
        inOrder(b->rchild);    // 递归右子树
    }
}
  • 结果类似中缀表达式,根节点在左右子树之间。
3. 后序遍历(左-右-根)
void postOrder(btNode *b) {
    if (b != NULL) {
        postOrder(b->lchild);   // 递归左子树
        postOrder(b->rchild);   // 递归右子树
        printf("%c", b->data);  // 访问根
    }
}
  • 结果类似后缀表达式,最后一个节点为根。
4. 层次遍历
  • 非递归实现,借助队列按层访问,先入队根节点,再依次入队左右子节点。

二叉树、树、森林的转换

1. 二叉树转树
  • 规则
    1. 二叉树的左孩子为树中该节点的最左孩子。
    2. 二叉树的右孩子为树中该节点的兄弟。
  • 要求:根节点无右孩子(否则转成森林)。
  • 方法:将右孩子连线左转45°,断开兄弟间连线,连接至双亲。
2. 二叉树转森林
  • 规则:同二叉树转树,但根节点需有右子树。
  • 方法:断开根节点与右子树的连线,右子树转为森林的其他树。
3. 树转二叉树
  • 规则:第一棵子树为左孩子,下一个兄弟为右孩子。
  • 方法:兄弟间连虚线,删除非最左子树连线,虚线右转45°。
4. 森林转二叉树
  • 方法:将森林中各树转二叉树后,以第一棵树的根为根,其他树的根作为其右孩子。

二叉树的存储结构

1. 顺序存储
typedef char elemtype;
typedef elemtype SqBinTree[Maxsize];  // 课本定义
  • 适用于完全二叉树,按层序存储,空节点用特殊符号表示(如#)。
2. 二叉链表
typedef char elemtype;
typedef struct node {
    elemtype data;
    struct node *lchild, *rchild;
} btNode;
  • 空域计算:总指针数2n,分支数n-1,空域数2n - (n-1) = n+1。
3. 三叉链表
typedef char elemtype;
typedef struct node {
    elemtype data;
    struct node *lchild, *rchild, *parent;
} btNode;
  • 增加父指针,便于查找祖先,空域数3n - 2(n-1) = n+2。

二叉树的构造

  1. 先序+中序构造
    • 先序第一个节点为根,中序中根左侧为左子树,右侧为右子树,递归构造。
  2. 后序+中序构造
    • 后序最后一个节点为根,中序中划分左右子树,递归构造。
  3. 层次+中序构造
    • 层次第一个节点为根,中序中划分左右子树,结合层次序列确定子树顺序。

特殊二叉树与应用

线索二叉树

  • 目的:利用空指针记录前驱和后继,提高遍历效率。
  • 规则
    • 左空指针指向中序前驱,右空指针指向中序后继。
    • 增加标志位区分指针指向子节点或线索。

二叉排序树(BST)

  • 性质:左子树所有节点值<根节点<右子树所有节点值。
  • 应用:高效查找、插入、删除,平均时间复杂度O(logn)。

平衡二叉树(AVL树)

  • 性质:任意节点的左右子树高度差≤1。
  • 目的:保证查找效率,避免退化为链表。

  • 性质
    • 完全二叉树,分为大根堆(父节点≥子节点)和小根堆(父节点≤子节点)。
    • 用数组存储,i节点的左孩子2i+1,右孩子2i+2(从0开始编号)。
  • 应用:优先级队列、堆排序。

哈夫曼树(最优二叉树)

1. 构造步骤(重点)
  1. 选两个权值最小的节点合并为新节点,新节点权值为两节点权值之和。
  2. 重复步骤1,直到只剩一个节点(共合并n-1次,n为叶子节点数)。
2. 带权路径长度(WPL)计算
  • 公式:WPL = Σ(叶子节点权值×路径长度)。
  • 示例:叶子节点权值为{1,2,3,4},构造哈夫曼树的WPL=1×3+2×3+3×2+4×2=21。
3. 哈夫曼编码(前缀编码)
  • 规则:左子树编码0,右子树编码1,叶子节点的编码为从根到叶子的路径序列。
  • 性质:无任何编码是其他编码的前缀,用于数据压缩。
4. 关键结论
  • 哈夫曼树中n0 = n2 + 1,且无度为1的节点(n1=0)。
  • 总节点数=2n0 - 1=2n2 + 1。

并查集

  • 功能:支持集合的合并(Union)和查找(Find)操作。
  • 实现:用双亲存储结构,每个节点记录父节点,路径压缩优化查找效率。

图示参考

  • 树的性质示意图:
    45
  • 树与二叉树转换示例:
    48
  • 二叉树存储结构示意图:
  • 哈夫曼树构造示例:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

houliabc

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

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

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

打赏作者

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

抵扣说明:

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

余额充值