1. 常用性质

  • 性质1:二叉树第i层,至多有🌲二叉树 - 图1个结点(🌲二叉树 - 图2)
  • 性质2:深度为k的二叉树至多有🌲二叉树 - 图3个结点(🌲二叉树 - 图4)
  • 性质3:🌲二叉树 - 图5
  • 性质4:具有n个结点的完全二叉树深度为🌲二叉树 - 图6
  • 性质5:一颗有n个结点的完全二叉树,则对任一结点i有:🌲二叉树 - 图7
    • 如果i=1,i是根节点无双亲;i>1双亲是🌲二叉树 - 图8
    • 如果2i>n,该结点为叶子结点,无左孩子;否则左孩子为2i
    • 如果2i+1>n,该结点为叶子结点,无右孩子;否则右孩子为2i+1

**

2. 存储结构

2.1 顺序存储结构

实现:按满二叉树的结点层次编号,依次存放二叉树中的数据元素。
image.png

  1. #define MAXSIZE 100
  2. //typedef int TElemType;
  3. typedef TElemType SqBiTree[MAXSIZE];
  4. SqBiTree bt;

缺点:结点间的关系蕴含在其存储位置中,浪费空间,尤其是左/右单支树。适合存满二叉树和完全二叉树。

2.2 链式存储结构

2.2.1 二叉链表存储

image.png

  1. typedef struct BiNode{
  2. TElemType data;
  3. struct BiNode *lchild,*rchild;//左右孩子指针
  4. }BiNode,*BiTree;

一般使用一个指针指向根节点,类似于链表的头指针。

练习
在n个结点的二叉链表中,有_个空指针域?

答案:n+1
分析:**n个结点,每个结点有两个指针域,所以一共有2n个指针域。除了根节点之外,其他n-1个结点每个都有一个双亲结点,也就是这n-1个结点每个占一个指针域,也就是有n-1个指针域不为空。所以问题就变成:
已知一共2n个指针域,有n-1个不为空,求空指针域
答案就是🌲二叉树 - 图11

2.2.2 三叉链表

image.png

  1. typedef struct TriTNode{
  2. TelemType data;
  3. struct TriTNode *lchild,*rchild,*parent;
  4. }TriTNode,*TriTree;

3. 遍历二叉树

常见三种遍历方法:先序遍历、中序遍历、后序遍历

little tips:**

  • 前缀表达式(波兰式),后缀表达式(逆波兰式)
  • 若二叉树各结点值不同,前中后序序列都是唯一
  • 前中或者中后可唯一确定一棵二叉树

3.1 先/后序和中序确定唯一二叉树

点击查看【bilibili】

3.2 先中后序遍历

3.2.1 递归算法

时间复杂度O(n)
空间复杂度O(n)

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. typedef char ElementType;
  4. typedef struct TNode *Position;
  5. typedef Position BinTree;
  6. struct TNode{
  7. ElementType Data;
  8. BinTree Left;
  9. BinTree Right;
  10. };
  11. BinTree CreatBinTree(); /* 实现细节忽略 */
  12. void InorderTraversal( BinTree BT );
  13. void PreorderTraversal( BinTree BT );
  14. void PostorderTraversal( BinTree BT );
  15. void LevelorderTraversal( BinTree BT );
  16. int main()
  17. {
  18. BinTree BT = CreatBinTree();
  19. printf("Inorder:"); InorderTraversal(BT); printf("\n");
  20. printf("Preorder:"); PreorderTraversal(BT); printf("\n");
  21. printf("Postorder:"); PostorderTraversal(BT); printf("\n");
  22. printf("Levelorder:"); LevelorderTraversal(BT); printf("\n");
  23. return 0;
  24. }
  25. /* 你的代码将被嵌在这里 */
  26. //中序遍历
  27. void InorderTraversal( BinTree BT ){
  28. if(BT != NULL){
  29. InorderTraversal(BT->Left);
  30. printf(" %c",BT->Data);
  31. InorderTraversal(BT->Right);
  32. }
  33. }
  34. //先序遍历
  35. void PreorderTraversal( BinTree BT ){
  36. if(BT != NULL){
  37. printf(" %c",BT->Data);
  38. PreorderTraversal(BT->Left);
  39. PreorderTraversal(BT->Right);
  40. }
  41. }
  42. //后序遍历
  43. void PostorderTraversal( BinTree BT ){
  44. if(BT != NULL){
  45. PostorderTraversal(BT->Left);
  46. PostorderTraversal(BT->Right);
  47. printf(" %c",BT->Data);
  48. }
  49. }

3.2.2 非递归算法

