树的基本定义与基本二叉树的基本操作(Binary tree)
树的基本定义与基本二叉树的基本操作
by @WRuperD
一.树的基本定义
可以先浏览一下这个。
和这个
蒟蒻口才不好,这里引用一下dalao @Neon090426 的感性理解:
(下图就是一棵树)
可以这么理解:
- 树: 一个群
- 根: 群主
- 节点 \(A\): 编号为 \(A\)的群成员
- \(A\)的儿子: 被\(A\)拉进群的群成员 (例: 3是1的儿子)
- \(A\)的父亲: 把\(A\)拉进群的群成员 (例: 1是3的父亲)
- \(A\)的祖先: \(A\)、 \(A\)的父亲、 \(A\)父亲的父亲、 \(A\)父亲的父亲的父亲...
- \(A\)的后代: \(A\)、 \(A\)的儿子、 \(A\)儿子的儿子、 \(A\)儿子的儿子的儿子...
- 叶子节点: 没有儿子的节点 (例: 10号节点)
- A的深度: 从根遍历到\(A\)需要经过的点数 (例: 7的深度为2)
- 左子树:对于一个二叉树的非叶子结点,
而在所有树中,oi最常用的就是二叉树,以下将重点讲解二叉树。
二.树的存储
对于一个树的存储,有许多种方式
1.直接存
开一个大数组数组。
这里设为bt(即Binary Tree)
如图:
设树根为1, 对于每个非叶子节点,设这个节点的编号为\(i\) , 则把其左子树存在\(2*k\), 右子树存在\(2*k+1\).
这里就先不给标程了,毕竟不怎么常用,为什么后面再讲。。。
2.左右儿子法
定义一个结构体名叫node,表示一个树的节点,结构体里面定义两个变量left和right,分别表示其的左右儿子的编号。这样就能够顺着跟运用dfs遍历整个树了。
const int MAXN = 1000+10;
struct node
{
int left, right;
}; node bt[MAXN];
细心的读者可能会发现,这样实现出来的二叉树很难从叶子结点往上搜索。
所以我们可以再在结构体中记录一下他的父亲的信息。
3.总结
先咕着。。。。
三.树的遍历
想必读者读到这应该和我一样:
不着急,我们再来看看树的遍历与实现。
1.前序遍历
别考过csp初赛的应该都知道这是一种什么样的操作。
其实前序就是对二叉树的一种遍历方式。
这里引用一下百度的说法:
前序遍历首先访问根结点然后遍历左子树,最后遍历右子树。在遍历左、右子树时,仍然先访问根结点,然后遍历左子树,最后遍历右子树。
好吧,貌似并不是特别清晰。
举个栗子:
如图:
还是这颗树。
先序遍历的顺序是:
\(1 - 2 - 4 - 5 - 3 - 6 - 7\)
请读者思考一下下图的前序遍历是什么:
公布答案:
\(1 - 2 - 3 - 6 - 4 - 8 - 5 - 7 - 9\)
如何遍历呢?
运用dfs按照顺序从根节点开始遍历整棵树。
试着实现一下吧!
link
\——————华丽分割线————————/
题解:
首先上文说了,存储左右儿子:
struct node{
int left, right;
}; node a[15] = {0,0};
输入:
int n;
cin>>n;
for(int i = 1; i <= n; i++)
{
cin>>a[i].left>>a[i].right;
}
然后,dfs遍历整个树。
由于是前序遍历,遍历到哪就输出那的编号:
void dfs(int p)//p表示编号
cout<
然后,判断是不是叶子节点:是的话就return:
if(a[p].left == 0 and a[p].right == 0) return ;
再判断有没有右儿子,有就遍历过去;
if(a[p].left) dfs(a[p].left);
再判断有没有左儿子,有就遍历过去;
if(a[p].right) dfs(a[p].right);
回溯;
return ;
连起来:
#include
#include
using namespace std;
struct node{
int left, right;
}; node a[15] = {0,0};
void dfs(int p)
{
cout<>n;
for(int i = 1; i <= n; i++)
{
cin>>a[i].left>>a[i].right;
}
dfs(1);
return 0;
}
完美结束!!!