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

    文章

    208

    浏览量

    24078
  • 二叉树
    +关注

    关注

    0

    文章

    74

    浏览量

    12238

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

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

收藏 人收藏

    评论

    相关推荐

    二叉树二叉树的性质(1)#数据结构

    数据函数二叉树
    未来加油dz
    发布于 :2023年09月05日 09:06:44

    二叉树二叉树的性质(2)#数据结构

    数据函数二叉树
    未来加油dz
    发布于 :2023年09月05日 09:08:32

    二叉树二叉树的性质(3)#数据结构

    数据函数二叉树
    未来加油dz
    发布于 :2023年09月05日 09:09:51

    计算机二叉树的问题

    各位大神,本人马上要考计算机级了,那个二叉树老是弄不明白,比如一个题目,一棵二叉树共有25个节点,其中五个叶子节点,则度为1的节点数为?
    发表于 09-04 09:45

    二叉树删除算法

    二叉树的删除操作主要是寻找替代点来进行替换操作。方法:先右转,再一直左转,直到左连接为空的那个点。然后,摘取出来,完成链接指向操作。public void deletmin(void){ root
    发表于 12-30 20:55

    二叉查找(GIF动图讲解)

    的数据域之值,则查找成功;否则:3.若x小于b的根节点的数据域之值,则搜索左子树;否则:4.查找右子树。图1:查找 BST 的某个元素图2 ↓ :从有序数组构造一个二叉查找 向一个
    发表于 07-29 15:24

    2016大疆笔试题与总结(编程类)

    ,若叶子节点数为m,度为2的节点数为n,则m=n+1.4. 性质4 具有n个节点的完全二叉树的深度为log2n(向下取整)+1。
    发表于 11-21 15:28

    用C语言构建一个二叉树失败

    ; ,tree.leftnode->data);这三个输出都是乱码,我不知道二叉树的建立过程问题在哪里,我应该在每个节点都分配了内存,不过结果好像有问题}
    发表于 03-24 03:47

    什么是“红黑”看了就知道

    今天我们要说的红黑就是就是一棵非严格均衡的二叉树,均衡二叉树又是在二叉搜索的基础上增加了自动维持平衡的性质,插入、搜索、删除的效率都比较
    发表于 10-27 17:00

    Java编程求二叉树的镜像两种方法介绍

    给出一棵二叉树,求它的镜像,如下图:右边是二叉树是左边二叉树的镜像。仔细分析这两棵的特点,看看能不能总结出求镜像的步骤。这两棵的根节点相
    发表于 12-16 16:25

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

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

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

    根据前、中序遍历的特点,(根左右、左根右),先根据前序遍历确定根节点,然后在中序遍历知道该根节点的左右树的数量,反推出前序遍历中左子树的结点有哪些。根据该思路进行递归即可完成
    的头像 发表于 11-27 16:25 3177次阅读

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

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

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

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

    二叉树的代码实现

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