0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看威廉希尔官方网站 视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

4中二叉树的遍历方式介绍

lviY_AI_shequ 来源:未知 作者:胡薇 2018-04-27 17:23 次阅读

对于一种数据结构而言,遍历是常见操作。二叉树是一种基本的数据结构,是一种每个节点的儿子数目都不多于2的树。二叉树的节点声明如下:

typedef struct TreeNode *PtrToNode;

typedef struct TreeNode *BinTree;

struct TreeNode

{

int Data; //为简单起见,不妨假设树节点的元素为int型

BinTree Left;

BinTree Right;

};

二叉树的遍历主要有先序遍历,中序遍历,后序遍历,层序遍历四种方式,下面一一介绍。

1. 先序遍历

在先序遍历中,对节点的访问工作是在它的左右儿子被访问之前进行的。换言之,先序遍历访问节点的顺序是根节点-左儿子-右儿子。由于树可以通过递归来定义,所以树的常见操作用递归实现常常是方便清晰的。递归实现的代码如下:

void PreOrderTraversal(BinTree BT)

{

if( BT )

{

printf(“%d\n”, BT->Data); //对节点做些访问比如打印

PreOrderTraversal(BT->Left); //访问左儿子

PreOrderTraversal(BT->Right); //访问右儿子

}

}

由递归代码可以看出,该递归为尾递归(尾递归即递归形式在函数末尾或者说在函数即将返回前)。尾递归的递归调用需要用栈存储调用的信息,当数据规模较大时容易越出栈空间。虽然现在大部分的编译器能够自动去除尾递归,但是即使如此,我们不妨自己去除。非递归先序遍历算法基本思路:使用堆栈

a. 遇到一个节点,访问它,然后把它压栈,并去遍历它的左子树;

b. 当左子树遍历结束后,从栈顶弹出该节点并将其指向右儿子,继续a步骤;

c. 当所有节点访问完即最后访问的树节点为空且栈空时,停止。

实现代码如下:

void PreOrderTraversal(BinTree BT)

{

BinTree T = BT;

Stack S = CreatStack(MAX_SIZE); //创建并初始化堆栈S

while(T || !IsEmpty(S))

{

while(T) //一直向左并将沿途节点访问(打印)后压入堆栈

{

printf("%d\n", T->Data);

Push(S, T);

T = T->Left;

}

if (!IsEmpty(S))

{

T = Pop(S); //节点弹出堆栈

T = T->Right; //转向右子树

}

}

}

2. 中序遍历

中序遍历的遍历路径与先序遍历完全一样。其实现的思路也与先序遍历非常相似。其主要的不同点是访问节点顺序不同:中序遍历是访问完所有左儿子后再访问根节点,最后访问右儿子,即为左儿子-根节点-右儿子。

递归实现的代码如下:

void InOrderTraversal(BinTree BT)

{

if(BT)

{

InOrderTraversal(BT->Left);

printf("%d\n", BT->Data);

InOrderTraversal(BT->Right);

}

}

非递归辅助栈实现代码如下:

void InOrderTraversal(BinTree BT)

{

BinTree T = BT;

Stack S = CreatStack(MaxSize); //创建并初始化堆栈S

while(T || !IsEmpty(S))

{

while(T) //一直向左并将沿途节点压入堆栈

{

Push(S,T);

T = T->Left;

}

if(!IsEmpty(S))

{

T = Pop(S); //节点弹出堆栈

printf("%d\n", T->Data); //(访问) 打印结点

T = T->Right; //转向右子树

}

}

}

非递归不用辅助栈实现中序遍历:

试设计一个非递归算法,按中根顺序遍历非线索二叉树,但不得用任何辅助栈。在执行算法期间,允许改变左孩子指针和右孩子指针的值。

算法:右线索化+回溯

若当前树的根节点p有左孩子且未被线索化:将其左孩子的最右结点(可为左孩子本身)指向p,即右线索化,然后p = p->lChild;

若p有左孩子但已被线索化,说明该p是回溯上来的,即左孩子已经被访问了,则释放线索化的指针;

若p无左孩子,打印p,向上回溯(即p = p->rChild)。

代码如下:

/*

输入:ABDH##I##E##CF#J##G##

*/

#include

typedef struct BTNode* Position;

typedef Position BTree;

typedef char ElementType;

struct BTNode {

ElementType data;

Position lChild, rChild;

};

BTree CreateBTree(void);

void Inorder(BTree bt);

int main()

{

BTree bt = CreateBTree();

Inorder(bt);

return 0;

}

void Inorder(BTree bt)

{

Position p = bt;

while (p)

{

Position pLeft = p->lChild;

if (pLeft)

{

while (pLeft->rChild && pLeft->rChild != p) //找到以p为根结点的树的最右孩子

pLeft = pLeft->rChild;

if (pLeft->rChild == NULL) //线索化

{

pLeft->rChild = p;

p = p->lChild;

continue;

}

else //线索化后已被访问

{

pLeft->rChild = NULL; //释放指向根节点(祖先)的指针

}

}

printf("%c ", p->data); //打印

p = p->rChild; //向上回溯或者转向右子树

}

printf("\n");

}

