【数据结构】C语言实现链式二叉树(附完整运行代码)

简介: 【数据结构】C语言实现链式二叉树(附完整运行代码)

一.了解项目功能

在本次项目中我们的目标是实现一个链式二叉树:

链式二叉树使用动态内存分配空间,可以用来存储任意数量的同类型数据.

二叉树结点(BTNode)需要包含三个要素:左孩子指针域left,数据域data,右孩子指针域right.

二叉树结点(BTNode)逻辑结构图示如下:

链式二叉树程序提供的功能有:

  1. 二叉树的先序建树.
  2. 二叉树的新节点创建.
  3. 二叉树的判空
  4. 二叉树的先序遍历
  5. 二叉树的中序遍历
  6. 二叉树的后序遍历
  7. 二叉树的层序遍历
  8. 二叉树的叶子结点数
  9. 二叉树的左孩子节点数
  10. 二叉树的右孩子节点数
  11. 二叉树的结点数
  12. 二叉树的高度
  13. 查询二叉树某层的结点个数
  14. 查询某节点是否在二叉树中
  15. 查询此树是否是完全二叉树
  16. 翻转二叉树
  17. 销毁二叉树

二.项目功能演示

要编写一个链式二叉树项目,首先要明确我们想要达到的效果是什么样,下面我将用vs2022编译器来为大家演示一下链式二叉树程序运行时的样子:

C语言实现l二叉树程序功能演示


三.逐步实现项目功能模块及其逻辑详解

通过第二部分对项目功能的介绍,我们已经对链式二叉树的功能有了大致的了解,虽然看似需要实现的功能很多,貌似一时间不知该如何下手,但我们可以分步分模块来分析这个项目的流程,最后再将各部分进行整合,所以大家不用担心,跟着我一步一步分析吧!


!!!注意,该部分的代码只是为了详细介绍某一部分的项目实现逻辑,故可能会删减一些与该部分不相关的代码以便大家理解,需要查看或拷贝完整详细代码的朋友可以移步本文第四部分。

为直观分析链式二叉树中的递归思路,在后续的功能实现部分,我会频繁使用下面这棵树画函数递归展开图,大家可以先熟悉一下这棵二叉树,我们马上就开始实现具体的功能:


1.实现链式二叉树程序菜单

菜单部分的逻辑比较简单,就是利用C语言printf函数打印出这个菜单界面即可。但要注意菜单的标序要和后续switch...case语句的分支相应,以免导致后续执行语句错乱的问题.基础问题就不过多赘述了,代码如下:

该部分功能实现代码如下:

//菜单二叉树
void BTMenu()
{
  printf("****************************************\n");
  printf("******请选择要进行的操作          ******\n");
  printf("******1.先序建树                  ******\n");
  printf("******2.查询树是否为空            ******\n");
  printf("******3.树的先序遍历              ******\n");
  printf("******4.树的中序遍历              ******\n");
  printf("******5.树的后序遍历              ******\n");
  printf("******6.树的层序遍历              ******\n");
  printf("******7.树的叶子节点数            ******\n");
  printf("******8.树的左孩子节点个数        ******\n");
  printf("******9.树的右孩子节点个数        ******\n");
  printf("******10.树的节点个数             ******\n");
  printf("******11.树的高度                 ******\n");
  printf("******12.查询树某层的节点个数     ******\n");
  printf("******13.查询结点是否在树中       ******\n");
  printf("******14.查询树是否是完全二叉树   ******\n");
  printf("******15.翻转此树                 ******\n");
  printf("******0.退出二叉树程序            ******\n");
  printf("****************************************\n");
  printf("请选择:>");
}

2.实现链式二叉树程序功能可循环使用

由于我们要实现链式二叉树的功能可以反复使用的逻辑,且至少在一开始执行一次,因此我们选择do...while的循环语句来实现这一部分的逻辑.

该部分功能实现代码如下:

int main()
{
  BTNode* root = NULL;
 
  int swi = 0;
  do         
  {
    BTMenu();
    scanf("%d", &swi);
    switch (swi)
    {
    case 0:
      TreeDestory(root); // 释放树内存
      printf("您已退出程序:>\n");
      break;
 
    case 1:
      printf("请输入树:>(以'#'表示空孩子)\n");
      char a[100];
      scanf("%s", a);
      int i = 0;
      root = CreatTree(a, &i);
      printf("已成功建树:>\n");
 
      break;
 
    case 2:
      if (BTEmpty(root) == true) //判断树空
        printf("当前树为空\n");
      else
        printf("当前树不为空\n");
 
      break;
 
    case 3:
      printf("先序遍历此树: ");    
      PreOrder(root);
      printf("\n");
 
      break;
 
    case 4:
      printf("中序遍历此树: ");
      InOrder(root);
      printf("\n");
 
      break;
 
    case 5:
      printf("后序遍历此树: ");
      PostOrder(root);
      printf("\n");
 
      break;
 
    case 6:
      printf("层序遍历此树: ");
      LevelOrder(root);
      printf("\n");
 
      break;
 
    case 7:
      printf("此树的叶子节点数有: %d\n", BinaryTreeLeafSize(root));
 
      break;
 
    case 8:
      printf("此树的左孩子节点数有: %d\n", BinaryTreeLLeafSize(root));
 
      break;
 
    case 9:
      printf("此树的右孩子节点数有: %d\n", BinaryTreeRLeafSize(root));
 
      break;
 
    case 10:
      printf("此树的节点数有:%d\n", TreeSize(root));
 
      break;
 
    case 11:
      printf("此树的高度为:%d\n", TreeHeight(root));
 
      break;
 
    case 12:
      printf("请输入你要查询的层数:>");
      int k = 0;
      scanf("%d", &k);
 
      printf("此树第%d层的节点数有:%d\n",k,TreeKLevel(root,k));
 
      break;
 
    case 13:
      printf("请输入你要查询的结点:>");
      setbuf(stdin, NULL);//之前键盘缓冲区有污染,该函数作用是清空键盘缓冲区
      BTDataType x = 0;
      scanf("%c", &x);
      //二叉树查找值为x的结点
      if (BinaryTreeFind(root, x) != NULL)
      {
        printf("结点%c在树中:>\n",x);
      }
      else
      {
        printf("结点%c不在树中:<\n",x);
      }
      
      break;
 
    case 14:
      if (TreeComplete(root))
      {
        printf("该树是完全二叉树:>\n");
      }
      else
      {
        printf("该树不是完全二叉树:<\n");
      }
 
      break;
      
    case 15:
      root = InvertTree(root);
      printf("翻转成功:>\n");
      printf("前序遍历结果为:");
      PreOrder(root);
      printf("\n");
 
      break;
 
    default:
      printf("输入错误,请重新输入\n");
 
      break;
    }
  } while (swi);
  return 0;
}

3.实现链式二叉树的新结点创建

创建链式二叉树结点的结构体应该包括:存储数据的数据域data,以及存储左孩子结点地址的指针域left,存储右孩子结点地址的指针域right.

图示如下:

因此我们创建BTNode结构体类型时应由一个数据成员类型及两个指向该结构体的结构体的指针组成.

了解了链式二叉树的结点构造后,创建新结点就和之前单链表中对新结点的处理方法相同了,

具体思路如下:

  1. 使用malloc动态开辟结点空间
  2. 对结点结构体内容进行初始化
  3. 处理完毕,返回该新结点

该部分代码实现如下:

//获取二叉树结点
BTNode* BuyNode(BTDataType x)
{
  BTNode* node = (BTNode*)malloc(sizeof(BTNode));
  if (node == NULL)
  {
    perror("malloc fail::");
    return NULL;
  }
 
  node->data = x;
  node->left = NULL;
  node->right = NULL;
 
  return node;
}

4.实现链式二叉树的先序建树

先序建树部分,我们采用的思路是:

  1. 先在主函数接收先序排列的建树字符串
  2. 再将其传入建树函数递归建树

我们按照最开始画的那颗树先前序遍历树:

我们给函数输入我们前序遍历的树得到的字符串,此时就可以画出前序建树时的函数递归展开图:

该部分实现代码如下:

//前序建树
BTNode* CreatTree(char* a, int* pi)
{
  
  if (a[(*pi)] == '#')  //不是#不++!
  {
    (*pi)++;
    return NULL;
  }
 
  BTNode* root = BuyNode(a[(*pi)]);
 
  (*pi)++;
  root->left = CreatTree(a, pi);
  root->right = CreatTree(a, pi);
 
  return root;
}
 
//主函数部分调用建树逻辑
case 1:
      printf("请输入树:>(以'#'表示空孩子)\n");
    char a[100];
    scanf("%s", a);
    int i = 0;
    root = CreatTree(a, &i);
    printf("已成功建树:>\n");
 
    break;

5.实现链式二叉树的判空

链式二叉树的判空逻辑较为简单,只需要返回树根节点的状态即可.

该部分实现代码如下:

//判断树空
bool BTEmpty(BTNode* root)
{
    return (!root);
}

