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

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


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

二叉树的创建

#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~

相关文章
|
存储 索引
数据结构(顺序结构、链式结构、索引结构、散列结构)
数据结构(顺序结构、链式结构、索引结构、散列结构)
|
存储 数据采集 数据可视化
大数据处理技术
【4月更文挑战第10天】大数据处理涵盖采集、预处理、存储、分析挖掘、展现和应用等关键步骤。采集涉及多种类型数据,预处理确保数据质量,存储管理关注规模、速度和安全,分析挖掘利用机器学习发现价值,展现和应用则通过可视化和检索实现数据价值。云计算和AI强化了大数据处理能力,整体目标是提取数据中的价值,驱动企业和社会进步。
810 4
大数据处理技术
|
10月前
|
开发框架 JavaScript 前端开发
HarmonyOS UI开发:掌握ArkUI(包括Java UI和JS UI)进行界面开发
【10月更文挑战第22天】随着科技发展,操作系统呈现多元化趋势。华为推出的HarmonyOS以其全场景、多设备特性备受关注。本文介绍HarmonyOS的UI开发框架ArkUI,探讨Java UI和JS UI两种开发方式。Java UI适合复杂界面开发,性能较高;JS UI适合快速开发简单界面,跨平台性好。掌握ArkUI可高效打造符合用户需求的界面。
472 8
|
11月前
|
机器学习/深度学习 自然语言处理 语音技术
使用Python实现深度学习模型:智能产品设计与开发
【10月更文挑战第2天】 使用Python实现深度学习模型:智能产品设计与开发
213 4
|
机器学习/深度学习 数据采集 算法
无监督学习实践:利用Sklearn进行聚类与降维分析
【7月更文第24天】在机器学习的广阔领域中,无监督学习占据着不可小觑的地位,它允许我们在没有标签数据的情况下发现数据中的隐藏结构和模式。本篇文章将深入探讨无监督学习的两大关键技术——聚类与降维分析,并通过使用Python的Scikit-learn库(sklearn)提供实践指南和代码示例,帮助读者掌握这些技术的应用。
833 1
|
存储 搜索推荐 算法
【数据结构入门精讲 | 第九篇】考研408排序算法专项练习(一)
【数据结构入门精讲 | 第九篇】考研408排序算法专项练习(一)
995 1
|
Shell Linux 开发工具
三招教你轻松扩展 git bash 命令(上)(二)
GitBash 是 Windows 系统安装 Git 时默认集成的命令行工具,提供运行 Git 命令的集成环境.
三招教你轻松扩展 git bash 命令(上)(二)
|
消息中间件 分布式计算 网络协议
服务搭建篇(六) 搭建基于Kafka + Zookeeper的集群
用来解决分布式集群中应用系统的一致性问题。Zookeeper 的设计目标是将那些复杂且容 易出错的分布式一致性服务封装起来,构成一个高效可靠的原语集,并以一系列简单易用的 接口提供给用户使用。
667 0
|
算法 C语言
KMP算法详解(理论+C语言代码实现)(上)
KMP算法详解(理论+C语言代码实现)
293 0
|
算法 C语言
九分钟带你弄懂KMP算法【C语言实现篇】
定义一个函数,传入参数为两个字符串,以及一个pos(表示从主串str的哪个位置开始搜寻子串pat)
426 0