BTree CreateBTree() //按照先序序列建立二叉树

{

BTree bt = NULL;

char ch;

scanf("%c", &ch);

if (ch != '#') //'#'代表空节点

{

bt = new BTNode;

bt->data = ch;

bt->lChild = CreateBTree();

bt->rChild = CreateBTree();

}

return bt;

}

运行结果:

参考博客:http://m.blog.csdn.net/blog/Raito__/40618257

3. 后序遍历

后序遍历与中序遍历,先序遍历的路径也完全一样。主要的不同点是后序遍历访问节点的顺序是先访问左儿子和右儿子,最后访问节点,即左儿子-右儿子-根节点。

递归实现思路与中序遍历和先序遍历相似,代码如下:

void PostOrderTraversal(BinTree BT)

{

if (BT)

{

PostOrderTraversal(BT->Left);

PostOrderTraversal(BT->Right);

printf("%d\n", BT->Data);

}

}

后序遍历的非递归实现

思路一:

对于一个节点而言,要实现访问顺序为左儿子-右儿子-根节点,可以利用后进先出的栈,在节点不为空的前提下,依次将根节点,右儿子,左儿子压栈。故我们需要按照根节点-右儿子-左儿子的顺序遍历树,而我们已经知道先序遍历的顺序是根节点-左儿子-右儿子,故只需将先序遍历的左右调换并把访问方式打印改为压入另一个栈即可。最后一起打印栈中的元素。代码如下:

void PostOrderTraversal(BinTree BT)

{

BinTree T = BT;

Stack S1 = CreatStack(MAX_SIZE); //创建并初始化堆栈S1

Stack S2 = CreatStack(MAX_SIZE); //创建并初始化堆栈S2

while(T || !IsEmpty(S1))

{

while(T) //一直向右并将沿途节点访问(压入S2)后压入堆栈S1

{

Push(S2, T);

Push(S1, T);

T = T->Right;

}

if (!IsEmpty(S1))

{

T = Pop(S1); //节点弹出堆栈

T = T->Left; //转向左子树

}

}

while(!IsEmpty(S2)) //访问(打印)S2中元素

{

T = Pop(S2);

printf("%d\n", T->Data);

}

}

思路一的优点是由于利用了先序遍历的思想,代码较简洁,思路较清晰。缺点是需要用一个栈来存储树的所有节点,空间占用较大。

思路二:

要访问一个节点的条件上一个访问的节点是右儿子。我们可以增加一个变量Prev来判断当前节点Curr的上一个节点与它的关系来执行相应的操作。

若Prev为空(Curr节点是根节点)或者Prev是Curr的父节点,将Curr节点的左孩子和右孩子分别压入栈;

若Prev是Curr的左儿子,则将Curr的右儿子压入栈;

否则Prev是Curr的右儿子,访问Curr;

代码如下:

void PostOrderTraversal(BinTree BT)

{

if(BT == NULL)

return ;

Stack S = CreatStack(MAX_SIZE);

BinTree Prev = NULL , Curr = NULL; //初始化

s.push(BT);

while(!IsEmpty(S))

{

Curr = Top(S); //将栈顶元素赋给Curr

if(Prev == NULL || Prev->Left == Curr || Prev->Right == Curr) //若Prev为NULL或是Curr的父节点

{

if(Curr->Left != NULL)

Push(S, Curr->Left);

else if(Curr->Right != NULL)

Push(S, Curr->Right);

}

else if(Curr->Left == Prev) //若Prev是Curr的左儿子

{

if(Curr->Right != NULL)

Push(S, Curr->Right);

}

else

{

printf("%d\n", Curr->Data); //访问当前节点

Pop(S); //访问后弹出

}

Prev = Curr; //处理完当前节点后将Curr节点变为Prev节点

}

}

4. 层序遍历

二叉树遍历的核心问题是二维结构的线性化。我们通过节点访问其左右儿子时,存在的问题是访问左儿子后,右儿子怎么访问。因此我们需要一个存储结构保存暂时不访问的节点。前面三种遍历方式的非递归实现,我们是通过堆栈来保存。事实上也可以通过队列来保存。

队列实现的基本思路:遍历从根节点开始,首先将根节点入队,然后执行循环:节点出队,访问(访问)根节点,将左儿子入队,将右儿子入队,直到队列为空停止。

这种遍历方式的结果是将二叉树从上到下,从左至右一层一层的遍历,即层序遍历,代码实现如下:

void LevelOrderTraversal(BinTree BT)

