当前位置:   article > 正文

树和二叉树的基本知识

树和二叉树的基本知识

一、树的概念及结构

1.树的概念

树是一种 非线性 的数据结构,它是由 n n>=0 )个有限结点组成一个具有层次关系的集合。 把它叫做树是因 为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的
有一个 特殊的结点,称为根结点 ,根节点没有前驱结点。
除根节点外, 其余结点被分成 M(M>0) 个互不相交的集合 T1 T2 …… Tm ,其中每一个集合 Ti(1<= i <= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有 0 个或多个后继。
因此, 树是递归定义 的。
注意:树形结构中,子树之间不能有交集,否则就不是树形结构

2.相关概念

节点的度:一个节点含有的子树个数称为该节点的度。如上图:根节点A的度为5

叶节点/终端节点:度为0的节点称为叶节点。如上图:B、D、G、I、K、L、M、N、O均为叶节点

分支节点/非终端节点:度不为0的节点。如上图:C、E、F、H、J为分支节点

双亲节点或父节点 :若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图: A B 的父节点
孩子节点或子节点 :一个节点含有的子树的根节点称为该节点的子节点; 如上图: B A 的孩子节点
兄弟节点 :具有相同父节点的节点互称为兄弟节点; 如上图: B C 是兄弟节点
树的度 :一棵树中,最大的节点的度称为树的度; 如上图:树的度为5
节点的层次 :从根开始定义起,根为第 1 层,根的子节点为第 2 层,以此类推
树的高度或深度 :树中节点的最大层次; 如上图:树的高度为 4
节点的祖先 :从根到该节点所经分支上的所有节点;如上图: A 是所有节点的祖先
子孙 :以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是 A 的子孙
森林 m m>0 )棵互不相交的树的集合称为森林

3.树的表示

实际中树的表示方法有很多种,最常用的是链式存储结构的孩子兄弟表示法:

  1. typedef int DataType;
  2. struct Node
  3. {
  4. struct Node* _firstChild; // 指向其第一个孩子结点
  5. struct Node* _Brother; // 指向其兄弟结点
  6. DataType _data; // 结点中的数据域
  7. };

例如,上图的部分节点表示如下:

4.树的性质

①设度为i的节点个数为n_{i},则对于一颗度为m的树,其总节点个数N = n_{0}+n_{1}+...+n_{m}

②所有节点的度之和n_{1}+2n_{2}+...+mn_{m}=N-1

二、二叉树的概念

1.概念

一棵二叉树是结点的一个有限集合,该集合 :
1. 或者为空
2. 或者 由一个根节点加上两棵别称为左子树和右子树的二叉树组成
从定义可以看出,二叉树的度小于等于2,即不存在度大于2的节点。因此,任意一颗二叉树都是由以下几种情况复合而成的:
注意:二叉树≠度为2的树:二叉树的度可以为0/1/2,但度为2的树的度必须为2,即度为2的树必须存在度为2的节点,而二叉树没有此限制。

2.特殊的二叉树

满二叉树 :一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。
也就是说,如果一个二叉树的层数为K ,且结点总数是2^{k}-1,则它就是满二叉树。
完全二叉树 :完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为 K的,有n 个结点的二叉树,当且仅当其每一个结点都与深度为 K 的满二叉树中编号从 1 n 的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树

3.二叉树的性质

①若规定根节点的层数为 1 ,则一棵非空二叉树的 i 层上最多有 2^{i-1}个结点
若规定根节点的层数为 1 ,则 深度为 h 的二叉树的最大结点数是2^{h}-1
③由树的性质可知,N=n_{0}+n_{1}+n_{2}n_{1}+2n_{2}=N-1,联立上式可得n_{0}=n_{2}+1
④若规定根节点的层数为 1 ,具有 n 个结点的满二叉树的深度 h=log_{2}(n+1)

三、二叉树的存储结构

1.顺序存储

顺序存储就是使用一个数组来存储,顺序存储一般只适用于完全二叉树或近似于完全二叉树,否则会造成较大的空间浪费。现实使用中只有堆才会使用数组存储,关于堆的内容将在下篇博客【堆的实现及应用】讲解。

二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。

2.链式存储

二叉树的链式存储结构是指,用链表来表示一颗二叉树,即用链来指示元素的逻辑关系。通常链表中的每个节点由三个域组成:数据域、左指针域、右指针域。其中左右指针域分别给出该节点左孩子和右孩子所在链节点的地址。这种链式结构又叫做二叉链。

  1. typedef int BTDataType;
  2. typedef struct BinaryTreeNode
  3. {
  4. BTDataType val;
  5. struct BinaryTreeNode* left;
  6. struct BinaryTreeNode* right;
  7. }BTNode;

一颗二叉树可以分为三部分:根节点、左子树、右子树,而左右子树又都是一颗二叉树,可见二叉树的结构具有递归性,因此基于链式存储结构的二叉树算法多数也是递归的,这样代码的可读性更高、容易理解。

(1)二叉树的遍历

二叉树遍历是指按照某种特定规则依次访问二叉树的全部节点,每个节点只访问一次。

遍历是二叉树最重要的算法之一,也是其他算法的基础。

二叉树的遍历可分为四种:

①前序遍历:按照根节点、左子树、右子树的顺序遍历二叉树。如上图,遍历结果为1 2 4 6 5 3

②中序遍历:按照左子树、根节点、右子树的顺序遍历二叉树。如上图,遍历结果为4 6 2 5 1 3

③后序遍历:按照左子树、右子树、根节点的顺序遍历二叉树。如上图,遍历结果为6 4 5 2 3 1

