树的定义及其相关概念

树是一种非线性的数据结构,用它能很好地描述有分支和层次特性的数据集合
树型结构在现实世界中广泛存在,比如组织关系图。
树型结构在计算机领域中也有广泛应用,如在编译系统中,用树表示源程序的语法结构。
在数据库系统中,树型结构是数据库层次模型的基础,也是各种索引和目录的主要组织形式。
在许多算法中,经常用树型结构描述问题的求解过程、所有解的状态和求解对策。

在树型结构中,二叉树是最常用的结构,分治个数确定,又可以为空,有良好的递归特性,特别适宜于程序设计。一般树会转换成二叉树进行处理。
image.png
image.png
image.pngimage.png

树的父亲表示法,数组

image.png
image.png
image.png

  1. // 优缺点:利用了树中除根结点外每个结点都有唯一的父结点这个性质
  2. // 很容易找到树根,但找孩子时需要遍历整个线性表
  3. struct node{
  4. int data, parent;
  5. }tree[110];

二叉树的定义及其基本性质

二叉树(Binary Tree, BT)是一种特殊的树型结构,每个结点最多有两个子节点。

5种基本形态

  • 空二叉树
  • 仅有根结点的二叉树
  • 右子树为空的二叉树
  • 左右子树均非空的二叉树
  • 左子树为空的二叉树

    性质

  • 在二叉树的第 i 层上最多有2. 简单树 - 图8个结点

  • 深度为 depth 的二叉树,至多有2. 简单树 - 图9个结点
  • 对任意一棵二叉树,如果叶子结点数为 2. 简单树 - 图10,度为 2 的结点数为2. 简单树 - 图11,一定满足2. 简单树 - 图12

证明:2. 简单树 - 图13,算两次

  • 具有 n 个结点的完全二叉树,深度为2. 简单树 - 图14。注意,根的深度,有的定义是0,有的定义是1
  • 对于一棵具有 n 个结点的完全二叉树,对于任意一个结点,编号为 i

if (i > 1) 父结点编号 i / 2
if (2 i > n) 没有左儿子 else 左儿子编号为 2 i
if (2 i + 1 > n) 没有右儿子 else 右儿子编号为 2 i + 1

Catalan数

  • 具有 n 个结点的不同形态的二叉树,有多少棵 ```cpp 一棵具有 n(n > 1) 个结点的二叉树

可以看成是, 由 一个根结点, 一棵具有 i 个结点的左子树, 一棵具有 n - i - 1个结点的右子树组成

i 的取值范围[0, n - 1]

  1. ![](https://cdn.nlark.com/yuque/__latex/05b433e6ac82dcfcdda48f7408b40c12.svg#card=math&code=%E5%85%AC%E5%BC%8F1%EF%BC%9AC_%7Bn%7D%20%3D%20%5Csum_%7Bi%3D0%7D%5E%7Bn-1%7DC_i%2AC_%7Bn-i-1%7D%EF%BC%8C%E5%85%B6%E4%B8%ADC_0%20%3D%201%2C%20C_1%20%3D%201&id=yx28a)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1559239/1630495797611-08d6f1fb-2f82-4f6e-8dcd-5fde9ec32467.png#clientId=u6ff515de-abf4-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=718&id=uf19a29ee&margin=%5Bobject%20Object%5D&name=image.png&originHeight=718&originWidth=1686&originalType=binary&ratio=1&rotation=0&showTitle=false&size=190881&status=done&style=none&taskId=ud53ceace-6324-4203-9d72-73dd4cbb5be&title=&width=1686)
  2. ![](https://cdn.nlark.com/yuque/__latex/f1162e08dfa0edfa096647a8ab0ad5be.svg#card=math&code=%E5%85%AC%E5%BC%8F2%EF%BC%9AC_n%20%3D%20%5Cfrac%7B1%7D%7Bn%2B1%7DC_%7B2n%7D%5E%7Bn%7D&id=OcFEm)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1559239/1630495706224-cb83f12c-ede9-4c12-8197-f2a0aac653c5.png#clientId=u6ff515de-abf4-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=372&id=ue53f6d5b&name=image.png&originHeight=372&originWidth=1698&originalType=binary&ratio=1&rotation=0&showTitle=false&size=119311&status=done&style=none&taskId=u5cf972ac-6037-4a36-b62d-2201297653a&title=&width=1698)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1559239/1630495717849-9f920eea-e010-499a-af8d-5a20547b77c1.png#clientId=u6ff515de-abf4-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=606&id=u6161b9a8&margin=%5Bobject%20Object%5D&name=image.png&originHeight=606&originWidth=1692&originalType=binary&ratio=1&rotation=0&showTitle=false&size=160096&status=done&style=none&taskId=uea5142f1-9ce7-4aa8-b47f-c01cacd4f34&title=&width=1692)
  3. <a name="UkfY1"></a>
  4. # 二叉树的孩子表示法,链式存储结构+顺序存储结构
  5. ```cpp
  6. // 链式存储结构
  7. struct node{
  8. int data;
  9. node *lchild, *rchild; //左儿子,右儿子
  10. };
  11. node *root;
  12. // 顺序存储结构
  13. const int N = 110;
  14. int data[N];
  15. int lchild[N], rchild[N]; // int child[N][2]; 也可以这样写
  16. int root;

https://www.cnblogs.com/tarbitrary/p/4030652.html
image.png
image.png
image.png
image.png

二叉树的遍历:前序、中序、后序遍历,递归

image.png