6.实现链式二叉树的先序遍历

先序遍历的思路是:

  1. 先访问根节点
  2. 再递归访问左子树
  3. 再递归访问右子树

我们画一下先序遍历的函数递归展开图:

该部分代码实现如下:

//前序遍历
void PreOrder(BTNode* root)
{
  if (root == NULL)
  {
    printf("NULL ");
    return;
  }
 
  printf("%c ", root->data);
  PreOrder(root->left);
  PreOrder(root->right);
}

7.实现链式二叉树的中序遍历

中序遍历的思路是:

  1. 先递归访问左子树
  2. 再访问根节点
  3. 最后递归访问右子树

该部分递归展开图于先序遍历类似,故不作展示,代码实现如下:

//中序遍历
void InOrder(BTNode* root)
{
  if (root == NULL)
  {
    printf("NULL ");
    return;
  }
 
  InOrder(root->left);
  printf("%c ", root->data);
  InOrder(root->right);
}

8.实现链式二叉树的后序遍历

后序遍历的思路是:

  1. 先递归访问左子树
  2. 再递归访问右子树
  3. 最后访问根节点

该部分递归展开图于先序遍历类似,故不作展示,代码实现如下:

//后序遍历
void PostOrder(BTNode* root)
{
  if (root == NULL)
  {
    printf("NULL ");
    return;
  }
 
  PostOrder(root->left);
  PostOrder(root->right);
  printf("%c ", root->data);
}

9.实现链式二叉树的层序遍历

实现层序遍历的思路为(借助队列实现):

  1. 将根节点入队
  2. 如果队首结点不为空,将队首结点不为空的孩子入队
  3. 访问队首元素并将其出队

注:因为在之前的文章中我们已经实现过链队列程序了,所以在树部分我们不涉及讲解链队列的实现,而是直接使用.

层序遍历算法演示如下:

该部分实现代码如下(链队列的实现在文末的两个Queue文件中):

//层序遍历(借助队列)
void LevelOrder(BTNode* root)
{
  Que q;
  QueueInit(&q);
 
  if (root)
    QueuePush(&q, root);
 
  while (!QueueEmpty(&q))
  {
    BTNode* front = QueueFront(&q);
    QueuePop(&q);
    printf("%c ", front->data);
    if (front->left != NULL)
    {
      QueuePush(&q, front->left);
    }
    if (front->right != NULL)
    {
      QueuePush(&q, front->right);
    }
  }
 
  QueueDestroy(&q);
}

10.实现链式二叉树的叶子节点数计算

叶子结点的计算上我们采用递归分治的思想,即

根结点的叶子节点数=左子树的叶子节点数+右子树的叶子节点数.

当然,叶子结点的判断条件根存在且左右子树都为空.

函数的递归展开图如下:

该部分代码实现如下:

//叶子节点数
int BinaryTreeLeafSize(BTNode* root)
{
  if (root == NULL)
    return 0;
 
  if (root->left==NULL && root->right==NULL)//左右子树都为空就是叶子结点
  {
    return 1;
  }
  return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);//返回每颗子树的左右叶子节点数
}

11.实现链式二叉树左孩子节点数计算

左孩子结点的计算上我们同样采用递归分治的思想,即

根结点的左孩子节点数=左子树的左孩子节点数+右子树的左孩子节点数.

当然,左孩子结点的判断条件根存在且左子树不为空.

该函数的递归展开和叶子节点计算类似,这里就不赘述了,代码实现如下:

//左孩子节点数
int BinaryTreeLLeafSize(BTNode* root)
{
  if (root == NULL)
    return 0;
  if (root->left != NULL)
    return BinaryTreeLLeafSize(root->left) + BinaryTreeLLeafSize(root->right)+1;
  else
    return BinaryTreeLLeafSize(root->left) + BinaryTreeLLeafSize(root->right);
}

12.实现链式二叉树右孩子节点数计算

右孩子结点的计算上我们同样采用递归分治的思想,即

根结点的右孩子节点数=左子树的右孩子节点数+右子树的右孩子节点数.

当然,右孩子结点的判断条件根结点存在且右子树不为空.

该函数的递归展开和叶子节点计算类似,这里就不赘述了,代码实现如下:

//右孩子节点数
int BinaryTreeRLeafSize(BTNode* root)
{
  if (root == NULL)
    return 0;
  if (root->right != NULL)
    return BinaryTreeRLeafSize(root->left) + BinaryTreeRLeafSize(root->right)+1;
  else
    return BinaryTreeRLeafSize(root->left) + BinaryTreeRLeafSize(root->right);
}

