深圳幻海软件技术有限公司 欢迎您!

【数据结构】二叉树 链式结构的相关问题

2023-09-05

 本篇文章来详细介绍一下二叉树链式结构经常使用的相关函数,以及相关的的OJ题。目录1.前置说明2.二叉树的遍历2.1前序、中序以及后序遍历2.2层次遍历3.节点个数相关函数实现3.1二叉树节点个数3.2二叉树叶子节点个数3.3二叉树第k层节点个数3.4在二叉树中查找值为x的节点4.二叉树基

 本篇文章来详细介绍一下二叉树链式结构经常使用的相关函数,以及相关的的OJ题。

目录

1.前置说明

2.二叉树的遍历

2.1 前序、中序以及后序遍历

2.2 层次遍历

3.节点个数相关函数实现

3.1 二叉树节点个数

3.2 二叉树叶子节点个数

3.3 二叉树第k层节点个数

3.4 在二叉树中查找值为x的节点

4.二叉树基础oj练习

5.二叉树的创建和销毁

5.1 通过前序遍历构建二叉树

5.2 销毁二叉树

5.3 判断二叉树是否为完全二叉树


1.前置说明

在学习链式二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。由于现在大家对二叉树结构掌握还不够深入,为了降低大家学习成本,此处手动快速创建一棵简单的二叉树,快速进入二叉树操作学习,等二叉树结构了解的差不多时,我们反过头再来研究二叉树真正的创建方式。

  1. typedef int BTDataType;
  2. typedef struct BinaryTreeNode
  3. {
  4. BTDataType data;
  5. struct BinaryTreeNode* left;
  6. struct BinaryTreeNode* right;
  7. }BTNode;
  8. //创造树节点
  9. BTNode* BuyNode(BTDataType x)
  10. {
  11. BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
  12. if (newnode == NULL)
  13. {
  14. perror("malloc fail");
  15. return NULL;
  16. }
  17. newnode->data = x;
  18. newnode->left = newnode->right = NULL;
  19. return newnode;
  20. }
  21. // 构建二叉树
  22. BTNode* CreatBinaryTree()
  23. {
  24. BTNode* node1 = BuyNode(1);
  25. BTNode* node2 = BuyNode(2);
  26. BTNode* node3 = BuyNode(3);
  27. BTNode* node4 = BuyNode(4);
  28. BTNode* node5 = BuyNode(5);
  29. BTNode* node6 = BuyNode(6);
  30. node1->Left = node2;
  31. node1->Right = node4;
  32. node2->Left = node3;
  33. node4->Right = node5;
  34. node4->Left = node6;
  35. return node1;
  36. }

注意:上述代码并不是创建二叉树的方式,真正创建二叉树方式后面详解重点讲解。

再看二叉树基本操作前,再回顾下二叉树的概念二叉树是:

  1. 空树
  2. 非空:根节点,根节点的左子树、根节点的右子树组成的。

创建的二叉树图解: 

从概念中可以看出,二叉树定义是递归式的,因此后序基本操作中基本都是按照该概念实现的。

2.二叉树的遍历

2.1 前序、中序以及后序遍历

学习二叉树结构,最简单的方式就是遍历。所谓二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉树中的节点进行相应的操作,并且每个节点只操作一次。访问结点所做的操作依赖于具体的应用问题。遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。
 

 按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:

  1. 前序遍历(Preorder Traversal亦称先序遍历)――访问根结点的操作发生在遍历其左右子树之前。
  2. 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
  3. 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。

由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。

  1. // 二叉树前序遍历
  2. void BinaryTreePrevOrder(BTNode* root);
  3. // 二叉树中序遍历
  4. void BinaryTreeInOrder(BTNode* root);
  5. // 二叉树后序遍历
  6. void BinaryTreePostOrder(BTNode* root);

三个函数实现起来非常相似,只是访问数据的顺序不同。

具体实现:

  1. // 二叉树前序遍历
  2. void BinaryTreePrevOrder(BTNode* root)
  3. {
  4. if (root==NULL)
  5. {
  6. printf("# ");
  7. return;
  8. }
  9. printf("%c ",root->data);
  10. BinaryTreePrevOrder(root->left);
  11. BinaryTreePrevOrder(root->right);
  12. }
  13. // 二叉树中序遍历
  14. void BinaryTreeInOrder(BTNode* root)
  15. {
  16. if (root == NULL)
  17. {
  18. printf("#");
  19. return;
  20. }
  21. BinaryTreePrevOrder(root->left);
  22. printf("%c ", root->data);
  23. BinaryTreePrevOrder(root->right);
  24. }
  25. // 二叉树后序遍历
  26. void BinaryTreePostOrder(BTNode* root)
  27. {
  28. if (root == NULL)
  29. {
  30. printf("#");
  31. return;
  32. }
  33. BinaryTreePrevOrder(root->left);
  34. BinaryTreePrevOrder(root->right);
  35. printf("%c ", root->data);
  36. }

下面主要分析前序递归遍历,中序与后序图解类似,大家可自己动手绘制。

前序遍历递归图解:

 

前序遍历结果:1 2 3 4 5 6

中序遍历结果:3 2 1 5 4 6

后序遍历结果:3 2 5 6 4 1
 

2.2 层次遍历

层序遍历:除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根节点所在层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。

 那么我们怎么实现呢?

这里需要使用队列,让根节点先入堆,再出队,出队时让左右子树入堆,空树不进队,按照这个方式可以实现二叉树的层次遍历。

