【数据结构】二叉树<遍历>

简介: 二叉树的遍历就是按照某种特定的规则,依次对二叉树中的结点进行相应的操作,并且每个节点只操作一次。

二叉树遍历】|-前序-中序-后序-层序-|


<二叉树的遍历>


在学习二叉树遍历之前我们先了解下二叉树的概念。


二叉树是:


1.空树

2.非空:根节点,根节点的左子树,根节点的右子树构造。


f047bb53c3e54cd9b9b6ec41e1430303.png


学习二叉树结构,最简单的方式就是遍历了。


二叉树的遍历就是按照某种特定的规则,依次对二叉树中的结点进行相应的操作,并且每个节点只操作一次。


访问结点所做的操作依赖于具体的应用问题。


二叉树的遍历分为


1.<前序遍历>[Preorder Traversal]

访问根节点的操作发生在遍历左右子树之前。


也就是对于一个节点,它要求先访问这个节点的内容,然后再去遍历左子树,当左子树遍历完后,再遍历右子树。


2.<中序遍历>[Inorder Traversal]

访问根节点的操作发生在遍历其左右子树之中


也就是对于一个节点,它要求先遍历左子树,当左子树都遍历完,再回来访问节点里的内容,然后再遍历右子树。


3.<后续遍历>[Postorder Traversal]

访问根节点的操作发生在遍历其左右子树之后

也就是对于一个节点,它要求先遍历完左子树,再遍历完右子树,最后回来的时候再访问节点的内容。


4.<层序遍历>


层序遍历,顾名思义,一层一层的遍历即可。


从第一层开始遍历,遍历完第一层再遍历下一层。


我们为了好验证二叉树的遍历操作,手动创造一个二叉树,也就是下图


533cbe9fe0a940d582ba8ed04c65a210.png


这样,用代码来实现就是这样:


#include <stdio.h>
#include <stdlib.h>
typedef int BTDataType;
typedef struct BinaryTreeNode
{
  BTDataType data;
  struct BinaryTreeNode* left;
  struct BinaryTreeNode* right;
}BTNode;
BTNode* BuyNode(BTDataType x)
{
  BTNode* ret =(BTNode*)malloc(sizeof(BTNode));
  ret->data = x;
  ret->left = NULL;
  ret->right = NULL;
  return ret;
}
BTNode* CreatBinaryTree()
{
  BTNode* node1 = BuyNode(1);
  BTNode* node2 = BuyNode(2);
  BTNode* node3 = BuyNode(3);
  BTNode* node4 = BuyNode(4);
  BTNode* node5 = BuyNode(5);
  BTNode* node6 = BuyNode(6);
  node1->left = node2;
  node1->right = node4;
  node2->left = node3;
  node4->left = node5;
  node4->right = node6;
  return node1;
}


1.前序遍历【递归】


我们为了真正展现前序遍历在二叉树中是如何实现的,将空节点也打印出来。这样就可以清晰的看出来遍历的过程。


// 二叉树前序遍历-<根节点-左子树-右子树>-
void PreOrder(BTNode* root)
{
  if (root == NULL)//如果遇到空节点就返回
  {
    printf("NULL ");
    return;
  }
  printf("%d ", root->data);//先访问根节点内容,打印完节点内容后再进入左子树。
  PreOrder(root->left);//进入左子树
  PreOrder(root->right);//进入右子树
}

400516905f35491c90fb21ed5ec7b401.png


dc3dc68e54284d788620da825d41e03e.png


根据结果你能想明白怎么遍历的吗?


递归展开图:


05c62511bfdf418f93949be0c58e3ef8.png


f95496f9a85a4e0da1590691e34f941b.png


2.中序遍历【递归】


// 二叉树中序遍历
void InOrder(BTNode* root)
{
  if (root == NULL)
  {
    printf("NULL ");
    return;
  }
  InOrder(root->left);//先遍历左子树
  printf("%d ", root->data);//遍历完左子树后再访问节点内容
  InOrder(root->right);//访问完节点内容后再遍历右子树
}


3f88570f91b54272a1663a3ce946647b.png


c271317dd6f347eba30ccabbc752fa8f.png