13.实现链式二叉树节点数计算

树的结点的计算上我们同样采用递归分治的思想,即

根结点的节点数=左子树的节点数+右子树的节点数.

当然,结点的判断标条件根结点存在.

该函数的递归展开和叶子节点计算类似,这里就不赘述了,代码实现如下:

//树的结点个数
int TreeSize(BTNode* root)
{
  if (root == NULL)
    return 0;
  else
    return TreeSize(root->left) + TreeSize(root->right) + 1;//返回左子树个数和右子树个数加上自己
 
  //return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
  //从这条语句里体会分治的思想
}

14.实现链式二叉树高度计算

二叉树的高度计算上我们同样采用递归分治的思想,即

二叉树的高度左子树和右子树高的那个高度加上根结点自己的高度,即

二叉树的高度=左子树的高度>右子树的高度?左子树高度+1:右子树高度+1.

函数的递归展开图如下:

该部分代码实现如下:

//树高
int TreeHeight(BTNode* root)
{
  if (root == NULL)
    return 0;
 
  int leftHeight = TreeHeight(root->left);
  int rightHeight = TreeHeight(root->right);
 
  return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1 ;
  //返回左子树和右子树高的那个并加上自己那份
}

15.实现链式二叉树查询某层结点个数

某层结点的计算我们同样采用递归分治的思想,即

根结点的K层节点数=左子树的K层节点数+右子树的K层节点数.

当然,K层结点的判断条件在K层且结点存在.

该函数的递归展开和高度计算类似,这里就不赘述了,代码实现如下:

//层结点个数
int TreeKLevel(BTNode* root, int k)
{
  assert(k > 0);
 
  if (root == NULL)    //思路是,对每个根结点分别求左子树第k层的结点+右子树第k层的结点
    return 0;
 
  //如果在k层,且不为空,则就是k层的有效结点
  if (k == 1)
    return 1;
 
  return TreeKLevel(root->left, k - 1) + TreeKLevel(root->right, k - 1);
}

16.实现链式二叉树查询某节点是否存在

查询某节点是否存在我们同样采用递归分治的思想,即

树中是否存在该结点取决于左子树是否存在该节点右子树是否存在该节点,

树中是否存在该结点 = 左子树是否存在该节点 || 右子树是否存在该节点

我们采取后序遍历的思想,从叶子节点逐级上传查找结果,如果没找到,就逐级传上空结点,如果找到了,就将该节点逐级传到根结点.

只要左右子树其中有一个存在这个结点,那树中就是存在该结点的.

该部分代码实现如下:

//二叉树查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
  if (root == NULL)
    return NULL;
 
  if (root->data == x)
    return root;
  //找到了就逐级传回
 
  BTNode* lret = BinaryTreeFind(root->left, x);
  if (lret)
  {
    return lret;
  }
 
  BTNode* rret = BinaryTreeFind(root->right, x);
  if (rret)
  {
    return rret;
  }
 
  return NULL;
}

17.实现链式二叉树判断是否为完全二叉树

我们判断二叉树是否为完全二叉树的思路是利用完全二叉树的性质:

完全二叉树不为满二叉树,空节点必定连续出现在最后一层的靠右部分.

如下图所示,虚线所表示的是空结点:

因此我们利用层序遍历的思路,将所有结点的空孩子也入队,当完全二叉树遍历到第一个空结点时后面一定全为空结点,如果后面还有非空结点,那么这颗树就不是完全二叉树.

该部分代码实现如下(实现链队列部分在文末的Queue文件中):

//判断一棵树是否是完全二叉树
bool TreeComplete(BTNode* root)
{
  //完全二叉树按层序走,非空结点一定是连续的(出过的结点的空子树也被无形中带入队了,不用担心结点在后面没有入队)
  Que q;
  QueueInit(&q);
  if (root)
    QueuePush(&q, root);
 
  while (!QueueEmpty(&q))
  {
    BTNode* front = QueueFront(&q);
    QueuePop(&q);
    
    if (front==NULL)
    {
      break;
    }
    else
    {
      QueuePush(&q, front->left);
      QueuePush(&q, front->right);
    }
  }
 
  //判断是不是完全二叉树(即出队过程中剩余元素有没有非空的结点)
  while (!QueueEmpty(&q))
  {
    BTNode* front = QueueFront(&q);
    QueuePop(&q);
 
    //front不为空,就为真,就返回假
    if (front)
    {
      QueueDestroy(&q);
      return false;
    }
  }
 
  QueueDestroy(&q);
  return true;
}

