二叉树的创建、销毁、层序遍历与层序遍历的进阶、利用层序遍历判断二叉树是否是为完全二叉树

简介: 二叉树的创建、销毁、层序遍历与层序遍历的进阶、利用层序遍历判断二叉树是否是为完全二叉树


<你想看的我这里都有😎 >

二叉树的创建

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
typedef int BTDataType;
typedef struct BinaryTreeNode
{
  BTDataType data;
  struct BinaryTreeNode* left;
  struct BinaryTreeNode* right;
}TreeNode;
TreeNode* BuyTreeNode(int x)
{
  TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));
  assert(node);
  node->data = x;
  node->left = NULL;
  node->right = NULL;
  return node;
}
TreeNode* CreatTree()
{
  TreeNode* node1 = BuyTreeNode(1);
  TreeNode* node2 = BuyTreeNode(2);
  TreeNode* node3 = BuyTreeNode(3);
  TreeNode* node4 = BuyTreeNode(4);
  TreeNode* node5 = BuyTreeNode(5);
  TreeNode* node6 = BuyTreeNode(6);
  node1->left = node2;
  node1->right = node4;
  node2->left = node3;
  //node2->right = NULL;
  //node3->left = NULL;
  //node3->right = NULL;
  node4->left = node5;
  node4->right = node6;
  //node5->left = NULL;
  //node5->right = NULL;
  //node6->left = NULL;
  //node6->right= NULL;
  return node1;
}
int main()
{
  TreeNode* root = CreatTree();
  return 0;
}

二叉树的销毁

//二叉树的销毁
void DestoryTree(TreeNode* root)
{ 
  //当结点为空时,开始返回
  if (root == NULL)
    return;
  //销毁左子树
  DestoryTree(root->left);
  //销毁右子树
  DestoryTree(root->right);
  //释放当前结点
  free(root);
}

层序遍历

概念:层序遍历即从二叉树根节点出发,自上而下,自左至右逐层访问树的结点

注意事项:在进行二叉树的层序遍历过程中我们要使用队列的性质

关于队列的文章:队列的实现和OJ练习(c语言)  

解释:借助队列先进先出的特点,先将根结点入队,然后获取此时的队头结点并将队头结点出队最后打印队头结点打印,将当前节点的左右非空的孩子入队。然后获取此时的队头结点并将队头结点出队最后打印队头结点打印,直到队列为空为止

队列的头文件

注意事项:因为我们要在队列中存放的是二叉树的结点而非结点中的值,所以原来队列实现中的typedef int QDataType需要改变为typedef struct BinaryTreeNode* QDataType,必须要写为struct BinaryTreeNode*而不是TreeNode*,这是因为typedef是预处理命令我们将二叉树的结构体重命名命名为TreeNode的编译器在执行typedef TreeNode* QDataType时检测不到(后面讲述队头的时候会用到)

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
//链式结构:表示队列
typedef struct BinaryTreeNode* QDataType;
typedef struct QueueNode
{
  QDataType val;
  struct QueueNode* next;
}QNode;
//队列的结构(使用结构体避免了二级指针的使用)
typedef struct Queue
{
  QNode* phead;
  QNode* ptail;
  int size;
}Queue;
//初始化队列
void QueueInit(Queue* pq);
//队尾入队列
void QueueDestroy(Queue* pq);
//队头出队列
void QueuePush(Queue* pq, QDataType x);
//获取队头元素
void QueuePop(Queue* pq);
//获取队尾元素
QDataType QueueFront(Queue* pq);
//获取队列中有效元素个数
QDataType QueueBack(Queue* pq);
//检测队列是否为空
bool QueueEmpty(Queue* pq);
//销毁队列
int QueueSize(Queue* pq);

队列的函数实现

