二叉树的遍历

前中后序的递归实现

    void ff(TreeNode* p,vector<int> &v){
        if(p==nullptr)
            return;
        v.push_back(p->val);
        ff(p->left,v);
        ff(p->right,v);
    }
    void mf(TreeNode* p,vector<int> &v){
        if(p==nullptr)
            return;
        mf(p->left,v);
        v.push_back(p->val);
        mf(p->right,v);
    }
    void bf(TreeNode* p,vector<int> &v){
        if(p==nullptr)
            return;
        bf(p->left,v);
        bf(p->right,v);
        v.push_back(p->val);
    }

前中后序的非递归标准实现

    void ff(TreeNode* root,vector<int>& v){
        TreeNode* p=root;
        stack<TreeNode*> s;
        while(!s.empty() || p){
            while(p){
                v.push_back(p->val);//Visit
                s.push(p);
                p=p->left;
            }
            p=s.top();
            s.pop();
            p=p->right;
        }
    }
    void mf(TreeNode* root,vector<int>& v){
        TreeNode* p=root;
        stack<TreeNode*> s;
        while(!s.empty() || p){
            while(p){
                s.push(p);
                p=p->left;
            }
            p=s.top();
            v.push_back(p->val);//Visit
            s.pop();
            p=p->right;
        }
    }
    void bf(TreeNode* root,vector<int>& v){
        TreeNode* p=root,*plast=nullptr;
        stack<TreeNode*> s;
        while(!s.empty() || p){
            while(p){
                s.push(p);
                p=p->left;
            }
            p=s.top();
            if(!p->right || plast==p->right){
                v.push_back(p->val);//Visit
                s.pop();
                plast=p;
                p=nullptr;//既然没有右子树或者右子树已经被访问,那么p就没有用了,应该让它失效,这样才会从栈顶,弹出根节点
            }else
                p=p->right;
            
        }
    }

总结

整体的思路是这样的:

  • 指针p指向root,创建栈
  • 当栈不为空或p有效时,循环:
    1. 沿着根节点的左分支一路压入根节点(如果是前序遍历此时就可以访问p)
    2. 循环结束时p为空,此时p失效。
    3. 当p失效时,从栈顶弹出结点(如果是中序遍历此时就可以访问p)。此时弹出的一定是底层的结点。
    4. 对该结点进行访问,并尝试访问其右分支。
    5. 右分支的结点视作根节点,重复以上过程。

当后序遍历时,预先设置plast为空,以记录上一次访问的结点。
取栈顶元素作p,

  • 当p无右分支或p的右分支已被访问时
    1. 访问p
    2. 弹出栈顶
    3. 更新plast
    4. 设置p失效(p失效之后才会从栈顶弹元素)
  • 否则(当p有右分支且右分支没有被访问)
    p=p->right 右分支作根节点进入循环进行访问

简单来说,p相当于当前操作的子树;当p失效时,弹出栈顶元素,即使当前操作的子树的根。左边处理完了,就索引根,通过根来索引右子树。

层遍历与前序遍历的非标准实现

前序遍历的非标准实现

流程:

  • 判断root是否为空
  • 指针p指向root
  • 创建栈并压入p
  • 当栈不为空时:循环
    1. 弹出栈顶元素进行访问
    2. 如果该元素有右分支,压入右孩子
    3. 如果该元素有左分支,压入左孩子
    void ff2(TreeNode* root,vector<int>& v){
        if(!root)
            return;
        TreeNode* p=root;
        stack<TreeNode*> s;
        s.push(p);
        while(!s.empty()){
            p=s.top();
            s.pop();
            v.push_back(p->val);
            if(p->right)
                s.push(p->right);
            if(p->left)
                s.push(p->left);
        }
    }

层遍历

流程:

  • 判断root是否为空
  • 指针p指向root
  • 创建队列并压入p
  • 当队列不为空时:循环
    1. 弹出队首元素进行访问
    2. 如果该元素有左分支,压入左孩子
    3. 如果该元素有右分支,压入右孩子
    void layer(TreeNode* root,vector<int> &v){
        if(!root)
            return;
        TreeNode* p=root;
        queue<TreeNode*> q;
        q.push(p);
        
        while(!q.empty()){
            p=q.front();
            v.push_back(p->val);
            q.pop();
            if(p->left)
                q.push(p->left);
            if(p->right)
                q.push(p->right);
        }
    }

扩展:层打印

