当前位置:   article > 正文

数据结构——二叉树的基本应用

数据结构——二叉树的基本应用

在此之前我们已经初步了解了二叉树,在介绍堆的基本应用时,我们已经具体介绍了完全二叉树的基本应用,本章我们介绍二叉树的基本应用,这个不止指的是完全二叉树,而是指泛型的二叉树。

二叉树的基本应用,由于其左右子树层层连接的特性,大多都是由双向递归实现的,在本章我们要大量使用递归思想,希望能帮助到你

目录

二叉树的遍历

1.前序遍历(深度优先遍历DFS)

2.中序遍历

3.后序遍历

二叉树遍历的代码实现

1.实现前序遍历        

2. 实现中序遍历

3.实现后序遍历 

二叉树的基本函数接口

二叉树函数实现

1.二叉树结构体

2.通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树

3. 二叉树销毁

4.二叉树节点个数

5.二叉树叶子节点个数

6. 二叉树第k层节点个数

7.二叉树查找值为x的节点

层序遍历(广度优先遍历BFS)

判断完全二叉树

总结:


二叉树的遍历

我们在介绍二叉树基本函数之前,先来介绍一下二叉树的遍历

二叉树的基本遍历分为三种,前序遍历,中序遍历,后序遍历

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

访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。

我们下面用这棵树进行讲解

1.前序遍历(深度优先遍历DFS)

即根据 根节点->左子树->右子树的顺序进行遍历,遇到叶结点即返回

逻辑思路:按照根节点 ->左子树->右子树的顺序我们可以排序,第一个就是根节点 1 ,之后去找左子树,再按照顺序可得 节点 2 ,再找2的左子树为节点 3 ,再去找3的子树,此时发现3的左右子树都为NULL,则返回到2节点,此时2节点的左子树已经找完,按照顺序,我们该去找右子树,而2的右子树为NULL,此时2节点已经找完,返回到1,1的左子树找完,去到1的右子树4节点,取根节点4,再找左子树5以及右子树6;

完成这个顺序就完成了前序遍历

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

2.中序遍历

即根据 左子树->根节点->右子树的顺序进行遍历,遇到叶结点返回。

具体逻辑和前序遍历类似,只是访问顺序不同

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

3.后序遍历

即根据 左子树->右子树->根节点的顺序进行遍历,遇到叶结点返回

具体逻辑和前序遍历类似,只是访问顺序不同

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

下面是3种遍历的顺序示意图:

 

二叉树遍历的代码实现

1.实现前序遍历        

我们详细介绍前序遍历

后序遍历和中序遍历我们用流程图解释

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <assert.h>
  4. typedef int BTDataType;
  5. typedef struct BinaryTreeNode //定义二叉树结构体
  6. {
  7. struct BinaryTreeNode* left;
  8. struct BinaryTreeNode* right;
  9. BTDataType data;
  10. }BTNode;
  11. BTNode* BuyBTNode(BTDataType x) //创建二叉树节点函数
  12. {
  13. BTNode* node = (BTNode*)malloc(sizeof(BTNode));
  14. if (node == NULL)
  15. {
  16. printf("malloc fail\n");
  17. exit(-1);
  18. }
  19. node->data = x;
  20. node->left = node->right = NULL;
  21. return node;
  22. }
  23. BTNode* CreatBinaryTree() //手动创建一个如上图所示的二叉树
  24. {
  25. BTNode* node1 = BuyBTNode(1);
  26. BTNode* node2 = BuyBTNode(2);
  27. BTNode* node3 = BuyBTNode(3);
  28. BTNode* node4 = BuyBTNode(4);
  29. BTNode* node5 = BuyBTNode(5);
  30. BTNode* node6 = BuyBTNode(6);
  31. node1->left = node2;
  32. node1->right = node4;
  33. node2->left = node3;
  34. node4->left = node5;
  35. node4->right = node6;
  36. return node1;
  37. }
  38. void PrevOrder(BTNode* root) { //先序遍历函数
  39. if (root == NULL) {
  40. printf("NULL ");
  41. return;
  42. }
  43. printf("%d ", root->data);
  44. PrevOrder(root->left);
  45. PrevOrder(root->right);
  46. }

以下具体介绍二叉树前序遍历的函数调用,前序遍历将会调用13个栈帧