递归展开图


76ed5a2afdf34b678220ea85f76e624c.png


3.后序遍历【递归】


void PostOrder(BTNode* root)
{
  if (root == NULL)
  {
    printf("NULL ");
    return;
  }
  PostOrder(root->left);
  PostOrder(root->right);
  printf("%d ", root->data);
}

a65be318b4a24c63a120e7b3a20c8ba7.png


46b75499d1f441c3aa0b485e61b49442.png


cf6327f536c342d7803d586ba962f7b8.png


而后序遍历这种特点很适合用在二叉树的销毁上去。


因为相比较前序遍历,如果先销毁了节点,那它的左右子树就无法找到了。


但后续遍历不一样,后序遍历是先遍历左右子树,最后再访问节点。


所以我们只要使用后序遍历,先销毁左右子树,再销毁节点就可以了。


比如:二叉树的销毁


void BTreeDestroy(BTNode* root)
{
  if (root == NULL)
  {
    return;
  }
  BTreeDestroy(root->left);//先销毁左子树
  BTreeDestroy(root->right);//再销毁右子树
  free(root);//最后再销毁节点
  root = NULL;
}


4.层序遍历【非递归】


上面三个都是属于递归形式的遍历,层序遍历是非递归的。


怎么进行层序遍历呢?


这个就需要用到队列来解决了。


思想:


出上一层,带入下一层。


一开始让根节点入队列,那队列中就有元素存在,不是空队列了。


然后接下来就是不断的出队列中的根节点,每一次出队列中的根节点时,都要将该根节点的孩子插入到队列的后面去。 也就是出根节点,带入它们的孩子进来。直到队列中没有数据为止。


aa0ab335a04c4e439926041ec5d348ff.png


不过注意的是,队列中不是真正节点,而是指向节点的指针,如果将节点插入进去,那怎么找到它们的孩子呢?


所以我们插入进入的是指向节点的指针。


typedef struct BTreeNode* QData;//注意这里将队列中元素的类型改成指向节点的指针
typedef struct QNode
{
  struct QNode* next;
  QData data;//队列的元素是指向节点的指针
}QNode;
//因为队列的数据结构操作需要找尾,这就需要传多个参数了,很麻烦,所以我们再分装个结构体将多个数据变成一个
typedef struct Queue
{
  QNode* head;
  QNode* tail;
  int size;
}Queue;
void QueueInit(Queue *pq);//初始化队列
void QueueDestroy(Queue *pq);//销毁队列
void QueuePush(Queue*pq ,QData x);//入队,从队尾插入一个数据,尾删
void QueuePop(Queue *pq);//出队,从队头删除数据,头删
bool QueueEmpty(Queue *pq);//判断队列是否为空
int QueueSize(Queue*pq);//获得队列有效数据个数大小
QData QueueFront(Queue*pq);//获取队头数据
QData QueueBack(Queue*pq);//获取队尾数据


