区别
1. 内存分配方式
- 使用对象(值类型):每个节点的左右子树是直接嵌入在该节点对象内部的。也就是说,当创建一个节点对象时,其左右子树所需的内存空间会随着节点对象的创建而一并分配,它们在内存中是连续存储的。
- 使用指针:节点结构体中存储的是左右子树节点的地址(指针)。左右子树节点的内存需要单独进行动态分配(例如使用
new
操作符),这些节点可以在内存的不同位置,它们通过指针相互关联。
2. 表示空节点的方式
- 使用对象(值类型):较难清晰地表示空节点。因为值类型成员必须有一个实际的对象实例,通常需要额外的标记或特定的值来表示该子树为空,这会增加代码的复杂性和理解难度。
- 使用指针:可以很方便地用
nullptr
来表示某个子树为空,这是一种直观且通用的表示方式。
3. 节点关系的灵活性
- 使用对象(值类型):节点之间的关系在创建时就基本固定,很难动态地改变节点的子树结构。因为子树是节点对象的一部分,若要改变子树,可能需要重新创建整个节点对象。
- 使用指针:可以非常灵活地改变节点之间的关系。只需要修改指针的值,就可以轻松地添加、删除子节点,或者改变子树的指向。
优缺点
使用对象(值类型)表示左右子树
优点
- 内存管理简单:无需手动管理子树节点的内存分配和释放,因为它们的生命周期与父节点一致,由系统自动处理,减少了因内存管理不当导致的错误(如内存泄漏、悬空指针)。
- 访问快速:由于子树节点与父节点在内存中连续存储,访问子树节点的成员时,不需要额外的指针解引用操作,直接通过成员访问运算符
.
即可,访问速度较快。
缺点
- 灵活性差:难以动态地调整树的结构,如添加或删除节点。如果要改变子树,往往需要重新创建包含新子树的节点对象。
- 内存浪费:即使某个子树为空,也会为其分配固定大小的内存空间,造成内存的浪费。尤其是在树结构较为稀疏的情况下,这种浪费会更加明显。
使用指针表示左右子树
优点
- 灵活性高:能够方便地动态创建、删除节点,调整树的结构。可以在运行时根据需要灵活地改变节点之间的父子关系,适应不同的应用场景。
- 内存利用高效:只有在实际需要时才为子树节点分配内存,避免了不必要的内存浪费。对于大规模的树结构,这种方式可以显著节省内存。
缺点
- 内存管理复杂:需要手动进行内存的分配(使用
new
)和释放(使用delete
),如果处理不当,容易出现内存泄漏、悬空指针等问题,增加了程序的出错风险。 - 访问开销:访问子树节点的成员时,需要先进行指针解引用操作(使用
->
),这会带来一定的性能开销,尤其是在频繁访问节点的情况下。
下面是分别使用值类型和指针表示左右子树的简单示例代码,帮助你进一步理解:
#include <iostream>// 使用对象(值类型)表示左右子树
struct TreeNodeValueType {int val;TreeNodeValueType left;TreeNodeValueType right;TreeNodeValueType(int x) : val(x), left(0), right(0) {}
};// 使用指针表示左右子树
struct TreeNodePointerType {int val;TreeNodePointerType* left;TreeNodePointerType* right;TreeNodePointerType(int x) : val(x), left(nullptr), right(nullptr) {}
};int main() {// 使用值类型创建节点会有诸多不便,这里仅示意// TreeNodeValueType node1(1);// 使用指针创建节点TreeNodePointerType* node2 = new TreeNodePointerType(2);node2->left = new TreeNodePointerType(3);node2->right = new TreeNodePointerType(4);// 释放内存delete node2->left;delete node2->right;delete node2;return 0;
}
在实际应用中,使用指针表示左右子树更为常见,因为它能更好地满足树结构动态变化的需求,尽管需要更谨慎地处理内存管理。