#include"Queue.h"
//初始化队列
void QueueInit(Queue* pq)
{
  assert(pq);
  pq->phead = pq->ptail = NULL;
  pq->size = 0;
}
//销毁队列
void QueueDestroy(Queue* pq)
{
  assert(pq);
  QNode* cur = pq->phead;
  while (cur)
  {
    QNode* next = cur->next;
    free(cur);
    cur = next;
  }
  pq->phead = pq->ptail = NULL;
  pq->size = 0;
}
//队尾入队列
void QueuePush(Queue* pq, QDataType x)
{
  assert(pq);
  QNode* newnode = (QNode*)malloc(sizeof(QNode));
  if (newnode == NULL)
  {
    perror("malloc fail");
    return;
  }
  newnode->val = x;
  newnode->next = NULL;
  if (pq->ptail == NULL)
  {
    pq->ptail = pq->phead = newnode;
  }
  else
  {
    pq->ptail->next = newnode;
    pq->ptail = newnode;
  }
  pq->size++;
}
// 对头出队列
void QueuePop(Queue* pq)
{
  assert(pq);
  // 
  assert(pq->phead);
  QNode* del = pq->phead;
  pq->phead = pq->phead->next;
  free(del);
  del = NULL;
  if (pq->phead == NULL)
    pq->ptail = NULL;
  pq->size--;
}
//获取队头元素
QDataType QueueFront(Queue* pq)
{
  assert(pq);
  // 
  assert(pq->phead);
  return pq->phead->val;
}
//获取队尾元素
QDataType QueueBack(Queue* pq)
{
  assert(pq);
  // 
  assert(pq->ptail);
  return pq->ptail->val;
}
//检测队列是否为空
bool QueueEmpty(Queue* pq)
{
  assert(pq);
  return pq->phead == NULL;
}
//获取队列中有效元素个数
int QueueSize(Queue* pq)
{
  assert(pq);
  return pq->size;
}

层序遍历的实现

//二叉树的层序遍历
void LevelOrder(TreeNode* root)
{
  //创建队列
  Queue q;
  //初始化队列
  QueueInit(&q);
  //如果当前结点不为空,就将当前结点入队
  if (root)
    QueuePush(&q, root);
  //只要队列不为空,就打印入队
  while (!QueueEmpty(&q))
  {
    //获取队头结点
    TreeNode* front = QueueFront(&q);
    //将删除队头结点
    QueuePop(&q);
    //打印队头结点的值
    printf("%d ", front->data);
    //如果当前结点的左孩子不为空,则将左孩子结点入队
    if (front->left)
      QueuePush(&q, front->left);
    //如果当前结点的右孩子不为空,则将右孩子结点入队
    if (front->right)
      QueuePush(&q, front->right); 
  }
  printf("\n");
  //最后销毁队列
  QueueDestroy(&q);
}

画图解释部分过程:

由于是先让当前结点的左孩子入队,然后再让当前结点的右孩子入队,当结点2作为队头的时候,它的左孩子和右孩子肯定会比结点4的左孩子和右孩子入队,当结点2出队,结点4变为队头时,结点4的左右孩子才会入队,这样刚好就可以满足我们层序遍历的需求了

关于"QueuePop(&q)后front为什么还能使用"的解释:front是TreeNode*类型,QueuePop释放掉的是QNode*类型的del,删除后者对前者无影响,此外QueueFront获取队头元素的类型为:BinaryTreeNode*,虽然队列返回的是pq->phead->val,但是val里面存放的其实是结点的地址,整个过程就相当于front把val中的内容拷贝了一份,然后删除了val而已

层序遍历的进阶

//二叉树的层序遍历
void LevelOrder(TreeNode* root)
{
  //创建队列
  Queue q;
  //初始化队列
  QueueInit(&q);
  //如果当前结点不为空,就将当前结点入队
  if (root)
    QueuePush(&q, root);
    //初始化levelsize
    int levelsize = 1;
  //只要队列不为空,就打印入队
  while (!QueueEmpty(&q))
  {
        while(levelsize--)
        {
        //获取队头结点
        TreeNode* front = QueueFront(&q);
        //将删除队头结点
        QueuePop(&q);
        //打印队头结点的值
        printf("%d ", front->data);
        //如果当前结点的左孩子不为空,则将左孩子结点入队
        if (front->left)
          QueuePush(&q, front->left);
        //如果当前结点的右孩子不为空,则将右孩子结点入队
        if (front->right)
          QueuePush(&q, front->right); 
        }
        //获取此时队列中的元素个数
        levelsize = QueueSize(&q);
  }
  printf("\n");
  //最后销毁队列
  QueueDestroy(&q);
}

       我们引入了一个变量 levelsize 来记录每一层的节点数量。这个变量在内部循环中被初始化为 1,并且每次循环结束后更新为当前队列的大小(即下一层节点的数量)。这样做的目的是确保内部循环只处理当前层级上已经入队但尚未处理完毕的节点。