18.实现翻转链式二叉树

翻转二叉树我们同样采用递归分治的思想,即

翻转二叉树=翻转左子树+翻转右子树.

我们采用后序遍历的思想,最后再翻转根节点的左右子树

该部分代码实现如下:

//翻转二叉树
BTNode* InvertTree(BTNode* root)
{
  if (root == NULL)
    return NULL;
  BTNode* tmp = InvertTree(root->right);
  root->right = InvertTree(root->left);
  root->left = tmp;
  return root;
}

19.实现链式二叉树的销毁

二叉树的销毁我们采用后序递归遍历的思想,即:先销毁左右子树,再销毁根节点

该部分代码实现如下:

//销毁树
void TreeDestory(BTNode* root)
{
  if (root == NULL)
    return;
  //递归后序销毁
  TreeDestory(root->left);
  TreeDestory(root->right);
  free(root);
}

四.项目完整代码

我们将程序运行的代码分别在三个工程文件中编辑,完整代码如下:

BTree.c文件

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
#include"Queue.h"
 
typedef char BTDataType;
 
typedef struct BinaryTreeNode
{
  BTDataType data;
  struct BinarytreeNode* left;
  struct BinarytreeNode* right;
}BTNode;
 
 
//获取二叉树结点
BTNode* BuyNode(BTDataType x)
{
  BTNode* node = (BTNode*)malloc(sizeof(BTNode));
  if (node == NULL)
  {
    perror("malloc fail::");
    return NULL;
  }
 
  node->data = x;
  node->left = NULL;
  node->right = NULL;
 
  return node;
}
 
 
//前序建树
BTNode* CreatTree(char* a, int* pi)
{
  
  if (a[(*pi)] == '#')  //不是#不++!
  {
    (*pi)++;
    return NULL;
  }
 
  BTNode* root = BuyNode(a[(*pi)]);
 
  (*pi)++;
  root->left = CreatTree(a, pi);
  root->right = CreatTree(a, pi);
 
  return root;
}
 
 
//前序遍历
void PreOrder(BTNode* root)
{
  if (root == NULL)
  {
    printf("NULL ");
    return;
  }
 
  printf("%c ", root->data);
  PreOrder(root->left);
  PreOrder(root->right);
}
 
 
//中序遍历
void InOrder(BTNode* root)
{
  if (root == NULL)
  {
    printf("NULL ");
    return;
  }
 
  InOrder(root->left);
  printf("%c ", root->data);
  InOrder(root->right);
}
 
 
//后序遍历
void PostOrder(BTNode* root)
{
  if (root == NULL)
  {
    printf("NULL ");
    return;
  }
 
  PostOrder(root->left);
  PostOrder(root->right);
  printf("%c ", root->data);
}
 
 
//树的结点个数
int TreeSize(BTNode* root)
{
  if (root == NULL)
    return 0;
  else
    return TreeSize(root->left) + TreeSize(root->right) + 1;//返回左子树个数和右子树个数加上自己
 
  //return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
  //从这条语句里体会分治的思想
}
 
//树高
int TreeHeight(BTNode* root)
{
  if (root == NULL)
    return 0;
 
  int leftHeight = TreeHeight(root->left);
  int rightHeight = TreeHeight(root->right);
 
  return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1 ;
  //返回左子树和右子树高的那个并加上自己那份
}
 
 
//层结点个数
int TreeKLevel(BTNode* root, int k)
{
  assert(k > 0);
 
  if (root == NULL)    //思路是,对每个根结点分别求左子树第k层的结点+右子树第k层的结点
    return 0;
 
  //如果在k层,且不为空,则就是k层的有效结点
  if (k == 1)
    return 1;
 
  return TreeKLevel(root->left, k - 1) + TreeKLevel(root->right, k - 1);
}
 
 
//二叉树查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
  if (root == NULL)
    return NULL;
 
  if (root->data == x)
    return root;
  //找到了就逐级传回
 
  BTNode* lret = BinaryTreeFind(root->left, x);
  if (lret)
  {
    return lret;
  }
 
  BTNode* rret = BinaryTreeFind(root->right, x);
  if (rret)
  {
    return rret;
  }
 
  return NULL;
}
 
 
//层序遍历(借助队列)
void LevelOrder(BTNode* root)
{
  Que q;
  QueueInit(&q);
 
  if (root)
    QueuePush(&q, root);
 
  while (!QueueEmpty(&q))
  {
    BTNode* front = QueueFront(&q);
    QueuePop(&q);
    printf("%c ", front->data);
    if (front->left != NULL)
    {
      QueuePush(&q, front->left);
    }
    if (front->right != NULL)
    {
      QueuePush(&q, front->right);
    }
  }
 
  QueueDestroy(&q);
}
 