④层序遍历:从根节点开始,自上而下、自左至右逐层访问二叉树的每个节点。如上图,遍历结果为1 2 3 4 5 6

其中,前三种遍历方式均属于递归遍历,第四种为非递归遍历。

下面以前序遍历为例演示递归遍历:

  1. void PreOrder(BTNode* t)
  2. {
  3. if (t == NULL)
  4. {
  5. printf("NULL ");
  6. return;
  7. }
  8. printf("%d ", t->val);
  9. PreOrder(t->left);
  10. PreOrder(t->right);
  11. }

层序遍历需要借助队列来实现,使用队列保存每个节点的孩子节点,通过不断地入队和出队以此来实现所有节点的访问:(注:有关队列的接口函数均与上篇博客中保持一致)

  1. void LevelOrder(BTNode* t)
  2. {
  3. Queue qu;
  4. QueueInit(&qu);
  5. QueuePush(&qu, t);
  6. while (!QueueEmpty(&qu))
  7. {
  8. BTNode* tmp = QueueFront(&qu);
  9. QueuePop(&qu);
  10. printf("%d ", tmp->val);
  11. if (tmp->left != NULL)
  12. QueuePush(&qu, tmp->left);
  13. if (tmp->right != NULL)
  14. QueuePush(&qu, tmp->right);
  15. }
  16. }

(2)二叉树的创建和销毁

已知一颗二叉树的某种递归遍历序列,如何构建出对应的二叉树?下面以前序序列为例:(注:子树为空用‘#’表示)

  1. //创建单个节点
  2. BTNode* SingleNode(BTDataType x)
  3. {
  4. BTNode* _new = (BTNode*)malloc(sizeof(BTNode));
  5. if (_new == NULL)
  6. {
  7. perror("malloc error");
  8. exit(-1);
  9. }
  10. _new->val = x;
  11. _new->left = NULL;
  12. _new->right = NULL;
  13. return _new;
  14. }
  15. //创建二叉树
  16. BTNode* CreateBT(char* a, int* pi) //pi为遍历序列a的下标,开始为0
  17. {
  18. if (a[*pi] == '#')
  19. {
  20. (*pi)++;
  21. return NULL;
  22. }
  23. BTNode* root = (BTNode*)malloc(sizeof(BTNode));
  24. if (root == NULL)
  25. {
  26. perror("malloc error");
  27. exit(-1);
  28. }
  29. root->val = a[(*pi)++]-'0'; //注意数据类型
  30. root->left = CreateBT(a, pi);
  31. root->right = CreateBT(a, pi);
  32. return root;
  33. }

二叉树的销毁:

  1. void BTDestroy(BTNode** pt)
  2. {
  3. BTNode* t = *pt;
  4. if (t == NULL)
  5. return;
  6. BTDestroy(&(t->left));
  7. BTDestroy(&(t->right));
  8. free(t);
  9. *pt = NULL;
  10. }

(3)其他算法

下面列举了一些有关二叉树遍历的其他算法,可以加深对递归遍历和链式结构的理解。当然,这些算法题在各大OJ平台都可以搜到。

①计算二叉树的节点个数:

  1. int TreeNodeSize(BTNode* t)
  2. {
  3. if (t == NULL)
  4. return 0;
  5. return TreeNodeSize(t->left) + TreeNodeSize(t->right) + 1;
  6. }

②计算二叉树叶子节点的个数:

  1. int LeafSize(BTNode* t)
  2. {
  3. if (t == NULL)
  4. return 0;
  5. if (t->left == NULL && t->right == NULL)
  6. return 1;
  7. return LeafSize(t->left) + LeafSize(t->right);
  8. }

③计算二叉树的高度:

  1. int TreeHeight(BTNode* t)
  2. {
  3. if (t == NULL)
  4. return 0;
  5. int left = TreeHeight(t->left);
  6. int right = TreeHeight(t->right);
  7. return (left > right ? left : right) + 1;
  8. }

④计算第k层的节点个数:

  1. int Size_k(BTNode* t,int k)
  2. {
  3. if (t == NULL)
  4. return 0;
  5. if (k == 1)
  6. return 1;
  7. return Size_k(t->left, k - 1) + Size_k(t->right, k - 1);
  8. }

⑤查找值为x的节点:

  1. BTNode* BTFind(BTNode* t, BTDataType x)
  2. {
  3. if (t == NULL)
  4. return NULL;
  5. if (t->val == x)
  6. return t;
  7. BTNode* t1 = BTFind(t->left, x);
  8. if (t1 != NULL)
  9. return t1;
  10. else
  11. return BTFind(t->right, x);
  12. }

⑥判断两棵树是否相同:

  1. bool isSameTree(BTNode* p, BTNode* q)
  2. {
  3. if (p == NULL && q == NULL) //在条件判别时,p==NULL等价于!p
  4. return true;
  5. if (p == NULL || q == NULL)
  6. return false;
  7. if (p->val != q->val)
  8. return false;
  9. bool left = isSameTree(p->left, q->left);
  10. bool right = isSameTree(p->right, q->right);
  11. return left && right;
  12. }

⑦翻转二叉树:

  1. BTNode* invertTree(BTNode* root)
  2. {
  3. if (root == NULL)
  4. return NULL;
  5. BTNode* _root = (BTNode*)malloc(sizeof(BTNode*));
  6. if (_root == NULL)
  7. {
  8. perror("malloc error");
  9. exit(-1);
  10. }
  11. _root->val = root->val;
  12. _root->left = invertTree(root->right);
  13. _root->right = invertTree(root->left);
  14. return _root;
  15. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/117640
推荐阅读
相关标签
  

闽ICP备14008679号