{

BinTree T;

Queue Q; //声明一个队列

if (BT == NULL)

return; //如果树为空,直接返回

Q = CreatQueue(MAX_SIZE); //创建并初始化队列

AddQ(Q, BT); //将根节点入队

while (!IsEmpty(Q))

{

T = DeleteQ(Q); //节点出队

printf("%d\n", T->Data); //访问出队的节点

if (T->Left) AddQ(Q, T->Left); //若左儿子不为空,将其入队

if (T->Right) AddQ(Q, T->Right) //若右儿子不为空,将其入队

}

}

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 节点
    +关注

    关注

    0

    文章

    218

    浏览量

    24422
  • 二叉树
    +关注

    关注

    0

    文章

    74

    浏览量

    12324

原文标题:二叉树的遍历:先序中序后序遍历的递归与非递归实现及层序遍历

文章出处:【微信号:AI_shequ,微信公众号:人工智能爱好者社区】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    基于二叉树的时序电路测试序列设计

    为了实现时序电路状态验证和故障检测,需要事先设计一个输入测试序列。基于二叉树节点和树枝的特性,建立时序电路状态二叉树,按照电路二叉树节点(状态)与树枝(输入)的层次逻辑
    发表于 07-12 13:57 0次下载
    基于<b class='flag-5'>二叉树</b>的时序电路测试序列设计

    二叉树层次遍历算法的验证

    实现二叉树的层次遍历算法,并对用”A(B(D,E(H(J,K(L,M(,N))))),C(F,G(,I)))”创建的二叉树进行测试。
    发表于 11-28 01:05 2096次阅读
    <b class='flag-5'>二叉树</b>层次<b class='flag-5'>遍历</b>算法的验证

    详解电源二叉树到底是什么

    作为数据结构的基础,分很多种,像 AVL 、红黑二叉搜索....今天我想分享的是关于二叉树
    的头像 发表于 06-06 15:05 1w次阅读
    详解电源<b class='flag-5'>二叉树</b>到底是什么

    C语言二叉树代码免费下载

    本文档的主要内容详细介绍的是C语言二叉树代码免费下载。
    发表于 08-27 08:00 1次下载

    面试算法之重建二叉树

    那么问题来了,只知道前序遍历能不能反推二叉树呢?我们就试一下,比如题目中所述,{1,2,4,7,3,5,6,8},根据前序遍历,根、左、右,1 肯定是 根节点,那么一下2,
    的头像 发表于 11-27 15:59 2555次阅读

    面试二叉树看这11个就够了

    根据前、遍历的特点,(根左右、左根右),先根据前序遍历确定根节点,然后在遍历知道该根节点的左右
    的头像 发表于 11-27 16:25 3324次阅读

    二叉树操作的相关知识和代码详解

    见的二叉树操作作个总结: 前序遍历遍历,后序遍历; 层次遍历; 求
    的头像 发表于 12-12 11:04 2042次阅读
    <b class='flag-5'>二叉树</b>操作的相关知识和代码详解

    二叉树的前序遍历非递归实现

    我们之前说了二叉树基础及二叉的几种遍历方式及练习题,今天我们来看一下二叉树的前序遍历非递归实现。
    的头像 发表于 05-28 13:59 1954次阅读

    C语言数据结构:什么是二叉树

    完全二叉树:完全二叉树是效率很高的数据结构。对于深度为K,有n个节点的二叉树,当且仅当每一个节点都与深度为K的满二叉树编号从1至n的节点一
    的头像 发表于 04-21 16:20 2509次阅读

    怎么就能构造成二叉树呢?

    一直跟着公众号学算法的录友 应该知道,我在二叉树:构造二叉树登场!,已经讲过,只有 序与后序 和 序和前序 可以确定一颗唯一的二叉树
    的头像 发表于 07-14 11:20 1581次阅读

    二叉树的最大深度

    精简之后的代码根本看不出是哪种遍历方式,也看不出递归三部曲的步骤,所以如果对二叉树的操作还不熟练,尽量不要直接照着精简代码来学。
    的头像 发表于 07-26 11:28 1073次阅读

    使用C语言代码实现平衡二叉树

    这篇博客主要总结平衡二叉树,所以,二叉排序树知识不会提及,但是会用到。
    的头像 发表于 09-21 11:00 1093次阅读

    二叉树的代码实现

    二叉树的主要操作有遍历,例如有先序遍历遍历、后序遍历。在
    的头像 发表于 01-18 10:41 1228次阅读
    <b class='flag-5'>二叉树</b>的代码实现

    C++自定义二叉树并输出二叉树图形

    使用C++构建一个二叉树并输出。
    的头像 发表于 01-10 16:29 1746次阅读
    C++自定义<b class='flag-5'>二叉树</b>并输出<b class='flag-5'>二叉树</b>图形

    解析LeetCode第226号题目:反转二叉树

    *简单讲就是把每个节点的左子树和右子树进行交换** 。 显然,这需要我们能够遍历二叉树。 那么遍历二叉树就有两种经典的解法:深度优先
    的头像 发表于 02-17 14:52 856次阅读
    解析LeetCode第226号题目:反转<b class='flag-5'>二叉树</b>