//判断一棵树是否是完全二叉树
bool TreeComplete(BTNode* root)
{
  //完全二叉树按层序走,非空结点一定是连续的(出过的结点的空子树也被无形中带入队了,不用担心结点在后面没有入队)
  Que q;
  QueueInit(&q);
  if (root)
    QueuePush(&q, root);
 
  while (!QueueEmpty(&q))
  {
    BTNode* front = QueueFront(&q);
    QueuePop(&q);
    
    if (front==NULL)
    {
      break;
    }
    else
    {
      QueuePush(&q, front->left);
      QueuePush(&q, front->right);
    }
  }
 
  //判断是不是完全二叉树(即出队过程中剩余元素有没有非空的结点)
  while (!QueueEmpty(&q))
  {
    BTNode* front = QueueFront(&q);
    QueuePop(&q);
 
    //front不为空,就为真,就返回假
    if (front)
    {
      QueueDestroy(&q);
      return false;
    }
  }
 
  QueueDestroy(&q);
  return true;
}
 
 
//销毁树
void TreeDestory(BTNode* root)
{
  if (root == NULL)
    return;
  //递归后序销毁
  TreeDestory(root->left);
  TreeDestory(root->right);
  free(root);
}
 
 
//叶子节点数
int BinaryTreeLeafSize(BTNode* root)
{
  if (root == NULL)
    return 0;
 
  if (root->left==NULL && root->right==NULL)//左右子树都为空就是叶子结点
  {
    return 1;
  }
  return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);//返回每颗子树的左右叶子节点数
}
 
//左孩子节点数
int BinaryTreeLLeafSize(BTNode* root)
{
  if (root == NULL)
    return 0;
  if (root->left != NULL)
    return BinaryTreeLLeafSize(root->left) + BinaryTreeLLeafSize(root->right)+1;
  else
    return BinaryTreeLLeafSize(root->left) + BinaryTreeLLeafSize(root->right);
}
 
//右孩子节点数
int BinaryTreeRLeafSize(BTNode* root)
{
  if (root == NULL)
    return 0;
  if (root->right != NULL)
    return BinaryTreeRLeafSize(root->left) + BinaryTreeRLeafSize(root->right)+1;
  else
    return BinaryTreeRLeafSize(root->left) + BinaryTreeRLeafSize(root->right);
}
 
 
//判断树空
bool BTEmpty(BTNode* root)
{
  return (!root);
}
 
 
//翻转二叉树
BTNode* InvertTree(BTNode* root)
{
  if (root == NULL)
    return NULL;
  BTNode* tmp = InvertTree(root->right);
  root->right = InvertTree(root->left);
  root->left = tmp;
  return root;
}
 
 
//菜单二叉树
void BTMenu()
{
  printf("****************************************\n");
  printf("******请选择要进行的操作          ******\n");
  printf("******1.先序建树                  ******\n");
  printf("******2.查询树是否为空            ******\n");
  printf("******3.树的先序遍历              ******\n");
  printf("******4.树的中序遍历              ******\n");
  printf("******5.树的后序遍历              ******\n");
  printf("******6.树的层序遍历              ******\n");
  printf("******7.树的叶子节点数            ******\n");
  printf("******8.树的左孩子节点个数        ******\n");
  printf("******9.树的右孩子节点个数        ******\n");
  printf("******10.树的节点个数             ******\n");
  printf("******11.树的高度                 ******\n");
  printf("******12.查询树某层的节点个数     ******\n");
  printf("******13.查询结点是否在树中       ******\n");
  printf("******14.查询树是否是完全二叉树   ******\n");
  printf("******15.翻转此树                 ******\n");
  printf("******0.退出二叉树程序            ******\n");
  printf("****************************************\n");
  printf("请选择:>");
}
 
 
int main()
{
  BTNode* root = NULL;
 
  int swi = 0;
  do         
  {
    BTMenu();
    scanf("%d", &swi);
    switch (swi)
    {
    case 0:
      TreeDestory(root); // 释放树内存
      printf("您已退出程序:>\n");
      break;
 
    case 1:
      printf("请输入树:>(以'#'表示空孩子)\n");
      char a[100];
      scanf("%s", a);
      int i = 0;
      root = CreatTree(a, &i);
      printf("已成功建树:>\n");
 
      break;
 
    case 2:
      if (BTEmpty(root) == true) //判断树空
        printf("当前树为空\n");
      else
        printf("当前树不为空\n");
 
      break;
 
    case 3:
      printf("先序遍历此树: ");    
      PreOrder(root);
      printf("\n");
 
      break;
 
    case 4:
      printf("中序遍历此树: ");
      InOrder(root);
      printf("\n");
 
      break;
 
    case 5:
      printf("后序遍历此树: ");
      PostOrder(root);
      printf("\n");
 
      break;
 
    case 6:
      printf("层序遍历此树: ");
      LevelOrder(root);
      printf("\n");
 
      break;
 
    case 7:
      printf("此树的叶子节点数有: %d\n", BinaryTreeLeafSize(root));
 
      break;
 
    case 8:
      printf("此树的左孩子节点数有: %d\n", BinaryTreeLLeafSize(root));
 
      break;
 
    case 9:
      printf("此树的右孩子节点数有: %d\n", BinaryTreeRLeafSize(root));
 
      break;
 
    case 10:
      printf("此树的节点数有:%d\n", TreeSize(root));
 
      break;
 
    case 11:
      printf("此树的高度为:%d\n", TreeHeight(root));
 
      break;
 
    case 12:
      printf("请输入你要查询的层数:>");
      int k = 0;
      scanf("%d", &k);
 
      printf("此树第%d层的节点数有:%d\n",k,TreeKLevel(root,k));
 
      break;
 
    case 13:
      printf("请输入你要查询的结点:>");
      setbuf(stdin, NULL);//之前键盘缓冲区有污染,该函数作用是清空键盘缓冲区
      BTDataType x = 0;
      scanf("%c", &x);
      //二叉树查找值为x的结点
      if (BinaryTreeFind(root, x) != NULL)
      {
        printf("结点%c在树中:>\n",x);
      }
      else
      {
        printf("结点%c不在树中:<\n",x);
      }
      
      break;
 
    case 14:
      if (TreeComplete(root))
      {
        printf("该树是完全二叉树:>\n");
      }
      else
      {
        printf("该树不是完全二叉树:<\n");
      }
 
      break;
      
    case 15:
      root = InvertTree(root);
      printf("翻转成功:>\n");
      printf("前序遍历结果为:");
      PreOrder(root);
      printf("\n");
 
      break;
 
    default:
      printf("输入错误,请重新输入\n");
 
      break;
    }
  } while (swi);
  return 0;
}

