EC21-将顺序存储的二叉树转换为二叉链表表示
代码实现
| C |
|---|
| // 函数 create:将顺序存储的二叉树转换为二叉链表表示
// 参数:
// A[] - 存储二叉树的顺序数组
// i - 当前处理节点在数组中的索引
// n - 数组的长度(节点总数)
// 返回值:构建好的二叉树节点指针
BiTree create(char A[], int i, int n) {
// 递归终止条件:索引越界或当前节点为空节点
if (i < n && A[i] != '#') {
// 创建新节点并分配内存
BiTree t = (BiTree)malloc(sizeof(BTNode));
// 将数组中当前索引位置的数据赋值给节点
t->data = A[i];
// 递归构建左子树
// 左子节点在数组中的索引为:2*i + 1
t->lchild = create(A, 2 * i + 1, n);
// 递归构建右子树
// 右子节点在数组中的索引为:2*i + 2
t->rchild = create(A, 2 * i + 2, n);
return t; // 返回构建好的节点
}
// 如果索引越界或节点为空(用'#'表示),返回NULL
return NULL;
}
|
代码逻辑解析
1. 顺序存储的二叉树
- 使用一维数组存储二叉树节点
- 对于索引为
i 的节点:
- 左子节点索引:
2*i + 1
- 右子节点索引:
2*i + 2
- 父节点索引:
(i-1)/2(向下取整)
- 空节点用特殊字符(如
'#')表示
2. 算法思路
- 采用递归方式遍历数组
- 对每个非空节点创建对应的二叉树节点
- 通过索引关系递归构建左右子树
关键算法理解
1. 数组索引映射关系
完全二叉树的数组表示:
| Text Only |
|---|
| 数组索引: 0 1 2 3 4 5 6
数组元素: [A, B, C, D, E, F, G]
对应的二叉树结构:
A // 索引 0
/ \
B C // 索引 1,2
/ \ / \
D E F G // 索引 3,4,5,6
|
索引关系验证:
- 节点A(索引0):左子B(2×0+1=1),右子C(2×0+2=2)
- 节点B(索引1):左子D(2×1+1=3),右子E(2×1+2=4)
- 节点C(索引2):左子F(2×2+1=5),右子G(2×2+2=6)
2. 图解说明
假设数组存储的二叉树:
| Text Only |
|---|
| 数组:['A', 'B', 'C', 'D', '#', 'F', 'G']
索引: 0 1 2 3 4 5 6
对应的二叉树结构:
A // 索引 0
/ \
B C // 索引 1,2
/ / \
D F G // 索引 3,5,6
(索引4为空)
|
构建过程:
| Text Only |
|---|
| create(A, 0, 7): A[0]='A' ≠ '#'
├─ 创建节点A
├─ create(A, 1, 7): A[1]='B' ≠ '#'
│ ├─ 创建节点B
│ ├─ create(A, 3, 7): A[3]='D' ≠ '#'
│ │ ├─ 创建节点D
│ │ ├─ create(A, 7, 7): 7≥7 → 返回NULL
│ │ └─ create(A, 8, 7): 8≥7 → 返回NULL
│ │ └─ 返回节点D
│ └─ create(A, 4, 7): A[4]='#' → 返回NULL
│ └─ 返回节点B
└─ create(A, 2, 7): A[2]='C' ≠ '#'
├─ 创建节点C
├─ create(A, 5, 7): A[5]='F' ≠ '#'
│ └─ ... → 返回节点F
└─ create(A, 6, 7): A[6]='G' ≠ '#'
└─ ... → 返回节点G
└─ 返回节点C
└─ 返回根节点A
|
时间复杂度分析
1. 访问节点数
- 每个数组元素被访问 exactly 一次
- 总共访问
n 个元素
2. 每次访问的操作
- 索引比较:O(1)
- 内存分配:O(1)
- 赋值操作:O(1)
- 递归调用:O(1)
3. 总时间复杂度
O(n),其中 n 是数组长度
空间复杂度分析
1. 额外空间使用
- 新创建的二叉树节点:O(n)
- 每个节点占用常量空间
2. 递归调用栈空间
- 递归深度取决于树的高度
h
- 最好情况(完全二叉树):O(log n)
- 最坏情况(退化为链表):O(n)
3. 总空间复杂度
O(n)(节点存储)+ O(h)(递归栈)
最坏情况下为 O(n)
边界条件处理
1. 索引越界检查
| C |
|---|
| if (i < n && A[i] != '#')
|
- i < n:防止数组越界访问
- A[i] != '#':跳过空节点
2. 空节点处理
- 用特殊字符
'#'表示空节点
- 遇到空节点直接返回NULL
延伸知识点
1. 顺序存储与链式存储的对比
| 特性 |
顺序存储 |
链式存储 |
| 内存利用率 |
高(完全二叉树) |
低(需要指针域) |
| 插入/删除 |
复杂(需要移动元素) |
简单(修改指针) |
| 随机访问 |
O(1) |
O(h) |
| 空间浪费 |
可能(非完全二叉树) |
少(按需分配) |
2. 反向转换:二叉链表转顺序存储
| C |
|---|
| // 计算二叉树节点数
int countNodes(BiTree root) {
if (root == NULL) return 0;
return 1 + countNodes(root->lchild) + countNodes(root->rchild);
}
// 二叉链表转顺序存储
void treeToArray(BiTree root, char A[], int i) {
if (root == NULL) {
A[i] = '#'; // 空节点用'#'表示
return;
}
A[i] = root->data;
treeToArray(root->lchild, A, 2 * i + 1);
treeToArray(root->rchild, A, 2 * i + 2);
}
// 完整转换函数
char* convertTreeToArray(BiTree root, int* size) {
*size = countNodes(root) * 2; // 估算数组大小
char* A = (char*)malloc(*size * sizeof(char));
treeToArray(root, A, 0);
return A;
}
|
3. 层次遍历构建
| C |
|---|
| // 使用队列进行非递归构建
BiTree createIterative(char A[], int n) {
if (n == 0 || A[0] == '#') return NULL;
BiTree root = (BiTree)malloc(sizeof(BTNode));
root->data = A[0];
root->lchild = root->rchild = NULL;
Queue* queue = createQueue();
enqueue(queue, root);
int i = 1;
while (!isEmpty(queue) && i < n) {
BiTree node = dequeue(queue);
// 处理左子节点
if (i < n && A[i] != '#') {
node->lchild = (BiTree)malloc(sizeof(BTNode));
node->lchild->data = A[i];
node->lchild->lchild = node->lchild->rchild = NULL;
enqueue(queue, node->lchild);
}
i++;
// 处理右子节点
if (i < n && A[i] != '#') {
node->rchild = (BiTree)malloc(sizeof(BTNode));
node->rchild->data = A[i];
node->rchild->lchild = node->rchild->rchild = NULL;
enqueue(queue, node->rchild);
}
i++;
}
return root;
}
|
4. 优化的递归版本
| C |
|---|
| // 带错误检查的版本
BiTree createSafe(char A[], int i, int n) {
// 输入验证
if (A == NULL || n <= 0 || i < 0) {
return NULL;
}
// 边界检查
if (i >= n) {
return NULL;
}
// 空节点检查
if (A[i] == '#') {
return NULL;
}
// 创建节点
BiTree node = (BiTree)malloc(sizeof(BTNode));
if (node == NULL) {
return NULL; // 内存分配失败
}
node->data = A[i];
node->lchild = createSafe(A, 2 * i + 1, n);
node->rchild = createSafe(A, 2 * i + 2, n);
return node;
}
|
5. 实际应用场景
- 堆数据结构:优先队列的实现
- 完全二叉树操作:如堆排序
- 树形数据的序列化:网络传输、文件存储
- 游戏开发:四叉树、八叉树的空间分割
- 数据库索引:B树、B+树的存储
6. 内存管理优化
| C |
|---|
| // 批量内存分配版本
typedef struct {
BiTree nodes;
int capacity;
int used;
} NodePool;
BiTree createWithPool(char A[], int i, int n, NodePool* pool) {
if (i >= n || A[i] == '#') return NULL;
// 从内存池中获取节点
if (pool->used >= pool->capacity) return NULL;
BiTree node = &pool->nodes[pool->used++];
node->data = A[i];
node->lchild = createWithPool(A, 2 * i + 1, n, pool);
node->rchild = createWithPool(A, 2 * i + 2, n, pool);
return node;
}
|
这个算法体现了树的存储结构转换思想,是数据结构中重要的基础操作,广泛应用于各种实际场景中。