性质:

  • 已知先序和中序,可以确定出二叉树
  • 已知中序和后序,可以确定出二叉树
  • 已知先序和后序,不可以确定出二叉树
  1. // 链式存储结构
  2. #include <bits/stdc++.h>
  3. using namespace std;
  4. struct node{
  5. char data;
  6. node *lchild, *rchild;
  7. };
  8. node *root;
  9. // 按先序输入二叉树的结点,输入$表示空树
  10. void build(node* &p){
  11. char c;
  12. scanf("%c", &c);
  13. if (c == '$') p = NULL;
  14. else{
  15. p = new node();
  16. p->data = c;
  17. build(p->lchild); build(p->rchild);
  18. }
  19. }
  20. void pre_order(node *p){
  21. if (p == NULL) return;
  22. if (p->data != '$') cout << p->data;
  23. pre_order(p->lchild);
  24. pre_order(p->rchild);
  25. }
  26. void in_order(node *p){
  27. if (p == NULL) return;
  28. in_order(p->lchild);
  29. if (p->data != '$') cout << p->data;
  30. in_order(p->rchild);
  31. }
  32. void post_order(node *p){
  33. if (p == NULL) return;
  34. post_order(p->lchild);
  35. post_order(p->rchild);
  36. if (p->data != '$') cout << p->data;
  37. }
  38. int main(){
  39. build(root);
  40. pre_order(root);
  41. puts("");
  42. in_order(root);
  43. puts("");
  44. post_order(root);
  45. puts("");
  46. return 0;
  47. }
  48. /*
  49. 输入:AB$$C$$
  50. 输出:
  51. ABC
  52. BAC
  53. BCA
  54. */

表达式树

二叉树是表达式处理的常用工具。例如,表达式

  1. a+b*(c-d)-e/f

image.png
其中,每个非叶子结点表示一个运算符,左子树是第一个运算数对应的表达式,而右子树则是第二个运算数对应的表达式。如何给一个表达式简历表达式树呢?方法有很多,这里只介绍一种:找到“最后计算”的运算符(它是整颗表达式树的根),然后递归处理。

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N = 1010;
  4. int lch[N], rch[N], idx, root;
  5. char op[N], s[N];
  6. int build_tree(char *s, int x, int y){
  7. int c1 = -1, c2 = -1, p = 0;
  8. int u;
  9. if (y - x == 1){ // 只有一个字符了
  10. u = ++idx;
  11. lch[u] = rch[u] = -1;
  12. op[u] = s[x];
  13. return u;
  14. }
  15. for (int i = x; i < y; i++){
  16. if (s[i] == '(') p++;
  17. if (s[i] == ')') p--;
  18. if (s[i] == '+' || s[i] == '-')
  19. if (!p) c1 = i;
  20. if (s[i] == '*' || s[i] == '/')
  21. if (!p) c2 = i;
  22. }
  23. // 找不到括号外的加减,就用乘除
  24. if (c1 < 0) c1 = c2;
  25. // 整个表达式被一对括号括起来,往里缩一位
  26. if (c1 < 0) return build_tree(s, x + 1, y - 1);
  27. u = ++idx;
  28. lch[u] = build_tree(s, x, c1);
  29. rch[u] = build_tree(s, c1 + 1, y);
  30. op[u] = s[c1];
  31. return u;
  32. }
  33. void in_order(int u){
  34. if (lch[u] == -1 && rch[u] == -1){
  35. cout << op[u];
  36. return ;
  37. }
  38. in_order(lch[u]);
  39. cout << op[u];
  40. in_order(rch[u]);
  41. }
  42. void pre_order(int u){
  43. if (lch[u] == -1 && rch[u] == -1){
  44. cout << op[u];
  45. return ;
  46. }
  47. cout << op[u];
  48. pre_order(lch[u]);
  49. pre_order(rch[u]);
  50. }
  51. int main(){
  52. scanf("%s", s);
  53. root = build_tree(s, 0, strlen(s));
  54. in_order(root);
  55. puts("");
  56. pre_order(root);
  57. return 0;
  58. }
  59. /*
  60. a+b*(c-d)-e/f
  61. */

上述代码是如何寻找“最后一个运算符”的呢?代码用了一个变量p,只有当p=0时才考虑这个运算符。为什么呢?因为括号里的运算符一定不是最后计算的,应当忽略。例如,(a+b)*c中虽然有一个加号,但却是在括号里的,实际上比它优先级高的乘号才是最后计算的。由于加减和乘除号都是左结合的,最后一个运算符才是最后计算的,所以用两个变量c1和c2分别记录“最右”出现的加减号和乘除号。
再接下来的代码就不难理解了:如果括号外有加减号,它们肯定最后计算;但如果没有加减号,就需要考虑乘除号(if(c1 < 0) c1 = c2);如果全都没有,说明整个表达式外面被一对括号括起来,把它去掉后递归调用。这样,就找到了最后计算的运算符s[c1],它的左子树是区间[x, c1],右子树是区间[c1+1, y]。

例题

例题,【例3-1】找树根和孩子

  1. //fa[N]维护每个结点的父亲,如果一个节点没有fa,他就是根
  2. //son[N]维护孩子个数

例题,【例3-4】求后序遍历

  1. //给出先序和中序,求后续
  2. void dfs(int l1, int r1, int l2, int r2)
  3. //根据先序l1位置的根,去找中序中根的位置

例题,【例3-5】扩展二叉树

  1. //给出扩展二叉树的先序序列,输出中序和后序
  2. //题解给出的是指针写法【指针】

例题,二叉树遍历(flist)

  1. //给出中序序列和按层序列,求先序序列
  2. //在按层遍历序列中先遇到根,在中序序列中,找到根的位置,break出来
  3. //进行递归处理
  4. void dfs(int l1, int r1, int l2, int r2)

大纲要求

•【3】树的定义及其相关概念

•【4】树的父亲表示法

•【3】二叉树的定义及其基本性质

•【4】二叉树的孩子表示法

•【4】二叉树的遍历:前序、中序、后序遍历