Queue.c 文件

#include"Queue.h"
 
void QueueInit(Que* pq)
{
  assert(pq);
 
  pq->head = pq->tail = NULL;
  pq->size = 0;
 
}
void QueueDestroy(Que* pq)
{
  assert(pq);
 
  QNode* cur = pq->head;
  while (cur)
  {
    QNode* next = cur->next;
    free(cur);
    cur = next;
  }
  pq->head = pq->tail = NULL;
  pq->size = 0;
 
}
 
void QueuePush(Que* pq, QDatatype x) //入队是尾插
{
  QNode* newnode = (QNode*)malloc(sizeof(QNode));
  if (newnode == NULL)
  {
    perror("malloc fail");
    return;
  }
  newnode->data = x;
  newnode->next = NULL;
 
  if (pq->head == NULL)//头插
  {
    assert(pq->tail == NULL);//head为空,tail不为空就出事了,队头为空但是队尾不为空
    
    pq->head = pq->tail = newnode;
    
  }
  else//尾插
  {
    pq->tail->next = newnode;
    pq->tail = newnode;
  }
 
  pq->size++;
 
}
 
 
void QueuePop(Que* pq)//出队是头删
{
  assert(pq);
  assert(!QueueEmpty(pq));//assert为假会终止程序
 
  QNode* cur = pq;
 
  if (pq->head->next == NULL)
  {
    free(pq->head);
    pq->head = pq->tail = NULL;
  }
  else
  {
    QNode* next = pq->head->next;
    free(pq->head);
    pq->head = next;
  }
 
  pq->size--;
 
}
 
int QueueSize(Que* pq)
{
  assert(pq);
 
  return pq->size;
 
}
 
bool QueueEmpty(Que* pq)//判空!为空返回真!
{
  assert(pq);
 
  return pq->size==0;
}
 
QDatatype QueueFront(Que* pq)
{
  assert(pq);
  assert(!QueueEmpty(pq));
 
  return pq->head->data;
 
}
QDatatype QueueBack(Que* pq)
{
  assert(pq);
  assert(!QueueEmpty(pq));
 
  return pq->tail->data;
 
}
 