结论:前者和后者的代码效果相同,但从理解代码逻辑和算法执行过程的角度来看,使用 levelsize 变量可以提供更好的可读性和可理解性。

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

// 判断二叉树是否是完全二叉树
bool TreeComplete(TreeNode* root)
{
  Queue q;
  QueueInit(&q);
  if (root)
    QueuePush(&q, root);
  int levelSize = 1;
  while (!QueueEmpty(&q))
  {
    TreeNode* front = QueueFront(&q);
    QueuePop(&q);
    if (front == NULL)
      break;
    QueuePush(&q, front->left);
    QueuePush(&q, front->right);
  }
  // 前面遇到空以后,后面还有非空就不是完全二叉树
  while (!QueueEmpty(&q))
  {
    TreeNode* front = QueueFront(&q);
    QueuePop(&q);
    if (front)
    {
      QueueDestroy(&q);
      return false;
    }
  }
  QueueDestroy(&q);
  return true;
}

解释:不再解释

~over~

相关文章
|
7月前
|
人工智能 Java 测试技术
二叉树通过前序中序来构建二叉树(炒鸡详细到每一步)
二叉树通过前序中序来构建二叉树(炒鸡详细到每一步)
|
7月前
二叉树层序遍历及判断完全二叉树
二叉树层序遍历及判断完全二叉树
108 2
|
1月前
|
机器学习/深度学习 C++
初阶数据结构之---二叉树链式结构(二叉树的构建,二叉树的前序,中序,后序和层序遍历,计算二叉树结点个数,第k层结点个数,叶子结点个数,判断是否为完全二叉树)
初阶数据结构之---二叉树链式结构(二叉树的构建,二叉树的前序,中序,后序和层序遍历,计算二叉树结点个数,第k层结点个数,叶子结点个数,判断是否为完全二叉树)
|
6月前
【霍罗维兹数据结构】二叉树前中后序遍历 | 层序遍历 | 复制二叉树 | 判断两个二叉树全等 | 可满足性问题
【霍罗维兹数据结构】二叉树前中后序遍历 | 层序遍历 | 复制二叉树 | 判断两个二叉树全等 | 可满足性问题
40 0
|
6月前
|
存储 算法 C++
【二叉树】利用前序和中序遍历结果生成二叉树并输出其后序和层序遍历结果
【二叉树】利用前序和中序遍历结果生成二叉树并输出其后序和层序遍历结果
60 0
|
存储 算法 图形学
LeetCode:二叉树的前、中、后序遍历——如何创建一棵【二叉树】
二叉树是一种树形数据结构,其每个节点最多只有两个子节点。通常将节点分为三种类型:根节点、内部节点和叶子节点。其中,根节点是二叉树的唯一访问起点,内部节点具有一个父节点和两个子节点,而叶子节点没有子节点。二叉树的底层数据结构可以使用链表或数组来实现。
二叉树的三种非递归遍历方式
二叉树的三种非递归遍历方式
|
算法 C语言
【递归调用在二叉树中的应用】前序遍历、中序遍历、后序遍历、求二叉树叶子结点及复制二叉树的C语言实现
【递归调用在二叉树中的应用】前序遍历、中序遍历、后序遍历、求二叉树叶子结点及复制二叉树的C语言实现
124 0
【递归调用在二叉树中的应用】前序遍历、中序遍历、后序遍历、求二叉树叶子结点及复制二叉树的C语言实现
|
存储 C++
二叉树的四种遍历方式(前序遍历,中序遍历,后序遍历,层序遍历)C++语言
二叉树的四种遍历方式(前序遍历,中序遍历,后序遍历,层序遍历)C++语言
202 0
|
存储 C++
井号法(#)创建二叉树(利用前序遍历来建树)C++实现
井号法(#)创建二叉树(利用前序遍历来建树)C++实现
119 0
井号法(#)创建二叉树(利用前序遍历来建树)C++实现