#include "queue.h"
void QueueInit(Queue* pq)//初始化队列
{
  assert(pq);
  pq->head = pq->tail = NULL;
  pq->size = 0;
}
void QueueDestroy(Queue* 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(Queue* pq, QData x)//入队,从队尾插入一个数据,尾删
{
  assert(pq);
/*  QNode* cur = pq->head;
  */QNode* newnode = (QNode*)malloc(sizeof(QNode));
  if (newnode == NULL)
  {
    perror("malloc");
  }
  newnode->data=x;
  newnode->next = NULL;
  if (pq->head == NULL)
  {
    //赋值
    pq->head = pq->tail = newnode;
  }
  else
  {
    pq->tail->next = newnode;
    //更新tail的位置
    pq->tail = newnode;
  }
  pq->size++;
}
void QueuePop(Queue* pq)//出队,从队头删除数据,头删
{
  assert(pq);
  //头删之前需要判断链队列是否为空
  assert(pq->head!=NULL);
    QNode* next = pq->head->next;
    free(pq->head);
    pq->head = next;
  if (pq->head==NULL)//只管头删,最后再处理。
  {
    pq->tail=NULL;
  }
  pq->size--;
}
bool QueueEmpty(Queue* pq)//判断队列是否为空----主要size的作用
{
  assert(pq);
  return pq->size == 0;
  //return pq->head=pq->tailk=NULL;
}
int QueueSize(Queue* pq)//获得队列有效数据个数大小
{
  assert(pq);
  return pq->size;
}
QData QueueFront(Queue* pq)//获取队头数据
{
  assert(pq);
  assert(!QueueEmpty(pq));
  return pq->head->data;
}
QData QueueBack(Queue* pq)//获取队尾数据
{
  assert(pq);
  assert(!QueueEmpty(pq));
  return pq->tail->data;
}


以上是创建一个队列,接下来就是进行二叉树的层序遍历了。


void LevOlder(BTNode* root)//层序遍历--
{
  Queue q;//定义一个队列
  QueueInit(&q);//初始化队列
  //首先将根 指针插入到队列里去
  if (root)
  {
    QueuePush(&q, root);
  }
  //再出上一层带入下一层
  while (!QueueEmpty(&q))
  {
    BTNode* 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);
}

15cc315a95d845fca75e804f146192aa.png


4.1判断是否是完全二叉树


如何判断一个二叉树树是否是完全二叉树呢?


首先我们需要了解什么是完全二叉树


【完全二叉树】


1.前n-1层都是满的二叉树。

2.最后一层从左到右是连续的。


特点:


1.非空节点是连续的。

2.空节点也是连续的。

3.至多有一个度为1的节点。


我们根据完全二叉树非空节点都是连续的这一特性,来作下面的思路:


如果对完全二叉树进行层序遍历,那么第一次出现空节点的地方就是最后一个节点的后面。 而后面就不能再出现非空节点了,后面应该都是空。


所以我们可以做出这样判断:层序遍历二叉树,如果第一次出现空之后,再出现非空节点的一定不是完全二叉树,如果后面只有空则是完全二叉树。


不过要注意的是,这里的层序遍历与原来的层序遍历不一样,原来的层序遍历,只会将根节点插入到队列中去,不会将空节点插入到队中去,而现在需要将空节点也插入到队列中去,如果出队列中的元素,出出队列的是一个空节点,那么我们就可以进行判断,是否后面还会出现非空节点呢?


//判断是否是完全二叉树,利用完全二叉树性质--非空结点是连续的,一旦出现空,后面就不应该再出现空结点。所以利用层序遍历,当第一次出现
//空时,就可以进行判断后面是否会出现非空结点。这里不同与普通层序遍历,NULL也进队列,而且出队列的结点有两种可能一种为空,一种不为空,不像层序遍历只出非空结点
{
bool BTreeCompele(BTNode* root)
{
  Queue q;
  QueueInit(&q);
  if (root)//将根节点插入到队列中
  {
    QueuePush(&q, root);
  }
  while (!QueueEmpty(&q))
  {
    BTNode* front = QueueFront(&q);
    QueuePop(&q);//出队列中的元素
    if (front == NULL)
    {
      break;
    }
    //如果front不为空,就将它的孩子插入到队列中去,空节点也插入进去,不需要讨论
    QueuePush(&q, front->left);
    QueuePush(&q, front->right);
  }
  //break 跳出来需要判断是否后面还会出现非空节点
  while (!QueueEmpty(&q))
  {
    BTNode* front = QueueFront(&q);
    QueuePop(&q);//出队列中的元素
    if (front)//如果队列中出的节点不为空节点
    {
      QueueDestroy(&q);
      return false;
    }
  }
  QueueDestroy(&q);
  return true;
}
相关文章
|
1月前
|
C++
【C++数据结构——树】二叉树的性质(头歌实践教学平台习题)【合集】
本文档介绍了如何根据二叉树的括号表示串创建二叉树,并计算其结点个数、叶子结点个数、某结点的层次和二叉树的宽度。主要内容包括: 1. **定义二叉树节点结构体**:定义了包含节点值、左子节点指针和右子节点指针的结构体。 2. **实现构建二叉树的函数**:通过解析括号表示串,递归地构建二叉树的各个节点及其子树。 3. **使用示例**:展示了如何调用 `buildTree` 函数构建二叉树并进行简单验证。 4. **计算二叉树属性**: - 计算二叉树节点个数。 - 计算二叉树叶子节点个数。 - 计算某节点的层次。 - 计算二叉树的宽度。 最后,提供了测试说明及通关代
46 10
|
1月前
|
Java C++
【C++数据结构——树】二叉树的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现二叉树的基本运算。​ 相关知识 创建二叉树 销毁二叉树 查找结点 求二叉树的高度 输出二叉树 //二叉树节点结构体定义 structTreeNode{ intval; TreeNode*left; TreeNode*right; TreeNode(intx):val(x),left(NULL),right(NULL){} }; 创建二叉树 //创建二叉树函数(简单示例,手动构建) TreeNode*create
48 12
|
1月前
|
数据采集 存储 算法
【C++数据结构——图】图的遍历(头歌教学实验平台习题) 【合集】
本文介绍了图的遍历算法,包括深度优先遍历(DFS)和广度优先遍历(BFS)。深度优先遍历通过递归方式从起始节点深入探索图,适用于寻找路径、拓扑排序等场景;广度优先遍历则按层次逐层访问节点,适合无权图最短路径和网络爬虫等应用。文中提供了C++代码示例,演示了如何实现这两种遍历方法,并附有测试用例及结果,帮助读者理解和实践图的遍历算法。
44 0
|
1月前
|
存储 算法 测试技术
【C++数据结构——树】二叉树的遍历算法(头歌教学实验平台习题) 【合集】
本任务旨在实现二叉树的遍历,包括先序、中序、后序和层次遍历。首先介绍了二叉树的基本概念与结构定义,并通过C++代码示例展示了如何定义二叉树节点及构建二叉树。接着详细讲解了四种遍历方法的递归实现逻辑,以及层次遍历中队列的应用。最后提供了测试用例和预期输出,确保代码正确性。通过这些内容,帮助读者理解并掌握二叉树遍历的核心思想与实现技巧。
51 2
|
2月前
|
数据库
数据结构中二叉树,哈希表,顺序表,链表的比较补充
二叉搜索树,哈希表,顺序表,链表的特点的比较
数据结构中二叉树,哈希表,顺序表,链表的比较补充
|
3月前
|
机器学习/深度学习 存储 算法
数据结构实验之二叉树实验基础
本实验旨在掌握二叉树的基本特性和遍历算法,包括先序、中序、后序的递归与非递归遍历方法。通过编程实践,加深对二叉树结构的理解,学习如何计算二叉树的深度、叶子节点数等属性。实验内容涉及创建二叉树、实现各种遍历算法及求解特定节点数量。
128 4
|
3月前
|
C语言
【数据结构】二叉树(c语言)(附源码)
本文介绍了如何使用链式结构实现二叉树的基本功能,包括前序、中序、后序和层序遍历,统计节点个数和树的高度,查找节点,判断是否为完全二叉树,以及销毁二叉树。通过手动创建一棵二叉树,详细讲解了每个功能的实现方法和代码示例,帮助读者深入理解递归和数据结构的应用。
213 8
|
4月前
|
存储 算法
探索数据结构:分支的世界之二叉树与堆
探索数据结构:分支的世界之二叉树与堆
|
4月前
|
存储 算法 关系型数据库
数据结构与算法学习二一:多路查找树、二叉树与B树、2-3树、B+树、B*树。(本章为了解基本知识即可,不做代码学习)
这篇文章主要介绍了多路查找树的基本概念,包括二叉树的局限性、多叉树的优化、B树及其变体(如2-3树、B+树、B*树)的特点和应用,旨在帮助读者理解这些数据结构在文件系统和数据库系统中的重要性和效率。
44 0
数据结构与算法学习二一:多路查找树、二叉树与B树、2-3树、B+树、B*树。(本章为了解基本知识即可,不做代码学习)
|
4月前
|
存储 算法 搜索推荐
数据结构与算法学习十七:顺序储存二叉树、线索化二叉树
这篇文章主要介绍了顺序存储二叉树和线索化二叉树的概念、特点、实现方式以及应用场景。
62 0
数据结构与算法学习十七:顺序储存二叉树、线索化二叉树

热门文章

最新文章