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

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


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

二叉树的创建

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

相关文章
|
存储 索引
数据结构(顺序结构、链式结构、索引结构、散列结构)
数据结构(顺序结构、链式结构、索引结构、散列结构)
|
消息中间件 存储 算法
嵌入式操作系统服务机制
嵌入式操作系统服务机制
396 0
|
11月前
|
存储 算法 C++
【C++数据结构——图】图的邻接矩阵和邻接表的存储(头歌实践教学平台习题)【合集】
本任务要求编写程序实现图的邻接矩阵和邻接表的存储。需掌握带权有向图、图的邻接矩阵及邻接表的概念。邻接矩阵用于表示顶点间的连接关系,邻接表则通过链表结构存储图信息。测试输入为图的顶点数、边数及邻接矩阵,预期输出为Prim算法求解结果。通关代码提供了完整的C++实现,包括输入、构建和打印邻接矩阵与邻接表的功能。
511 10
|
11月前
|
机器学习/深度学习 存储 C++
【C++数据结构——线性表】顺序表的基本运算(头歌实践教学平台习题)【合集】
本文档介绍了线性表的基本运算任务,涵盖顺序表和链表的初始化、销毁、判定是否为空、求长度、输出、查找元素、插入和删除元素等内容。通过C++代码示例详细展示了每一步骤的具体实现方法,并提供了测试说明和通关代码。 主要内容包括: - **任务描述**:实现顺序表的基本运算。 - **相关知识**:介绍线性表的基本概念及操作,如初始化、销毁、判定是否为空表等。 - **具体操作**:详述顺序表和链表的初始化、求长度、输出、查找、插入和删除元素的方法,并附有代码示例。 - **测试说明**:提供测试输入和预期输出,确保代码正确性。 - **通关代码**:给出完整的C++代码实现,帮助完成任务。 文档
384 5
|
机器学习/深度学习 自然语言处理 语音技术
使用Python实现深度学习模型:智能产品设计与开发
【10月更文挑战第2天】 使用Python实现深度学习模型:智能产品设计与开发
246 4
|
Shell Linux 开发工具
三招教你轻松扩展 git bash 命令(上)(二)
GitBash 是 Windows 系统安装 Git 时默认集成的命令行工具,提供运行 Git 命令的集成环境.
三招教你轻松扩展 git bash 命令(上)(二)
判断闰年的两种常见方法
判断闰年的两种常见方法
558 1
|
存储
数据结构学习记录——如何建立图(邻接矩阵、邻接表-图节点的结构、创建并初始化、插入变、完整图的建立)
数据结构学习记录——如何建立图(邻接矩阵、邻接表-图节点的结构、创建并初始化、插入变、完整图的建立)
330 0
|
存储 搜索推荐 算法
【数据结构入门精讲 | 第九篇】考研408排序算法专项练习(一)
【数据结构入门精讲 | 第九篇】考研408排序算法专项练习(一)
1390 1
|
编解码
RTP传输AAC
RTP传输AAC
390 1