【数据结构与算法】第23篇:树、森林与二叉树的转换

一、树的存储结构

1.1 双亲表示法

每个节点存储数据和父节点下标,适合找父节点的场景。

c

复制代码
#define MAX_SIZE 100
typedef struct {
    int data;
    int parent;  // 父节点下标
} PNode;

typedef struct {
    PNode nodes[MAX_SIZE];
    int root;    // 根节点下标
    int size;
} PTree;

缺点:找孩子需要遍历整个数组。

1.2 孩子表示法

每个节点用链表存储所有孩子,适合找孩子的场景。

c

复制代码
typedef struct ChildNode {
    int childIndex;
    struct ChildNode *next;
} ChildNode;

typedef struct {
    int data;
    ChildNode *firstChild;
} CNode;

缺点:找父节点不方便。

1.3 孩子兄弟表示法(重点)

每个节点存储:第一个孩子、右兄弟。

c

复制代码
typedef struct CSNode {
    int data;
    struct CSNode *firstChild;  // 第一个孩子
    struct CSNode *nextSibling; // 右兄弟
} CSNode, *CSTree;

这种表示法把 变成了二叉树------左指针指向孩子,右指针指向兄弟。

画个图:

text

复制代码
原树:              孩子兄弟表示法:
      A                     A
    / | \                  /
   B  C  D                B
      |                   \
      E                    C
                            \
                             D
                            /
                           E

二、树与二叉树的转换

2.1 转换规则(左孩子右兄弟)

原树关系 转换后二叉树关系
第一个孩子 左孩子
下一个兄弟 右孩子

口诀:左孩子,右兄弟。

2.2 手动转换示例

原树:

text

复制代码
        A
      / | \
     B  C  D
       / \
      E   F

转换步骤

  1. A的第一个孩子是B → A的左孩子是B

  2. B的兄弟是C → B的右孩子是C

  3. C的兄弟是D → C的右孩子是D

  4. C的第一个孩子是E → C的左孩子是E

  5. E的兄弟是F → E的右孩子是F

转换后的二叉树:

text

复制代码
        A
       /
      B
       \
        C
       / \
      E   D
       \
        F

2.3 转换后的特点

  • 根节点没有右孩子(因为根没有兄弟)

  • 左孩子是原树的第一个孩子

  • 右孩子是原树的下一个兄弟

2.4 代码实现

c

复制代码
#include <stdio.h>
#include <stdlib.h>

// 树节点(孩子兄弟表示法)
typedef struct CSNode {
    char data;
    struct CSNode *firstChild;
    struct CSNode *nextSibling;
} CSNode, *CSTree;

// 创建节点
CSNode* createNode(char data) {
    CSNode *node = (CSNode*)malloc(sizeof(CSNode));
    node->data = data;
    node->firstChild = NULL;
    node->nextSibling = NULL;
    return node;
}

// 手动构建一棵树
//        A
//      / | \
//     B  C  D
//       / \
//      E   F
CSTree buildTree() {
    CSNode *A = createNode('A');
    CSNode *B = createNode('B');
    CSNode *C = createNode('C');
    CSNode *D = createNode('D');
    CSNode *E = createNode('E');
    CSNode *F = createNode('F');
    
    A->firstChild = B;
    B->nextSibling = C;
    C->nextSibling = D;
    C->firstChild = E;
    E->nextSibling = F;
    
    return A;
}

// 二叉树的前序遍历(验证转换结果)
void preorder(CSNode *root) {
    if (root == NULL) return;
    printf("%c ", root->data);
    preorder(root->firstChild);
    preorder(root->nextSibling);
}

// 二叉树的中序遍历
void inorder(CSNode *root) {
    if (root == NULL) return;
    inorder(root->firstChild);
    printf("%c ", root->data);
    inorder(root->nextSibling);
}

int main() {
    CSTree tree = buildTree();
    
    printf("转换后的二叉树(前序): ");
    preorder(tree);
    printf("\n");
    
    printf("转换后的二叉树(中序): ");
    inorder(tree);
    printf("\n");
    
    return 0;
}

运行结果:

text

复制代码
转换后的二叉树(前序): A B C E F D 
转换后的二叉树(中序): B E F C D A 

三、森林与二叉树的转换

3.1 森林的定义

森林是 m(m ≥ 0)棵互不相交的树的集合。

3.2 森林转二叉树

规则

  1. 将森林中的每棵树分别转换成二叉树(左孩子右兄弟)

  2. 将每棵树的根节点视为兄弟关系,用右指针连接

步骤

  • 第一棵树的根作为转换后二叉树的根

  • 第二棵树的根作为第一棵树根的右孩子

  • 第三棵树的根作为第二棵树根的右孩子

  • 以此类推...

text

复制代码
森林:          转换后的二叉树:
Tree1  Tree2    Tree1根
  ↓      ↓        \
Tree1根 Tree2根    Tree2根
                    \
                     Tree3根

3.3 二叉树转森林

判断条件:如果二叉树的根节点有右孩子,说明它是由森林转换来的。

规则

  1. 将根节点和左子树作为第一棵树

  2. 将右子树作为剩下的森林,递归处理

3.4 代码实现

c

复制代码
#include <stdio.h>
#include <stdlib.h>