栈帧1:将根节点传入到prevOrder函数中,打印数据1,然后分别调用1左子树的prevOrder,再调用1右子树的prevOrder

栈帧2:打印2后,分别调用2左子树的prevOrder,再调用2右子树prevOrder

栈帧3:打印3后,分别调用3左子树的prevOrder,再调用3右子树prevOrder

栈帧4:由于3的左子树为NULL,那么就打印NULL,递归回上一层函数,到3右子树prevOrder,同时销毁栈帧4

栈帧5:由于3的右子树为NULL,那么就打印NULL,递归回上一层函数,到2右子树prevOrder,同时栈帧销毁5,3

栈帧6:栈帧6调用的是2的右子树,2的右子树为NULL,就打印NULL,递归回上一层函数,此时最初的根节点1的左子树全部访问完成,进而将去访问1的右子树,这里栈帧2结束,被销毁

栈帧7:打印4后,分别调用4左子树的prevOrder,再调用4右子树prevOrder

栈帧8:打印5后,分别调用5左子树的prevOrder,再调用5右子树prevOrder

栈帧9:由于5的左子树为NULL,那么就打印NULL,递归回上一层函数,到5右子树prevOrder,同时栈帧销毁9

栈帧10:栈帧10调用的是5的右子树,5的右子树为NULL,就打印NULL,递归回上一层函数,这里5节点已经完全访问完成,销毁栈帧10,8

栈帧11:此时访问完成4的左子树,去访问4的右子树,即打印6,然后分别调用6左子树的prevOrder,再调用6右子树prevOrder

栈帧12:栈帧12调用的是6的左子树,6的右子树为NULL,就打印NULL,递归回上一层函数,销毁栈帧12

栈帧13:此时访问完成6的左子树,去访问6的右子树,6的右子树为NULL,即打印NULL,至此已经将二叉树全部访问完成,依次返回结果,然后逐步将栈帧销毁。

下面是函数递归展开图能够更好的理解

2. 实现中序遍历

  1. // 二叉树中序遍历
  2. void BinaryTreeInOrder(BTNode* root)
  3. {
  4. if (root == NULL)
  5. {
  6. printf("N ");
  7. return;
  8. }
  9. BinaryTreeInOrder(root->_left);
  10. printf("%d ", root->_data);
  11. BinaryTreeInOrder(root->_right);
  12. }

 递归展开图如下:

3.实现后序遍历 

  1. void BinaryTreePostOrder(BTNode* root)
  2. {
  3. if (root == NULL)
  4. {
  5. printf("N ");
  6. return;
  7. }
  8. BinaryTreePostOrder(root->_left);
  9. BinaryTreePostOrder(root->_right);
  10. printf("%d ", root->_data);
  11. }

 递归展开图如下: 

 

二叉树的基本函数接口

下面我们实现二叉树的基本函数

先来看一下二叉树的函数接口

  1. typedef char BTDataType;
  2. typedef struct BinaryTreeNode
  3. {
  4. BTDataType _data;
  5. struct BinaryTreeNode* _left;
  6. struct BinaryTreeNode* _right;
  7. }BTNode;
  8. // 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
  9. BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi);
  10. // 二叉树销毁
  11. void BinaryTreeDestory(BTNode** root);
  12. // 二叉树节点个数
  13. int BinaryTreeSize(BTNode* root);
  14. // 二叉树叶子节点个数
  15. int BinaryTreeLeafSize(BTNode* root);
  16. // 二叉树第k层节点个数
  17. int BinaryTreeLevelKSize(BTNode* root, int k);
  18. // 二叉树查找值为x的节点
  19. BTNode* BinaryTreeFind(BTNode* root, BTDataType x);
  20. // 二叉树前序遍历
  21. void BinaryTreePrevOrder(BTNode* root);
  22. // 二叉树中序遍历
  23. void BinaryTreeInOrder(BTNode* root);
  24. // 二叉树后序遍历
  25. void BinaryTreePostOrder(BTNode* root);
  26. // 层序遍历
  27. void BinaryTreeLevelOrder(BTNode* root);
  28. // 判断二叉树是否是完全二叉树
  29. int BinaryTreeComplete(BTNode* root);

二叉树函数实现

1.二叉树结构体

