二叉树的遍历(包括递归遍历和非递归遍历)

本文介绍了二叉树的基础知识,包括定义、性质、存储结构和遍历方法。详细讲解了先序、中序、后序及层次遍历,并分别展示了递归和非递归的遍历实现,如使用栈和三叉链表。此外,还探讨了遍历在二叉树操作中的应用,如求深度、计数叶子节点、销毁、删除子树和复制二叉树。
摘要由CSDN通过智能技术生成

二叉树基础知识

概念

  • 二叉树定义:含有n(n>=0)个节点的有限集合
  • 非空二叉树中:
    • 有且仅有一个根节点
    • 其余节点划分为两个互不相交的子集L和R,L称为左子树,R称为右子树
  • 任一结点的左、右子树的根称为该结点的左、右孩子,反过来,该结点称为孩子的双亲
  • 度:结点的孩子个数
  • 叶子结点:度为0 的结点
  • 非叶子结点:度大于0,也称为内部结点或分支结点
  • 二叉树的深度(或高度):结点的最大层次称为二叉树的深度(或高度)。(所谓层次,根节点即为第一层,以此类推)

二叉树的性质

  1. 在非空二叉树的第 i 层上最多右 2^(i-1) 个结点(i>=1)
  2. 深度为 k 的二叉树最多有 2^k - 1 个结点(k>=1)
  3. 对于任意一颗二叉树,如果度为 0 的结点个数为 n0 ,度为 2 的结点个数为 n2,则 n0 = n2+1
  4. 具有 n 个结点的完全二叉树的深度 k = [log2n] + 1
  5. 对于含 n 个结点的完全二叉树中编号为 i (1<=i<=n) 的结点
    1. 如果 i = 1,则 i 结点是这颗完全二叉树的根,没有双亲;否则其双亲的编号为 [i/2]
    2. 如果 2i>n,则 i 结点没有左孩子;否则其左孩子的编号为 2i
    3. 如果 2i+1>n,则 i 结点没有右孩子;否则其右孩子的编号为 2i+1

满二叉树与完全二叉树

  • 满二叉树:深度为 k 且有 2^k - 1 个结点的二叉树

  • 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。深度为K且含 n 个结点的二叉树,如果其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应,则称之为完全二叉树。换句话讲,在一棵二叉树中,除最后一层外,若其余层都是满的,并且最后一层或者是满的,或者是在右边缺少连续若干节点,则此二叉树为完全二叉树。

二叉树的存储结构

顺序存储

    // 一维数组实现
    typedef char TElemType;	  // 假设结点元素类型为字符
    typedef struct {
   
    TElemType * elem;	      // 0号单元闲置
    int lastIndex;		      // 二叉树最后一个结点的编号
    } SqBiTree;

链式存储

  • 二叉链表
typedef struct BiTNode{
   
    TElemType data;	  				// 数据域
    struct BiTNode *lchild,*rchild;	// 左、右孩子指针域
} BiTNode,*BiTNode;
  • 三叉链表
typedef struct TriTNode{
   
    TElemType data;	  						// 数据域
    struct TriTNode *parent,*lchild,*rchild;// 双亲左、右孩子指针域
} TriTNode,*TriTNode;

二叉树的遍历

首先来认识一下遍历

遍历:树的遍历(也称为树的搜索)是图的遍历的一种,指的是按照某种规则,不重复地访问某种树的所有结点,使得每个结点被且仅被访问的过程。具体的访问操作可能是检查节点的值、更新节点的值等。二叉树的遍历也适用于其它树的遍历。

遍历是二叉树的一类重要操作,也是二叉树的其他一些操作和各种应用算法的基本框架。

遍历有两种类别,一种是深度优先遍历,另一种是广度优先遍历

  • 深度优先遍历:先访问子节点,再访问父节点,最后是第二个子节点
    • 先序遍历:VLR,即根结点->左结点->右节点
    • 中序遍历:LVR,即左结点->根结点->右节点
    • 后序遍历:LRV,即左结点->右结点->根节点
  • 广度优先遍历:先访问第一个子节点,再访问第二个子节点,最后访问父节点,二叉树的广度优先遍历就是层次遍历

遍历.jpg

由于从给定的某个节点出发,有多个可以前往的下一个节点(树不是线性数据结构),所以在顺序计算(即非并行计算)的情况下,只能推迟对某些节点的访问——即以某种方式保存起来以便稍后再访问。常见的做法是采用栈(LIFO)或队列(FIFO)。由于树本身是一种自我引用(即递归定义)的数据结构,因此很自然也可以用递归方式。

所以,下面我重点总结了这两种不同的遍历实现方式。

注:本文所讲的数据结构均为C语言版

先序遍历

递归遍历

Status PreOrderTraverse(BiTree T,Status(*visit)(TElemType e)){
   
   if(NULL == T) return OK;
   if(ERROR == visit(T->data))
       return ERROR; //访问结点的数据域
   if(ERROR == PreOrderTraverse(T->lchild,visit))
       return ERROR; //递归遍历T的左子树
   return PreOrderTraverse(T->rchild,visit);//递归遍历T的右子树
    
}

非递归遍历

1.使用栈的非递归先序遍历算法

/**********
二叉链表类型定义:
typedef struct BiTNode {
  TElemType  data;
  struct BiTNode  *lchild,*rchild;
} BiTNode, *BiTree;
栈类型Stack的相关定义:
typedef BiTree SElemType;   	      // 栈的元素类型
Status InitStack(Stack &S); 	      // 初始化栈
Status StackEmpty(Stack S); 	      // 判栈空
Status Push(Stack &S, SElemType e);	  // 进栈
Status Pop(Stack &S, SElemType &e);   // 出栈 
Status GetTop(Stack S, SElemType &e); // 取栈顶元素
**********/
void PreOrder(BiTree T, void (*visit)(TElemType))
/*对每个结点的元素域data调用函数visit进行访问 */
{
   
   Stack S;   InitStack(S);
   BiTree p = T;
   // 先序访问根节点,遍历左节点 ,左节点入栈
   // StackEmpty(Stack S);S为空返回true反之false;
   // 当栈不空或 p非空时
   while(!StackEmpty(S) || p!=NULL){
   
     while(p!=NULL){
   
        visit(p->data);
        Push(S,p);
        p = p->lchild;
     }
     if(!StackEmpty(S)){
   
        Pop(S,p); // 执行完 p指向S出栈的元素
        p = p->rchild;
     }
   }
}

先序遍历.jpg

2.不使用栈,使用三叉链表的非递归先序遍历算法

/**********  
三叉链表类型定义:  
typedef struct TriTNode {  
	TElemType data;  
	struct TriTNode *parent, *lchild, *rchild;  
} TriTNode, *TriTree;  
**********/  
void PreOrder(BiTree T, void (*visit)(TElemType)){
   
    TriTree p, pr;
    if(T
  • 8
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值