typedef struct CSNode {
    char data;
    struct CSNode *firstChild;
    struct CSNode *nextSibling;
} CSNode, *CSTree;

CSNode* createNode(char data) {
    CSNode *node = (CSNode*)malloc(sizeof(CSNode));
    node->data = data;
    node->firstChild = NULL;
    node->nextSibling = NULL;
    return node;
}

// 构建森林:三棵树
// 树1: A       树2: D       树3: G
//     / \          |            |
//    B   C         E            H
//                  |            |
//                  F            I
CSTree buildForest() {
    // 树1
    CSNode *A = createNode('A');
    CSNode *B = createNode('B');
    CSNode *C = createNode('C');
    A->firstChild = B;
    B->nextSibling = C;
    
    // 树2
    CSNode *D = createNode('D');
    CSNode *E = createNode('E');
    CSNode *F = createNode('F');
    D->firstChild = E;
    E->firstChild = F;
    
    // 树3
    CSNode *G = createNode('G');
    CSNode *H = createNode('H');
    CSNode *I = createNode('I');
    G->firstChild = H;
    H->firstChild = I;
    
    // 连接成森林(根节点用右兄弟连接)
    A->nextSibling = D;
    D->nextSibling = G;
    
    return A;
}

// 森林转二叉树(其实构建森林时已经按规则连接好了)
// 这里只是验证

// 二叉树转森林:按右兄弟断开
void forestToTree(CSTree root) {
    if (root == NULL) return;
    
    printf("树根: %c\n", root->data);
    
    // 递归处理左子树(孩子)
    if (root->firstChild) {
        printf("  %c 的孩子: ", root->data);
        CSNode *child = root->firstChild;
        while (child) {
            printf("%c ", child->data);
            child = child->nextSibling;
        }
        printf("\n");
        forestToTree(root->firstChild);
    }
    
    // 递归处理右子树(下一棵树)
    if (root->nextSibling) {
        forestToTree(root->nextSibling);
    }
}

void preorder(CSTree root) {
    if (root == NULL) return;
    printf("%c ", root->data);
    preorder(root->firstChild);
    preorder(root->nextSibling);
}

int main() {
    CSTree forest = buildForest();
    
    printf("森林转换后的二叉树(前序): ");
    preorder(forest);
    printf("\n\n");
    
    printf("从二叉树还原森林:\n");
    forestToTree(forest);
    
    return 0;
}

运行结果:

text

复制代码
森林转换后的二叉树(前序): A B C D E F G H I 

从二叉树还原森林:
树根: A
  A 的孩子: B C 
树根: B
树根: C
树根: D
  D 的孩子: E 
树根: E
  E 的孩子: F 
树根: F
树根: G
  G 的孩子: H 
树根: H
  H 的孩子: I 
树根: I

四、树与二叉树的遍历对应关系

树的遍历 对应二叉树的遍历
先根遍历 前序遍历
后根遍历 中序遍历

验证:以前面的树为例

原树先根遍历:A B C E F D

对应二叉树前序:A B C E F D

原树后根遍历:B E F C D A

对应二叉树中序:B E F C D A


五、三种存储方式对比

存储方式 找孩子 找父节点 空间开销 适用场景
双亲表示法 并查集
孩子表示法 树形结构遍历
孩子兄弟表示法 树转二叉树

六、小结

这一篇我们学习了树、森林与二叉树的转换:

要点 说明
孩子兄弟表示法 firstChild + nextSibling
树→二叉树 左孩子,右兄弟
森林→二叉树 先每棵树转二叉树,再根节点用右兄弟连接
遍历对应 树先根=二叉树前序,树后根=二叉树中序

核心口诀

  • 左孩子:第一个孩子

  • 右兄弟:下一个兄弟

  • 森林转:根连根

下一篇我们讲哈夫曼树与哈夫曼编码。


七、思考题

  1. 一棵树转换成的二叉树,它的右子树可能为空吗?什么情况下为空?

  2. 森林转换成的二叉树,根节点的右子树可能为空吗?什么情况下为空?

  3. 如果已知一棵二叉树的先序和中序遍历序列,如何判断它是由树还是由森林转换来的?

  4. 尝试用孩子兄弟表示法实现树的先根遍历和后根遍历。

欢迎在评论区讨论你的答案。

相关推荐
chushiyunen2 小时前
大模型评测、质量保证、datasets数据集、LmEval工具
开发语言·python
hnjzsyjyj2 小时前
洛谷 P2015:二叉苹果树 ← 有依赖的背包问题
数据结构·有依赖的背包
伯恩bourne2 小时前
SpringDoc OpenAPI 3 常用注解详解
java·开发语言
温九味闻醉2 小时前
人工智能应用作业1:PPO强化学习算法
人工智能·算法
ab1237682 小时前
C++ size() 与 length() 核心笔记
开发语言·c++·笔记
苏宸啊2 小时前
哈希表开放定址法增删改查简单实现
数据结构·c++
apcipot_rain2 小时前
Python 脚本生成目录树
开发语言·python
kyriewen113 小时前
本地存储全家桶:从localStorage到IndexedDB,把数据塞进用户浏览器
开发语言·前端·javascript·ecmascript·html5
玉小格3 小时前
动态内存管理
数据结构