我们在之前已经介绍了二叉树节点的基本结构,包含三个元素:数据,左子树,右子树

这样我们可以清楚的访问到树的全部节点

结构体构建

  1. typedef char BTDataType;
  2. typedef struct BinaryTreeNode
  3. {
  4. BTDataType _data;
  5. struct BinaryTreeNode* _left;
  6. struct BinaryTreeNode* _right;
  7. }BTNode;

2.通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树

这个函数的意思就是根据前序遍历的数组,来创建一个二叉树,我们既然知道了二叉树的前序遍历结果,同时空节点由#代替,我们就能利用递归来构建这个符合要求的二叉树、

进行遍历数组,由于这里是递归函数,那么我们就需要记住数组下标,而如果仅仅是用int类型的话,那么形参是会在递归过程中不断的销毁重建,是没法达到记住数组下标的目的的。

因此,我们传入int*,将地址传入,这样无论递归多少层,修改的变量一直是同一个变量,就能实现记录下标的目的

若数组元素为#则,返回NULL,同时下标+1

下面按照前序遍历进行递归构建二叉树

代码如下:

  1. // 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
  2. BTNode* BinaryTreeCreate(BTDataType* a, int* pi)
  3. {
  4. if (a[*pi] == '#')
  5. {
  6. (*pi)++;
  7. return NULL;
  8. }
  9. BTNode* root = (BTNode*)malloc(sizeof(BTNode));
  10. root->_data = a[(*pi)++];
  11. root->_left = BinaryTreeCreate(a, pi);
  12. root->_right = BinaryTreeCreate(a, pi);
  13. return root;
  14. }

3. 二叉树销毁

二叉树销毁的过程,也是一个递归的过程,但是我们不能用前序遍历进行递归,如果我们用前序遍历进行递归的话,先将root释放,那我们就没法找到根的左右子树了,因此我们需要用后序遍历进行销毁。

我们用递归,找到根的左子树和右子树,从叶节点开始,从叶到根依次释放掉根节点的空间即可

不要忘记递归的终止条件,当root == NULL 时 返回

这里我们需要传入结构体二级指针,因为我们的根节点是一级指针,因此想要释放,就需要传入其地址,由二级指针接收

代码如下:

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

4.二叉树节点个数

树的节点个数,也不难理解,还是利用递归,我们定义一个全局变量size,在左右子树进行递归,每次调用一次函数进行判断,若根节点不为NULL,就将size++,层层递归,最后size就是节点个数的结果

代码如下:

  1. // 二叉树节点个数
  2. int size = 0;
  3. int BinaryTreeSize(BTNode* root)
  4. {
  5. if (root == NULL)
  6. {
  7. return 0;
  8. }
  9. else
  10. {
  11. size++;
  12. }
  13. BinaryTreeSize(root->_left);
  14. BinaryTreeSize(root->_right);
  15. return size;
  16. }

5.二叉树叶子节点个数

叶节点和节点个数略有差异,总体思想都是递归,但是叶结点需要进行判断,很显然叶结点的左右子树都为空,因此我们只要进行判断一下,符合条件就返回1,利用递归将左右子树的叶节点加起来就可以了

代码如下:

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

6. 二叉树第k层节点个数

统计k层的节点个数,还是利用递归,我们创建一个全局变量sizek,假设求第k层的节点,那么从第一层开始进行递归,每一次传入k = k -1,

当k==1 时sizek++

若k >1 则说明在k层之上,我们就需要继续递归,将 k -1,找到第k层

若k<1 说明已经找到了k层的元素,超过了第k层,直接返回0就可以了

注意,记住判断根节点是否为空,若为空就返回 0 ,不然会出现越界现象

逻辑图如下:

代码实现如下:

  1. // 二叉树第k层节点个数
  2. int sizek = 0;
  3. int BinaryTreeLevelKSize(BTNode* root, int k)
  4. {
  5. if (root == NULL)
  6. return 0;
  7. if (k < 1)
  8. return 0;
  9. else if (k > 1)
  10. {
  11. BinaryTreeLevelKSize(root->_left, k - 1);
  12. BinaryTreeLevelKSize(root->_right, k - 1);
  13. }
  14. else if (k == 1)
  15. {
  16. sizek++;
  17. }
  18. return sizek;
  19. }