按层打印到一个二维数组中。
该问题的难点在于如何判断当前访问的结点是不是某一行的行末。所以我们使用nlast指针,来记录当前行的行末;用last指针来记录最近压入队列的结点。

  • 初始时last和nlast都为root
  • 循环时,last记录最近压入的结点
  • 当前访问的结点等于nlast时,表示已经访问到了某一行的行末。这个时候做行末相关的处理,并将nlast更新为last。因为当某一行访问完成后,新压入的结点,一定是下一行的行尾。
    void layer(TreeNode* root,vector<vector<int> > &v){
        if(!root)
            return;
        TreeNode* p=root;
        queue<TreeNode*> q;
        q.push(p);
        TreeNode* last=root,*nlast=root; //last和nlast初始化为root
        vector<int> temp;
        while(!q.empty()){
            p=q.front();
            q.pop();
            temp.push_back(p->val);
            if(p->left){
                q.push(p->left);
                last=p->left;  //记录last
            }
            if(p->right){
                q.push(p->right);
                last=p->right;//记录last
            }
            if(p==nlast){//如果p指针已经到达了一行的行末,那么最新压入栈的last一定是下一行的行末
                v.push_back(temp);
                temp.clear();
                nlast=last;
            }
        }
    }

扩展:判满、完全二叉树

判断完全二叉树

思路:按层遍历二叉树,直到发现第一个非满结点(单左或叶子结点,不可以是单右)。在这个结点之后,所有的结点都是叶子结点。那么该树就是完全二叉树。

    bool chk(TreeNode* root) {
        //按层遍历,找到第一个不满结点,其后所有结点必须是叶子
        if(!root)
            return false;
        TreeNode *p=root;
        queue<TreeNode*> que;
        que.push(p);
        int find=0;
        while(!que.empty()){
            p=que.front();
            que.pop();
            if(p->left){
                que.push(p->left);
            }
            if(p->right){
                que.push(p->right);
            }
            if(!p->right){//没有右树,就涵盖了叶子和单左 两种情况
                find=1;
                continue;
            }else{//如果有右树,看看是叶子,还是单右。单右直接返回false
                if(!p->left)
                    return false;
            }
            if(find==1 && (p->left || p->right)){//注意优先级,&&高于||
                return false;
            }
        }
        return true;
    }

判断满二叉树

思路:按层遍历二叉树,直至发现第一个非满结点(且必须是叶子结点)。在该结点之后的所有结点都是叶子结点,且该结点前一个节点是某行的行末。
实现:用一个plast指针来记录上次访问的结点。其他按照判完全二叉树的方法来进行即可。代码未经过验证就不写了。另外,nlast更新时可以按照nlast=nlast->right来更新,这样就不用last指针了。

扩展:二叉树的规则化构建

根节点的值是1,其左右孩子分别是1和2,每个孩子的左右孩子依然是1和2。以此来构建N层的二叉树。
思路:首先需要保证N大于0.建立总根节点root,然后初始化p和nlast为root。然后共遍历n-2层,每一层遍历时都把结点添加左右孩子。比如n==3时,应该遍历中间的一层就好。

    TreeNode* buildBt(int n){
        if(!n)
            return nullptr;
        TreeNode* root=new TreeNode(1);
        TreeNode *p=root,*nlast=root;
        queue<TreeNode*> que;
        que.push(p);
        ----n;
        while(n){
            p=que.front();
            que.pop();
            p->left=new TreeNode(1);
            p->right=new TreeNode(2);
            que.push(p->left);
            que.push(p->right);
            if(p==nlast->right){//如果发现遍历至某一行尾,则更新n和nlast
                --n;
                nlast=p;
                if(n<=0)
                    break;
            }
        }
        return root;
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容

  • -先序遍历: 访问根结点,先序遍历其左子树,先序遍历其右子树;运用到递归void PreOrderTraversa...
    Spicy_Crayfish阅读 2,021评论 0 0
  • 二叉树的三种常用遍历方式 学习过数据结构的同学都清楚,除了层序遍历外,二叉树主要有三种遍历方式: 1. 先序遍历...
    SherlockBlaze阅读 1,222评论 0 4
  • 树(tree)是一种抽象数据类型(ADT)或是实作这种抽象数据类型的数据结构,用来模拟具有树状结构性质的数据集合。...
    曾大稳丶阅读 995评论 0 1
  • 数据结构和算法--二叉树的实现 几种二叉树 1、二叉树 和普通的树相比,二叉树有如下特点: 每个结点最多只有两棵子...
    sunhaiyu阅读 6,433评论 0 14
  • 现在决定把自己很久以前的一些文章重新markdown一下,发到简书来,先从这篇二叉树的遍历说起的。大家都知道二叉树...
    董成鹏阅读 364评论 0 1