813 字
4 分钟
树-基础
树 - 基础
简述
树是一种数据结构,本质是由有限个节点按照层级关系进行组织的结构,其形状像一个倒着的树

对于这样的数据结构,有一些专业的术语需要了解(下面给出的例子均针对上图进行):
- 节点深度:一个节点x的深度代表这个节点到根节点的距离
例如,节点6的深度就是2;而对于根节点,这个深度是0 - 节点高度:与“深度”相反,高度的意思是一个节点x到最远的叶子节点的距离
例如,节点3的高度就是2 - 树深度:最末端叶子节点的节点深度
- 节点层次:从根节点开始为第一层,往下以此类推
- 兄弟节点:直接拥有共同父节点的子节点
- 度:一个节点的子节点个数
- 叶子节点:度为0的节点
- 祖先:对于一个节点x,从根节点到这个节点的路径上的所有节点均为节点x的祖先
例如,节点
6的祖先是:1,2 - 后代:对于一个节点x,这个节点下的子树下的所有节点均为节点x的后代
例如,节点
6的后代是:7,8,9
简单的实现
一个树可以通过下面的代码实现:
struct TreeNode { int val = 0; std::vector<TreeNode*> subs;
TreeNode()=default; TreeNode(int v): val(v) {}
TreeNode& addChild(int v) { subs.push_back(new TreeNode(v)); return *this; }};针对上述代码,方法 addChild 用于向这个节点下添加子节点
给出下面的示例代码:
void test_normal_tree() { auto root = TreeNode();
root.subs.push_back(new TreeNode(1)); root.subs.push_back(new TreeNode(2)); root.subs.push_back(new TreeNode(3)); root.subs.push_back(new TreeNode(4));
root.subs.at(0)->subs.push_back(new TreeNode(10)); root.subs.at(0)->subs.push_back(new TreeNode(11)); root.subs.at(0)->subs.push_back(new TreeNode(12)); root.subs.at(1)->subs.push_back(new TreeNode(13)); root.subs.at(1)->subs.push_back(new TreeNode(14)); root.subs.at(3)->subs.push_back(new TreeNode(15));}运行完上述代码之后,如果要对这个树的内容进行输出,那么应该是下面的输出:
0:{ 1:{ (10), (11), (12) }, 2:{ (13), (14) }, (3), 4:{, (15), }}那么,如何输出一个树的存储内容呢?
树的遍历
上面问题的答案是很简单的,遍历
但是,对于一个树,遍历分为两种方法:先序遍历和后序遍历,下面,我们将对这两种方法进行介绍:
先序遍历
所谓“先序”,意思是在遍历的过程中根节点的访问顺序,那么在先序遍历中,对根节点的访问顺序也就是最先的了
因此,我们有下列代码,作为上述代码给出的 TreeNode 的方法:
void preorderTraversal() { if (subs.empty()) std::cout <<"("<< val <<")"; else std::cout << val << ":{" ;
for (auto node: subs) { node->preorderTraversal(); }
if (!subs.empty()) { std::cout << "}"; }}因为是先序遍历,所以上面的代码先把当前节点的值 val 输出,之后才对自己的子节点进行 preorderTraversal 操作
后序遍历
了解了上述“先序”的概念,那么“后序”也是同理:即后访问根节点,先对子节点进行访问:
void postorderTraversal() { std::cout << "{";
for (auto node: subs) { node->postorderTraversal(); }
if (subs.empty()) std::cout << "sub: " << val << "}"; else std::cout << "(" << val << ")}";}可以看到,对于后序遍历,我们先对子节点进行 postorderTraversal 操作,之后才对自己的节点内容进行输出