二叉树遍历 痛定思痛。 2022-05-10 06:24 234阅读 0赞 **二叉树**:二叉树是每个节点最多有两个子树的树结构。 本文介绍二叉树的遍历相关知识。 我们学过的基本遍历方法,无非那么几个:前序,中序,后序,还有按层遍历等等。 设L、D、R分别表示遍历左子树、访问根结点和遍历右子树, 则对一棵二叉树的遍历有三种情况:DLR(称为先根次序遍历),LDR(称为中根次序遍历),LRD (称为后根次序遍历)。 首先我们定义一颗二叉树 typedef char ElementType; typedef struct TNode *Position; typedef Position BinTree; struct TNode{ ElementType Data; BinTree Left; BinTree Right; }; # 前序 # 首先访问根,再先序遍历左(右)子树,最后先序遍历右(左)子树 思路: 就是利用函数,先打印本个节点,然后对左右子树重复此过程即可。 void PreorderTraversal( BinTree BT ) { if(BT==NULL)return ; printf(" %c", BT->Data); PreorderTraversal(BT->Left); PreorderTraversal(BT->Right); } # 中序 # 首先中序遍历左(右)子树,再访问根,最后中序遍历右(左)子树 思路: 还是利用函数,先对左边重复此过程,然后打印根,然后对右子树重复。 void InorderTraversal( BinTree BT ) { if(BT==NULL)return ; InorderTraversal(BT->Left); printf(" %c", BT->Data); InorderTraversal(BT->Right); } # 后序 # 首先后序遍历左(右)子树,再后序遍历右(左)子树,最后访问根 思路: 先分别对左右子树重复此过程,然后打印根 void PostorderTraversal(BinTree BT) { if(BT==NULL)return ; PostorderTraversal(BT->Left); PostorderTraversal(BT->Right); printf(" %c", BT->Data); } # 进一步思考 # 看似好像很容易地写出了三种遍历。。。。。 但是你真的理解为什么这么写吗? 比如前序遍历,我们真的是按照定义里所讲的,首先访问根,再先序遍历左(右)子树,最后先序遍历右(左)子树。这种过程来遍历了一遍二叉树吗? 仔细想想,其实有一丝不对劲的。。。 再看代码: void Traversal(BinTree BT)//遍历 { //1111111111111 Traversal(BT->Left); //22222222222222 Traversal(BT->Right); //33333333333333 } 为了叙述清楚,我给三个位置编了号 1,2,3 我们凭什么能前序遍历,或者中序遍历,后序遍历? 我们看,前序中序后序遍历,实现的代码其实是类似的,都是上面这种格式,只是我们分别在位置1,2,3打印出了当前节点而已啊。我们凭什么认为,在1打印,就是前序,在2打印,就是中序,在3打印,就是后序呢?不管在位置1,2,3哪里操作,做什么操作,我们利用函数遍历树的顺序变过吗?当然没有啊。。。 都是三次返回到当前节点的过程:先到本个节点,也就是位置1,然后调用了其他函数,最后调用完了,我们开到了位置2。然后又调用别的函数,调用完了,我们来到了位置3.。然后,最后操作完了,这个函数才结束。代码里的三个位置,每个节点都被访问了三次。 而且不管位置1,2,3打印了没有,操作了没有,这个顺序是永远存在的,不会因为你在位置1打印了,顺序就改为前序,你在位置2打印了,顺序就成了中序。 为了有更直观的印象,我们做个试验:在位置1,2,3全都放入打印操作; 我们会发现,每个节点都被打印了三次。而把每个数第一次出现拿出来,就组成了前序遍历的序列;所有数字第二次出现拿出来,就组成了中序遍历的序列。。。。 其实,遍历是利用了一种数据结构:栈 而我们这种写法,只是通过函数,来让系统帮我们压了栈而已。为什么能实现遍历?为什么我们访问完了左子树,能返回到当前节点?这都是栈的功劳啊。我们把当前节点(对于函数就是当时的现场信息)存到了栈里,记录下来,后来才能把它拿了出来,能回到以前的节点。 想到这里,可能就有更深刻的理解了。 我们能否不用函数,不用系统帮我们压栈,而是自己做一个栈,来实现遍历呢? 先序实现思路:拿到一个节点的指针,先判断是否为空,不为空就先访问(打印)该结点,然后直接进栈,接着遍历左子树;为空则要从栈中弹出一个节点来,这个时候弹出的结点就是其父亲,然后访问其父亲的右子树,直到当前节点为空且栈为空时,结束。 核心思路代码实现: *p=root; while(p || !st.empty()) { if(p)//非空 { //visit(p);进行操作 st.push(p);//入栈 p = p->lchild;左 } else//空 { p = st.top();//取出 st.pop(); p = p->rchild;//右 } } 中序实现思路:和前序遍历一样,只不过在访问节点的时候顺序不一样,访问节点的时机是从栈中弹出元素时访问,如果从栈中弹出元素,就意味着当前节点父亲的左子树已经遍历完成,这时候访问父亲,就是中序遍历. (对应递归是第二次遇到) 核心代码实现: *p=root; while(p || !st.empty()) { if(p)//非空 { st.push(p);//压入 p = p->lchild; } else//空 { p = st.top();//取出 //visit(p);操作 st.pop(); p = p->rchild; } } 后序遍历是最难的。因为要保证左孩子和右孩子都已被访问并且左孩子在右孩子前访问才能访问根结点,这就为流程的控制带来了难点。 因为我们原来说了,后序是第三次遇到才进行操作的,所以我们很容易有这种和递归函数类似的思路:对于任一结点,将其入栈,然后沿其左子树一直往下走,一直走到没有左孩子的结点,此时该结点在栈顶,但是不能出栈访问, 因此右孩子还没访问。所以接下来按照相同的规则对其右子树进行相同的处理。访问完右孩子,该结点又出现在栈顶,此时可以将其出栈并访问。这样就保证了正确的访问顺序。可以看出,在这个过程中,每个结点都两次出现在栈顶,只有在第二次出现在栈顶时,才能访问它。因此需要多设置一个变量标识该结点是否是第一次出现在栈顶。 第二种思路:对于任一结点P,先将其入栈。如果P不存在左孩子和右孩子,或者左孩子和右孩子都已被访问过了,就可以直接访问该结点。如果有孩子未访问,将P的右孩子和左孩子依次入栈。 网上的思路大多是第一种,所以我在这里给出第二种的大概实现吧 首先初始化cur,pre两个指针,代表访问的当前节点和之前访问的节点。把根放入,开始执行。 s.push(root); while(!s.empty()) { cur=s.top(); if((cur->lchild==NULL && cur->rchild==NULL)||(pre!=NULL && (pre==cur->lchild||pre==cur->rchild))) { //visit(cur); 如果当前结点没有孩子结点或者孩子节点都已被访问过 s.pop();//弹出 pre=cur; //记录 } else//分别放入右左孩子 { if(cur->rchild!=NULL) s.push(cur->rchild); if(cur->lchild!=NULL) s.push(cur->lchild); } } 这两种方法,都是利用栈结构来实现的遍历,需要一定的栈空间,而其实存在一种时间O(N),空间O(1)的遍历方式,下次写了我再放链接。 斗个小机灵:后序是LRD,我们其实已经知道先序是DLR,那其实我们可以用先序来实现后序啊,我们只要先序的时候把左右子树换一下:DRL(这一步很好做到),然后倒过来不就是DRL了嘛。。。。。就把先序代码改的左右反过来,然后放栈里倒过来就好了,不需要上面介绍的那些复杂的方法。。。。
相关 二叉树 二叉树遍历 通过二叉树遍历求得二叉树 什么是二叉树 > > 二叉树(Binary tree)是树形结构的一个重要类型。许多实际问题抽象出来的数据结构往往是二叉树形式,即使是一般的树也能简单地转换为二叉树,而且 偏执的太偏执、/ 2022年12月04日 01:08/ 0 赞/ 302 阅读
相关 遍历二叉树 1. 二叉树的遍历 遍历定义 ——顺着某一条搜索路径巡访二叉树中的结点,使得 每个结点均被访问一次,而且仅被访问一次。 “访问”的含义可以很广,如:输出结点的信息等。 迈不过友情╰/ 2022年08月22日 14:26/ 0 赞/ 439 阅读
相关 遍历二叉树 遍历二叉树 二叉树是由3个基本单元组成的:根结点、左子树和右子树。 若限定先左后右的顺序,则遍历二叉树只有三种情况分别称为先(根)序遍历、中(根)序遍历和后(根)序遍 比眉伴天荒/ 2022年06月08日 00:48/ 0 赞/ 302 阅读
相关 遍历二叉树 遍历二叉树 今天我们学习了二叉树,现在我就基于二叉树的递归定义来说一说遍历二叉树的三种方法:先序、中序和后序。 根据二叉树的递归定义可知,二叉树是由3个基本单元组成:根结点 傷城~/ 2022年06月05日 08:11/ 0 赞/ 352 阅读
相关 二叉树遍历 include<iostream> include<queue> using namespace std; //二叉树结点 ╰+攻爆jí腚メ/ 2022年06月04日 05:35/ 0 赞/ 300 阅读
相关 二叉树遍历 前序遍历(左中右)ABDGHCEIF ![前序遍历][70] 中序遍历(左根右)GDHBAEICF![中序遍历][70 1] 后序遍历(左右根)GHDBIEFCA![ Bertha 。/ 2022年05月11日 13:48/ 0 赞/ 359 阅读
相关 二叉树遍历 二叉树:二叉树是每个节点最多有两个子树的树结构。 本文介绍二叉树的遍历相关知识。 我们学过的基本遍历方法,无非那么几个:前序,中序,后序,还有按层遍历等等。 设L、D、R 痛定思痛。/ 2022年05月10日 06:24/ 0 赞/ 235 阅读
相关 二叉树遍历 二叉树是数据结构中很重要的一个章节,很多同学反应很难,今天我就把我平时的经验给大家讲解一下,今天讲一下二叉树的遍历 工具/原料 ● 数据结构知识 ● 了解树的定义 爱被打了一巴掌/ 2022年05月10日 03:08/ 0 赞/ 317 阅读
相关 遍历二叉树 用递归的方法实现前序遍历,中序遍历,后序遍历: 用递归的方法遍历的时候,其实每个节点都遍历了三遍,根据打印时间的不同,即可实现前序中序及后续,这就是三个遍历代码一样而打印顺序 浅浅的花香味﹌/ 2021年11月01日 15:28/ 0 赞/ 464 阅读
还没有评论,来说两句吧...