具体实现:这里队列相关函数要自己实现,C++就方便多了,以后会讲。

  1. // 层序遍历
  2. void BinaryTreeLevelOrder(BTNode* root)
  3. {
  4. //创建一个队列
  5. Queue q;
  6. //初始化队列
  7. QueueInit(&q);
  8. if (root)
  9. QueuePush(&q, root);
  10. while (!QueueEmpty(&q))
  11. {
  12. BTNode* front = QueueFront(&q);
  13. QueuePop(&q);
  14. printf("%c ", front->data);
  15. if (front->left)
  16. {
  17. QueuePush(&q, front->left);
  18. }
  19. if (front->right)
  20. {
  21. QueuePush(&q, front->right);
  22. }
  23. }
  24. printf("\n");
  25. QueueDestroy(&q);
  26. }

3.节点个数相关函数实现

3.1 二叉树节点个数

=左子树的节点数+右子树的节点数+根节点数。根节点数为1。

  1. // 二叉树节点个数
  2. int BinaryTreeSize(BTNode* root)
  3. {
  4. if (root == NULL)
  5. {
  6. return 0;
  7. }
  8. return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
  9. }

3.2 二叉树叶子节点个数

=左子树的叶子节点数+右子树的叶子节点数。

  1. // 二叉树叶子节点个数
  2. int BinaryTreeLeafSize(BTNode* root)
  3. {
  4. if (root == NULL)
  5. {
  6. return 0;
  7. }
  8. if (root->right == NULL && root->left == NULL)
  9. {
  10. return 1;
  11. }
  12. return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
  13. }

3.3 二叉树第k层节点个数

=左子树的K-1层节点数+右子树的K-1层节点数。

  1. // 二叉树第k层节点个数
  2. int BinaryTreeLevelKSize(BTNode* root, int k)
  3. {
  4. assert(k > 0);
  5. if (root == NULL)
  6. {
  7. return 0;
  8. }
  9. if (k == 1)
  10. {
  11. return 1;
  12. }
  13. return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
  14. }

3.4 在二叉树中查找值为x的节点

=根节点不是,就在左子树和右子树中寻找

  1. //在二叉树中查找值为x的节点
  2. BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
  3. {
  4. if (root == NULL)
  5. {
  6. return NULL;
  7. }
  8. if (root->data == x)
  9. {
  10. return root;
  11. }
  12. BTNode* left = BinaryTreeFind(root->left, x);
  13. BTNode* right = BinaryTreeFind(root->right, x);
  14. return left == NULL ? right : left;
  15. }

4.二叉树基础oj练习

  1. 单值二叉树。OJ链接
  2. 检查两颗树是否相同。OJ链接
  3. 对称二叉树。OJ链接
  4. 二叉树的前序遍历。OJ链接
  5. 二叉树中序遍历。OJ链接
  6. 二叉树的后序遍历。OJ链接
  7. 另一颗树的子树。OJ链接

5.二叉树的创建和销毁

二叉树的构建及遍历。OJ链接

5.1 通过前序遍历构建二叉树

通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树,'#'代表空。

代码实现:

  1. //开辟树节点空间
  2. BTNode* BuyNode(char x)
  3. {
  4. BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
  5. newnode->data = x;
  6. newnode->left = newnode->right = NULL;
  7. return newnode;
  8. }
  9. //构建树
  10. BTNode* CreatTree(char* arr,int*i)
  11. {
  12. if(arr[*i] =='#')
  13. {
  14. (*i)++;
  15. return NULL;
  16. }
  17. BTNode* node = BuyNode(arr[*i]);
  18. (*i)++;
  19. node->left = CreatTree(arr,i);
  20. node->right = CreatTree(arr,i);
  21. return node;
  22. }
  23. int main()
  24. {
  25. char arr[] = "ABD##E#H##CF##G##";
  26. int i = 0;
  27. //传递下标的地址,这样就可以通过地址修改下标。
  28. BTNode* tree = CreatTree(arr, &i);
  29. return 0;
  30. }

5.2 销毁二叉树

这里是后序思想,先释放左右子树,最后释放根节点。

  1. // 二叉树销毁
  2. void BinaryTreeDestory(BTNode** root)
  3. {
  4. if (*root == NULL)
  5. {
  6. return;
  7. }
  8. BinaryTreeDestory(&((*root)->left));
  9. BinaryTreeDestory(&((*root)->right));
  10. free(*root);
  11. *root = NULL;
  12. }

5.3 判断二叉树是否为完全二叉树

这里也是通过队列进行判断,之前层次遍历空树不进队,而这里空树进队,当出队时遇到空时,停止出队,判断队列中是否有非空,如果有就不是完全二叉树

代码实现:

  1. // 判断二叉树是否是完全二叉树
  2. int BinaryTreeComplete(BTNode* root)
  3. {
  4. Queue q;
  5. QueueInit(&q);
  6. if (root)
  7. QueuePush(&q, root);
  8. while (!QueueEmpty(&q))
  9. {
  10. BTNode* front = QueueFront(&q);
  11. if (front != NULL)
  12. {
  13. QueuePop(&q);
  14. QueuePush(&q, front->left);
  15. QueuePush(&q, front->right);
  16. }
  17. else
  18. {
  19. //遇到空就跳出
  20. break;
  21. }
  22. }
  23. //检查后面节点有没有非空
  24. //有非空就不是完全二叉树
  25. while (!QueueEmpty(&q))
  26. {
  27. BTNode* front = QueueFront(&q);
  28. QueuePop(&q);
  29. if (front != NULL)
  30. return 0;//不是
  31. }
  32. return 1;//是
  33. }

本篇结束!

文章知识点与官方知识档案匹配,可进一步学习相关知识
算法技能树首页概览51117 人正在系统学习中