中序遍历的非递归算法
基本思想是利用栈将访问到的根节点存储起来,在遍历到左子树为空的时候,出栈根节点并访问,然后再以相同的方式去访问右子树。

  1. typedef struct TNode *Position;
  2. typedef Position BinTree;
  3. struct TNode{
  4. ElementType Data;
  5. BinTree Left;
  6. BinTree Right;
  7. int flag;
  8. };
  9. #include <stdio.h>
  10. #include <stdlib.h>
  11. typedef enum { false, true } bool;
  12. typedef char ElementType;
  13. typedef struct TNode *Position;
  14. typedef Position BinTree;
  15. struct TNode{
  16. ElementType Data;
  17. BinTree Left;
  18. BinTree Right;
  19. int flag;
  20. };
  21. /*------堆栈的定义-------*/
  22. typedef Position SElementType;
  23. typedef struct SNode *PtrToSNode;
  24. struct SNode {
  25. SElementType Data;
  26. PtrToSNode Next;
  27. };
  28. typedef PtrToSNode Stack;
  29. /* 裁判实现,细节不表 */
  30. Stack CreateStack();
  31. bool IsEmpty( Stack S );
  32. bool Push( Stack S, SElementType X );
  33. SElementType Pop( Stack S ); /* 删除并仅返回S的栈顶元素 */
  34. SElementType Peek( Stack S );/* 仅返回S的栈顶元素 */
  35. /*----堆栈的定义结束-----*/
  36. BinTree CreateBinTree(); /* 裁判实现,细节不表 */
  37. void InorderTraversal( BinTree BT );
  38. void PreorderTraversal( BinTree BT );
  39. void PostorderTraversal( BinTree BT );
  40. int main()
  41. {
  42. BinTree BT = CreateBinTree();
  43. printf("Inorder:"); InorderTraversal(BT); printf("\n");
  44. printf("Preorder:"); PreorderTraversal(BT); printf("\n");
  45. printf("Postorder:"); PostorderTraversal(BT); printf("\n");
  46. return 0;
  47. }
  48. /* 你的代码将被嵌在这里 */
  49. //中序遍历的非递归遍历
  50. void InorderTraversal( BinTree BT ){
  51. BinTree t;
  52. Stack s = CreateStack();
  53. t = BT;
  54. //p和s不同时为空
  55. while(p||!IsEmpty(s)){
  56. if(p){
  57. Push(s,t);
  58. t = t->Left;
  59. }else{
  60. t = Pop(s);
  61. printf(" %c",t->Data);
  62. t = t->Right;
  63. }
  64. }
  65. }

3.2.3 层次遍历

基本思想:用一个队列,先将根节点入队列,如果队列不为空的话,然后让根节点出队列,同时输出它并且让它的左结点和右结点分别入队(如果左右结点分别存在的话),重复这个过程。

  1. void LevelorderTraversal( BinTree BT ){
  2. BinTree QueBT[985];
  3. int front = 0;
  4. int tail = 0;
  5. BinTree t = BT;
  6. //将头结点入队
  7. if(BT != NULL){
  8. QueBT[++tail] = t;
  9. }
  10. //队列不为空
  11. while(front != tail){
  12. //根节点出队
  13. t = QueBT[++front];
  14. //访问根节点
  15. printf(" %c",t->Data);
  16. //该结点的左结点入队
  17. if(t->Left != NULL){
  18. QueBT[++tail] = t->Left;
  19. }
  20. //该结点的右结点入队
  21. if(t->Right != NULL){
  22. QueBT[++tail] = t->Right;
  23. }
  24. }

3.3 二叉树的建立

根据一段带空结点的先序序列构建二叉树
image.png
本质还是递归

3.4 复制二叉树

本质还是递归……
image.png

3.5 计算二叉树的深度

思路:

  • 如果是空树,则深度为0。
  • 否则,递归计算左子树的深度为m,递归计算右子树的深度为n,最后深度为m与n中最大值+1
    1. int dep(BinTree t){
    2. int m = 0;
    3. int n = 0;
    4. if(t == NULL){
    5. return 0;
    6. }else{
    7. m = dep(t->Left);
    8. n = dep(t->Right);
    9. if(m>n){
    10. return m+1;
    11. }else{
    12. return n+1;
    13. }
    14. }
    15. }

    3.6 求结点数和叶子结点数

    求结点个数
    思路: 如果是空树,0。否则,结点个数 = 左子树的结点个数 + 右子树的结点个数 + 1;
    1. int NodeCount(BinTree t){
    2. if(t == NULL){
    3. return 0;
    4. }else{
    5. return NodeCount(t->Left)+NodeCount(t->Right)+1;
    6. }
    7. }
    求叶子结点的个数
    思路: 空树为0,否则为 左子树叶子结点个数 + 右子树叶子结点个数;
    1. int LeadCount(BinTree t){
    2. if(t == NULL){
    3. return 0;
    4. }else if(t->Left == NULL && t->Right == NULL){//只有根结点
    5. return 1;
    6. }else{
    7. return LeadCount(t->Left) + LeadCount(t->Right);
    8. }
    9. }

4. 线索二叉树

问题提出:
如何寻找特定遍历序列中二叉树结点的前驱结点和后继结点?
通过遍历查找————费时间
再增设前驱后继指针域————费空间
最优解决方案: 利用二叉链表中的空指针域

首先我们回顾一下前面的性质,二叉链表有n+1个指针域

线索二叉树是

  • 如果某个结点的左孩子指针为空,让其指向其在特定序列中的前驱结点
  • 同理右孩子指向后继结点。
  • 遍历序列首尾的结点指向头指针,
  • 头指针左指针指向根节点,右指针指向尾结点

例如:
某二叉树如下
image.png
中序遍历为:CBEGDFA
线索化之后为
image.png

为了区分某个结点的lchild和rchild指向的是孩子结点还是前驱或者后继结点,二叉链表结点增加两个遍历:ltag和rtag
image.png
所以现在结点应该为:
image.png
image.png

最终演示图
image.png