void QMenu()
{
  printf("**********************************\n");
  printf("******请选择要进行的操作    ******\n");
  printf("******1.链队列入队          ******\n");
  printf("******2.链队列出队          ******\n");
  printf("******3.取队首元素          ******\n");
  printf("******4.取队尾元素          ******\n");
  printf("******5.队列判空            ******\n");
  printf("******6.查询当前队列长      ******\n");
  printf("******7.清空队列            ******\n");
  printf("******0.退出链队列程序      ******\n");
  printf("**********************************\n");
  printf("请选择:>");
 
}

Queue.h文件

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
 
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
 
typedef struct BinaryTreeNode* QDatatype;
 
typedef struct QueueNode//一个是结点结构
{
  struct QueueNode* next;
  QDatatype data;
}QNode;
 
 
typedef struct Queue//一个是队列整体结构
{
  QNode* head;
  QNode* tail;
  int size ;
}Que;
 
void QueueInit(Que* pq);
void QueueDestroy(Que* pq);
 
void QueuePush(Que* pq,QDatatype x);
void QueuePop(Que* pq);
 
int QueueSize(Que* pq);
 
bool QueueEmpty(Que* pq);
 
QDatatype QueueFront(Que* pq);
QDatatype QueueBack(Que* pq);
 
void QMenu();

结语

希望这篇链式二叉树的实现详解能对大家有所帮助,欢迎大佬们留言或私信与我交流.

学海漫浩浩,我亦苦作舟!关注我,大家一起学习,一起进步!



相关文章
|
算法 数据处理 C语言
C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合
本文深入解析了C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合,旨在帮助读者掌握这一高效的数据处理方法。
576 1
|
6月前
|
存储 C语言
c语言——二叉树
二叉树的概念在这里就不进行过多的赘述,那么主要说一下我认为重要的部分,第一点就是二叉树里面部分概念的理解:就比如说,你对于如何构建二叉树,掌握的十分深刻,但刷题的时候对于一些题目所给的概念不清楚,导致看不明白题目,这课不好,二叉树的概念如下图所示,其实都很简单,主要是当给他的名字时,你明不明白。还有对于满二叉树与完全二叉树。
156 0
|
10月前
|
定位技术 C语言
c语言及数据结构实现简单贪吃蛇小游戏
c语言及数据结构实现简单贪吃蛇小游戏
|
11月前
|
搜索推荐 C语言
数据结构(C语言)之对归并排序的介绍与理解
归并排序是一种基于分治策略的排序算法,通过递归将数组不断分割为子数组,直到每个子数组仅剩一个元素,再逐步合并这些有序的子数组以得到最终的有序数组。递归版本中,每次分割区间为[left, mid]和[mid+1, right],确保每两个区间内数据有序后进行合并。非递归版本则通过逐步增加gap值(初始为1),先对单个元素排序,再逐步扩大到更大的区间进行合并,直至整个数组有序。归并排序的时间复杂度为O(n*logn),空间复杂度为O(n),且具有稳定性,适用于普通排序及大文件排序场景。
|
存储 算法 程序员
C 语言递归算法:以简洁代码驾驭复杂逻辑
C语言递归算法简介:通过简洁的代码实现复杂的逻辑处理,递归函数自我调用解决分层问题,高效而优雅。适用于树形结构遍历、数学计算等领域。
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
366 5
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
445 1
|
3月前
|
存储 C语言
`scanf`是C语言中用于按格式读取标准输入的函数
`scanf`是C语言中用于按格式读取标准输入的函数,通过格式字符串解析输入并存入指定变量。需注意输入格式严格匹配,并建议检查返回值以确保读取成功,提升程序健壮性。
1023 0
|
11月前
|
存储 算法 C语言
【C语言程序设计——函数】素数判定(头歌实践教学平台习题)【合集】
本内容介绍了编写一个判断素数的子函数的任务,涵盖循环控制与跳转语句、算术运算符(%)、以及素数的概念。任务要求在主函数中输入整数并输出是否为素数的信息。相关知识包括 `for` 和 `while` 循环、`break` 和 `continue` 语句、取余运算符 `%` 的使用及素数定义、分布规律和应用场景。编程要求根据提示补充代码,测试说明提供了输入输出示例,最后给出通关代码和测试结果。 任务核心:编写判断素数的子函数并在主函数中调用,涉及循环结构和条件判断。
687 23
|
5月前
|
安全 C语言
C语言中的字符、字符串及内存操作函数详细讲解
通过这些函数的正确使用,可以有效管理字符串和内存操作,它们是C语言编程中不可或缺的工具。
331 15