7.二叉树查找值为x的节点

查找对应值的节点,还是利用递归思想,由根->左子树->右子树的顺序即前序遍历的顺序进行查找,找到对应值就返回对应节点,若没有找到就返回NULL。

注意:在递归查找对应节点时,我们需要用指针进行接收结果,然后先判断左子树结果是否为NULL,若不为NULL,就直接返回对应节点,这样就不用再去遍历右子树,节省了时间和空间

代码如下:

  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. return root;
  10. BTNode* ret1 = BinaryTreeFind(root->_left, x);
  11. if (ret1)
  12. return ret1;
  13. BTNode* ret2 = BinaryTreeFind(root->_right, x);
  14. if (ret2)
  15. return ret2;
  16. return NULL;
  17. }

层序遍历(广度优先遍历BFS)

层序遍历比较特殊,我们单独分一部分来讲

这里我们需要用到队列的基本用法,如果忘记了可以去复习一下

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

我们利用队列,上一层带下一层,将数据一层一层的入队列,根据队列的先进先出的特性,我们能将数据按照层序打印出来

逻辑图如下

我们建立队列q,此时队列q存的数据类型是结构体指针,这样才能记录根节点的地址,从而进行修改,然后判断根节点是否为空,若不为空就让根节点入队列,然后建立循环,当队列为空时说明数据已经遍历完成,此时终止循环

在循环中,我们记录队首数据,然后打印数据,删除队首数据,再用递归访问根节点的左右子树,从而实现层序遍历

代码如下:

  1. void BinaryTreeLevelOrder(BTNode* root)
  2. {
  3.     Queue q;
  4.     QueueInit(&q);
  5.     if (root)
  6.     {
  7.         QueuePush(&q, root);
  8.     }
  9.     while (!QueueEmpty(&q))
  10.     {
  11.         BTNode* front = QueueFront(&q);
  12.         QueuePop(&q);
  13.         printf("%d ", front->_data);
  14.         if (front->_left)
  15.             QueuePush(&q, front->_left);
  16.         if (front->_right)
  17.             QueuePush(&q, front->_right);
  18.     }
  19. }

判断完全二叉树

判断完全二叉树,我们先来分析一下

完全二叉树:前n-1层 都是满的 第n层从左到右是连续的

我们仍然利用层序遍历的思想,不同的是,刚才我们的层序遍历不进空节点,这里我们也进入空节点 

1.进行层序遍历,空也进队列

2.遇到第一个空节点时,开始判断,后面全空就是完全二叉树,后面有非空就不是完全二叉树

 我们用逻辑图分析一下,即

下面我们根据思路来实现代码,总体思路还是层序遍历,但是不用打印数据,直接将左右子树数据入队,然后若出队数据为NULL节点,那么就进行判断,判断队列内是否全为NULL节点,若全为NULL节点,那么就是完全二叉树,若有非空数据,就不是完全二叉树。

代码实现如下:

  1. // 判断二叉树是否是完全二叉树
  2. int BinaryTreeComplete(BTNode* root)
  3. {
  4. Queue q;
  5. QueueInit(&q);
  6. if (root)
  7. {
  8. QueuePush(&q, root);
  9. }
  10. while (!QueueEmpty(&q))
  11. {
  12. BTNode* front = QueueFront(&q);
  13. QueuePop(&q);
  14. if (front == NULL)
  15. {
  16. break;
  17. }
  18. QueuePush(&q, front->_left);
  19. QueuePush(&q, front->_right);
  20. }
  21. while (!QueueEmpty(&q))
  22. {
  23. BTNode* front = QueueFront(&q);
  24. QueuePop(&q);
  25. if (front)
  26. {
  27. QueueDestroy(&q);
  28. return false;
  29. }
  30. }
  31. QueueDestroy(&q);
  32. return true;
  33. }

总结

二叉树的基本应用,还是比较复杂的,大多要使用递归实现,个别还需要用到队列的知识,因此我们需要掌握递归的原理,若不理解递归过程,可以通过画递归展开图的方式去理解,一定要将每一个函数接口给理解清楚,此类问题需要我们不断的练习和复习,才能将二叉树的知识巩固住,希望本篇文章能够帮助到你。

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

闽ICP备14008679号