`
844604778
  • 浏览: 548488 次
文章分类
社区版块
存档分类
最新评论

二叉树的遍历-递归与非递归 - 海子

 
阅读更多

    二叉树的遍历-递归与非递归

    二叉树是一种非常重要的数据结构,很多其它数据结构都是基于二叉树的基础演变而来的。对于二叉树,有前序、中序以及后序三种遍历方法。因为树的定义本身就是递归定义,因此采用递归的方法去实现树的三种遍历不仅容易理解而且代码很简洁。而对于树的遍历若采用非递归的方法,就要采用栈去模拟实现。在三种遍历中,前序和中序遍历的非递归算法都很容易实现,非递归后序遍历实现起来相对来说要难一点。

    .前序遍历

    前序遍历按照“根结点-左孩子-右孩子”的顺序进行访问。

    1.递归实现

    void preOrder1(BinTree *root)//递归前序遍历

    {

    if(root!=NULL)

    {

    cout<<root->data<<" ";

    preOrder1(root->lchild);

    preOrder1(root->rchild);

    }

    }

    2.非递归实现

    方法一:

    根据前序遍历访问的顺序,优先访问根结点,然后再分别访问左孩子和右孩子。即对于任一结点,其可看做是根结点,因此可以直接访问,访问完之后,若其左孩子不为空,按相同规则访问它的左子树;当访问其左子树时,再访问它的右子树。因此其处理过程如下:

    对于任一结点P:

    1)访问结点P,并将结点P入栈;

    2)判断结点P的左孩子是否为空,若为空,则取栈顶结点并进行出栈操作,并将栈顶结点的右孩子置为当前的结点P,循环至1);若不为空,则将P的左孩子置为当前的结点P;

    3)直到P为NULL并且栈为空,则遍历结束。

    void preOrder2(BinTree *root)// 非递归前序遍历

    {

    stack<BinTree*> s;

    BinTree *p=root;

    while(p!=NULL||!s.empty())

    {

    while(p!=NULL)

    {

    cout<<p->data<<" ";

    s.push(p);

    p=p->lchild;

    }

    if(!s.empty())

    {

    p=s.top();

    s.pop();

    p=p->rchild;

    }

    }

    }

    方法二(更加容易理解的方式):

    初始:维护一个栈,将根节点压入栈中。

    循环:每次从栈顶读出一个节点信息,直接将节点值输出,同时将儿子节点按从右到做的顺序推到栈顶。

    分析:跟递归调用的整体思路一样,不同的是,递归调用时是利用运行时系统所维护的程序调用栈来维护顺序,而这个非递归方法是用过自己维护的栈来保存信息。如此节省了调用栈的空间。

    public void preOrderTravNoRecur(Node n) {

    Stack<Node> stack = new Stack<Node>();

    stack.add(root);

    while (!stack.empty()) {

    Node t = stack.pop();

    System.out.print(t.value + " ");

    if (t.rightNode != null)

    stack.add(t.rightNode);

    if (t.leftNode != null)

    stack.add(t.leftNode);

    }

    }

    .中序遍历

    中序遍历按照“左孩子-根结点-右孩子”的顺序进行访问。

    1.递归实现

    void inOrder1(BinTree *root) //递归中序遍历

    {

    if(root!=NULL)

    {

    inOrder1(root->lchild);

    cout<<root->data<<" ";

    inOrder1(root->rchild);

    }

    }

    2.非递归实现

    根据中序遍历的顺序,对于任一结点,优先访问其左孩子,而左孩子结点又可以看做一根结点,然后继续访问其左孩子结点,直到遇到左孩子结点为空的结点才进行访问,然后按相同的规则访问其右子树。因此其处理过程如下:

    对于任一结点P,

    1)若其左孩子不为空,则将P入栈并将P的左孩子置为当前的P,然后对当前结点P再进行相同的处理;

    2)若其左孩子为空,则取栈顶元素并进行出栈操作,访问该栈顶结点,然后将当前的P置为栈顶结点的右孩子;

    3)直到P为NULL并且栈为空则遍历结束

    void inOrder2(BinTree *root)// 非递归中序遍历

    {

    stack<BinTree*> s;

    BinTree *p=root;

    while(p!=NULL||!s.empty())

    {

    while(p!=NULL)

    {

    s.push(p);

    p=p->lchild;

    }

    if(!s.empty())

    {

    p=s.top();

    cout<<p->data<<" ";

    s.pop();

    p=p->rchild;

    }

    }

    }

    .后序遍历

    后序遍历按照“左孩子-右孩子-根结点”的顺序进行访问。

    1.递归实现

    void postOrder1(BinTree *root) //递归后序遍历

    {

    if(root!=NULL)

    {

    postOrder1(root->lchild);

    postOrder1(root->rchild);

    cout<<root->data<<" ";

    }

    }

    2.非递归实现

    后序遍历的非递归实现是三种遍历方式中最难的一种。因为在后序遍历中,要保证左孩子和右孩子都已被访问并且左孩子在右孩子前访问才能访问根结点,这就为流程的控制带来了难题。下面介绍两种思路。

    第一种思路:

    对于任一结点P,将其入栈,然后沿其左子树一直往下搜索,直到搜索到没有左孩子的结点,此时该结点出现在栈顶,但是此时不能将其出栈并访问,因此其右孩子还未被访问。所以接下来按照相同的规则对其右子树进行相同的处理,当访问完其右孩子时,该结点又出现在栈顶,此时可以将其出栈并访问。这样就保证了正确的访问顺序。可以看出,在这个过程中,每个结点都两次出现在栈顶,只有在第二次出现在栈顶时,才能访问它。因此需要多设置一个变量标识该结点是否是第一次出现在栈顶。

    void postOrder2(BinTree *root)//非递归后序遍历

    {

    stack<BTNode*> s;

    BinTree *p=root;

    BTNode *temp;

    while(p!=NULL||!s.empty())

    {

    while(p!=NULL)//沿左子树一直往下搜索,直至出现没有左子树的结点

    {

    BTNode *btn=(BTNode *)malloc(sizeof(BTNode));

    btn->btnode=p;

    btn->isFirst=true;

    s.push(btn);

    p=p->lchild;

    }

    if(!s.empty())

    {

    temp=s.top();

    s.pop();

    if(temp->isFirst==true)//表示是第一次出现在栈顶

    {

    temp->isFirst=false;

    s.push(temp);

    p=temp->btnode->rchild;

    }

    else//第二次出现在栈顶

    {

    cout<<temp->btnode->data<<" ";

    p=NULL;

    }

    }

    }

    }

    第二种思路(较简洁的代码):

    要保证根结点在左孩子和右孩子访问之后才能访问,因此对于任一结点P,先将其入栈。如果P不存在左孩子和右孩子,则可以直接访问它;或者P存在左孩子或者右孩子,但是其左孩子和右孩子都已被访问过了,则同样可以直接访问该结点。若非上述两种情况,则将P的右孩子和左孩子依次入栈,这样就保证了每次取栈顶元素的时候,左孩子在右孩子前面被访问,左孩子和右孩子都在根结点前面被访问。

    void postOrder3(BinTree * root)//非递归后序遍历

    {

    stack < BinTree *> s;

    BinTree * cur;//当前结点

    BinTree * pre = NULL;//前一次访问的结点

    s.push(root);

    while ( ! s.empty())

    {

    cur = s.top();

    //如果当前结点没有孩子结点或者孩子节点都已被访问过

    if ((cur -> lchild == NULL && cur -> rchild == NULL) ||

    (pre != NULL && (pre == cur -> lchild || pre == cur -> rchild)))

    {

    cout << cur -> data <<" " ;

    s.pop();

    pre = cur;

    }

    else

    {

    if (cur -> rchild != NULL)

    s.push(cur -> rchild);

    if (cur -> lchild != NULL)

    s.push(cur -> lchild);

    }

    }

    }

    四、层次遍历

    • 无法使用递归方法

    层序遍历不同于其他的遍历。可以通过反证法证明:

    如果能实现对 A 节点的层序递归,在对 A 节点处理的过程中,应该递归的对两个儿子 B 和 C 分别调用了层序遍历。在这种情况下,我们无法让 B 和 C 的同一个层级的儿子在集中的时间中被遍历到,换言之,B 的第一层儿子在对 B 的调用中被遍历,而 C 的第一层儿子,则在对 C 的调用中遍历,这是分离开的。不成立,得证。

    • 非递归方法:

    分析:此方法类似于前序遍历的非递归方法的第一种。用一个栈维护信息。

    public void levelOrderTrav(Node n) {

    System.out.print("Level OrderTrav: ");

    Queue<Node> q = new LinkedList<Node>();

    q.add(n);

    while (q.size() != 0) {

    n = q.poll();

    System.out.print(" " + n.value);

    if (n.leftNode != null)

    q.add(n.leftNode);

    if (n.rightNode != null)

    q.add(n.rightNode);

    }

    }

    五、整个程序完整的代码

    /*二叉树的遍历* 2011.8.25*/

    #include <iostream>

    #include<string.h>

    #include<stack>

    using namespace std;

    typedef struct node

    {

    char data;

    struct node *lchild,*rchild;

    }BinTree;

    typedef struct node1

    {

    BinTree *btnode;

    bool isFirst;

    }BTNode;

    void creatBinTree(char *s,BinTree *&root) //创建二叉树,s为形如A(B,C(D,E))形式的字符串

    {

    int i;

    bool isRight=false;

    stack<BinTree*> s1; //存放结点

    stack<char> s2; //存放分隔符

    BinTree *p,*temp;

    root->data=s[0];

    root->lchild=NULL;

    root->rchild=NULL;

    s1.push(root);

    i=1;

    while(i<strlen(s))

    {

    if(s[i]=='(')

    {

    s2.push(s[i]);

    isRight=false;

    }

    else if(s[i]==',')

    {

    isRight=true;

    }

    else if(s[i]==')')

    {

    s1.pop();

    s2.pop();

    }

    else if(isalpha(s[i]))

    {

    p=(BinTree *)malloc(sizeof(BinTree));

    p->data=s[i];

    p->lchild=NULL;

    p->rchild=NULL;

    temp=s1.top();

    if(isRight==true)

    {

    temp->rchild=p;

    cout<<temp->data<<"的右孩子是"<<s[i]<<endl;

    }

    else

    {

    temp->lchild=p;

    cout<<temp->data<<"的左孩子是"<<s[i]<<endl;

    }

    if(s[i+1]=='(')

    s1.push(p);

    }

    i++;

    }

    }

    void display(BinTree *root) //显示树形结构

    {

    if(root!=NULL)

    {

    cout<<root->data;

    if(root->lchild!=NULL)

    {

    cout<<'(';

    display(root->lchild);

    }

    if(root->rchild!=NULL)

    {

    cout<<',';

    display(root->rchild);

    cout<<')';

    }

    }

    }

    void preOrder1(BinTree *root) //递归前序遍历

    {

    if(root!=NULL)

    {

    cout<<root->data<<" ";

    preOrder1(root->lchild);

    preOrder1(root->rchild);

    }

    }

    void inOrder1(BinTree *root) //递归中序遍历

    {

    if(root!=NULL)

    {

    inOrder1(root->lchild);

    cout<<root->data<<" ";

    inOrder1(root->rchild);

    }

    }

    void postOrder1(BinTree *root) //递归后序遍历

    {

    if(root!=NULL)

    {

    postOrder1(root->lchild);

    postOrder1(root->rchild);

    cout<<root->data<<" ";

    }

    }

    void preOrder2(BinTree *root) //非递归前序遍历

    {

    stack<BinTree*> s;

    BinTree *p=root;

    while(p!=NULL||!s.empty())

    {

    while(p!=NULL)

    {

    cout<<p->data<<" ";

    s.push(p);

    p=p->lchild;

    }

    if(!s.empty())

    {

    p=s.top();

    s.pop();

    p=p->rchild;

    }

    }

    }

    void inOrder2(BinTree *root) //非递归中序遍历

    {

    stack<BinTree*> s;

    BinTree *p=root;

    while(p!=NULL||!s.empty())

    {

    while(p!=NULL)

    {

    s.push(p);

    p=p->lchild;

    }

    if(!s.empty())

    {

    p=s.top();

    cout<<p->data<<" ";

    s.pop();

    p=p->rchild;

    }

    }

    }

    void postOrder2(BinTree *root) //非递归后序遍历

    {

    stack<BTNode*> s;

    BinTree *p=root;

    BTNode *temp;

    while(p!=NULL||!s.empty())

    {

    while(p!=NULL) //沿左子树一直往下搜索,直至出现没有左子树的结点

    {

    BTNode *btn=(BTNode *)malloc(sizeof(BTNode));

    btn->btnode=p;

    btn->isFirst=true;

    s.push(btn);

    p=p->lchild;

    }

    if(!s.empty())

    {

    temp=s.top();

    s.pop();

    if(temp->isFirst==true) //表示是第一次出现在栈顶

    {

    temp->isFirst=false;

    s.push(temp);

    p=temp->btnode->rchild;

    }

    else //第二次出现在栈顶

    {

    cout<<temp->btnode->data<<" ";

    p=NULL;

    }

    }

    }

    }

    void postOrder3(BinTree *root) //非递归后序遍历

    {

    stack<BinTree*> s;

    BinTree *cur; //当前结点

    BinTree *pre=NULL; //前一次访问的结点

    s.push(root);

    while(!s.empty())

    {

    cur=s.top();

    if((cur->lchild==NULL&&cur->rchild==NULL)||

    (pre!=NULL&&(pre==cur->lchild||pre==cur->rchild)))

    {

    cout<<cur->data<<" "; //如果当前结点没有孩子结点或者孩子节点都已被访问过

    s.pop();

    pre=cur;

    }

    else

    {

    if(cur->rchild!=NULL)

    s.push(cur->rchild);

    if(cur->lchild!=NULL)

    s.push(cur->lchild);

    }

    }

    }

    int main(int argc, char *argv[])

    {

    char s[100];

    while(scanf("%s",s)==1)

    {

    BinTree *root=(BinTree *)malloc(sizeof(BinTree));

    creatBinTree(s,root);

    display(root);

    cout<<endl;

    preOrder2(root);

    cout<<endl;

    inOrder2(root);

    cout<<endl;

    postOrder2(root);

    cout<<endl;

    postOrder3(root);

    cout<<endl;

    }

    return 0;

    }

分享到:
评论

相关推荐

    60道关于Redis的常见面试题.pdf

    - 1. 什么是 Redis?它的主要特点是什么? - 2. Redis 支持哪些数据结构?请详细描述每种数据结构的用途和特点。 - 3. 什么是缓存穿透?在使用 Redis 时,如何防止缓存穿透? - 4. 介绍 Redis 的持久化机制以及对比它们之间的区别。 - 5. 如何实现 Redis 的分布式锁?你了解的分布式锁有哪些实现方式? - 6. Redis 的数据淘汰策略有哪些?分别是如何工作的? - 7. 什么是 Redis 事务?它是如何实现的?与传统数据库事务有何不同? - 8. 如何设置 Redis 的主从复制?主从复制有什么优势和限制? - 9. Redis 支持的数据结构中,有哪些可以实现计数功能?请详细说明其使用场景。 - 10. 什么是 Redis Sentinel?它的作用是什么?如何配置和使用 Sentinel?

    2024年社交媒体广告行业分析报告.pptx

    2024年社交媒体广告行业分析报告.pptx

    网站界面设计mortal0418代码

    网站界面设计mortal0418代码

    2024年休闲椅行业分析报告.pptx

    2024年休闲椅行业分析报告.pptx

    anaconda3 -windows安装的

    anaconda3 -windows安装的

    华为客户关系管理策略解析glz.pptx

    华为客户关系管理策略解析glz.pptx

    asp.net基于三层模式实验室仪器设备管理系统源码.7z

    实验室设备仪器管理系统基于MVC思想和三层设计模式构建,前台采用bootstrap响应式框架,后台运用div+css技术,确保用户界面的友好与兼容性。在Visual Studio 2010或更高版本软件上进行程序开发,利用sqlserver2005或更先进的数据库系统提供稳定的数据支持。 该系统包含四个核心模块:实验室登陆模块、学生模块、教师模块和管理员模块。登陆模块提供用户注册和登陆功能,确保用户信息的准确与安全。学生模块提供实验课仪器设备的信息查询、借领仪器耗材、设备事故的登记等服务,满足学生在实验过程中的各种需求。 管理员模块功能丰富,包括实验室设备信息查询、设备事故记录、设备资料管理、设备损坏管理以及设备耗材借领等。管理员可以方便地查询和统计设备仪器信息,上报和处理设备事故,更新设备操作指南,管理设备损坏信息,以及处理设备耗材的借领和归还。 实验设备管理数据库是系统的核心部分,管理员可以添加、删除、更改设备信息,记录报废、维修、申请购买以及新增设备的详细信息。所有相关信息如报废表、维修表、设备购买申请表、新增设备属性表等都会在终端实时显示,确保信息的及时性和准确性。 此

    java练习题2.txt

    java练习题

    国产达梦数据库DM88.1.1.45下载链接,Linux-rh7-64位版本.zip

    国产达梦数据库DM88.1.1.45下载链接,Linux-rh7-64位版本.zip

    物联网嵌入式ESP32开发例程18-FreeRTOS操作系统之任务通知模拟事件标志组C程序代码.rar

    1、嵌入式物联网ESP32项目实战开发。例程经过精心编写,简单好用。 2、代码使用Visual Studio Code + ESP-IDF开发,C语言编程。例程在ESP32-S3上运行。若在其他型号上运行,请自行调整。 3、如果接入其他传感器,请查看发布的其他资料。 4、ESP32与模块的接线,在代码当中均有定义,请自行对照。 5、若硬件差异,请根据自身情况适当调整代码,程序仅供参考。 6、代码有注释说明,请耐心阅读。 7、技术v:349014857;

    工作汇报 年终总结2.pptx

    引言 年度工作回顾 系统进展与亮点 技术创新与应用 市场反馈与用户评价 存在问题与挑战 未来展望与计划 结束语与感谢 一、引言 简要介绍智能家居系统的重要性和发展趋势 回顾本年度的工作目标和重点 二、年度工作回顾 系统建设与维护 完成的项目与里程碑 系统稳定性与可靠性提升 团队建设与培训 团队成员构成与职责 培训与技能提升活动 合作伙伴与资源整合 与供应商、合作伙伴的合作情况 资源整合与利用 三、系统进展与亮点 功能扩展与优化 新增功能介绍与效果评估 现有功能的优化与改进 用户体验提升 界面设计与交互优化 用户反馈与改进措施 四、技术创新与应用 物联网技术的应用 传感器与通信技术的升级 大数据分析与应用 智能家居的智能化管理 自动化控制与节能策略 安全防护与预警系统 五、市场反馈与用户评价 市场反馈分析 市场需求与竞争态势 市场占有率与增长趋势 用户评价总结 用户满意度调查结果

    基于ssm+vue开发的web新闻流媒体平台源码数据库文档.zip

    基于ssm+vue开发的web新闻流媒体平台源码数据库文档.zip

    哈夫曼树与哈夫曼编码介绍.zip

    哈夫曼树与哈夫曼编码

    2024年千里明贴膏行业分析报告.pptx

    行业分析报告

    java练习题9.txt

    java练习题

    stm32c8t6超声波标准库开发 stm32c8t6超声波测距.zip

    stm32c8t6超声波标准库开发 stm32c8t6超声波测距

    学生成绩管理系统 C# 毕业设计项目用C#语言写的学生成绩管理系统, 代码有参考和学习价值, 可

    学生成绩管理系统 C# 毕业设计项目用C#语言写的学生成绩管理系统, 代码有参考和学习价值, 可用于期末项目, 以及毕业设计项目 !

    excel函数公式大全

    excel函数公式大全

    基于stm32的智能小车(遥控控制、避障、循迹)stm32f103系列单片机

    基于stm32的智能小车(遥控控制、避障、循迹)stm32f103系列单片机控制智能小车,具有三种控制方式,遥控控制、避障、循迹(内含三个工程,分别对应三种控制方式).zip

    基于Java的高校教师绩效考核系统的设计与实现【附源码】.zip

    基于Java的高校教师绩效考核系统的设计与实现【附源码】.zip

Global site tag (gtag.js) - Google Analytics