zoukankan      html  css  js  c++  java
  • 编程之美-分层遍历二叉树

    问题1:给定一颗二叉树,要求按分层遍历该二叉树,即从上到下按层访问该二叉树(每一层将单独输出一行),每一层要求访问的顺序为从左到右,并将节点依次编号,那么分层变量如图的二叉树:输出应为:

    输出:

    1
    2 3
    4 5 6
    7 8

    问题2:写另外一个函数,打印二叉树中某层次的节点(从左到右),其中根节点为第0层,函数原型为
    int printNodeAtLevel(Node* root,int level),成功返回0,失败返回0.

    仔细看2个问题,我们发现解决了第二个问题,第一个问题也迎刃而解
    要想打印第k层,我们只需要打印k-1层的孩子即可。依此递减。

    typedef struct Node{
        Node* left;
        Node* right;
        char data;
        Node(char c)
        {
            data=c;
            left=NULL;//很重要
            right=NULL;
        }
        ~Node()
        {
            delete left;//如果left为null,也没问题,delete兼容null
            delete right;
        }
    }*BiTree;
    typedef  char ElemType;
    void createBiTree(BiTree& T)
    {
        ElemType elem;
        cin>>elem;
    
        if(elem!='#')
        {
            T=new Node(elem);
    
            createBiTree(T->left);
            createBiTree(T->right);
        }
        else
            T=NULL;
    
    
    }
    
    
     
    //输出以root为根节点的第level(从0开始)的所有节点,从左到右 
    int printNodeAtLevel(Node * root,int level)
    {
        if(!root)
            return 0;
        if(level==0)
        {
            cout<<root->data<<ends;
            return 1;
        }
        return printNodeAtLevel(root->left,level-1)+printNodeAtLevel(root->right,level-1);
    }
    
    int main()
    {
        BiTree T;
        createBiTree(T);
    
        cout<<printNodeAtLevel(T,2);//输出3
        cout<<printNodeAtLevel(T,1);//输出2
        
    }
    输入:124##57##8##3#6##
    上面的是个二路递归,递归效率较低

    如果改成:

    if(level<0) return 0;
    if(level==0)

    运行有问题。


    解决了问题2,如何解决问题1?
    如果我们知道了二叉树深度为n,只需n次调用printNodeAtLevel就可以了。
    void printNodeByLevle(Node* root,int depth)
    {
    for(int level=0;level<depth;level++)
    {
    printNodeAtLevel(root,level);
    cout<<endl;
    }
    }

    如何事先不知道,当访问二叉树某一层次失败的时候返回就可以了。如下:
    void printNodeByLevle(Node* root)
    {
    for(int level=0; ;level++)
    {
    if(!printNodeAtLevel(root,level)
       break;
    cout<<endl;
    }
    }
    至此我们解决了题目中的两个问题,但细心的读者可能会发现,其实在问题1的算法中,对二叉树的每一层访问都需要重新从根节点开始,直到访问完所有的层次。这样的做法,效率实在不高,那么有没有更好的算法呢?

    从根节点出发,依次将每层的节点从左到右压入一个数组,并用一个游标Cur记录当前访问的节点,另一个游标Last指示当前层次的最后一个节点的下一个位置,以Cur==Last作为当前层次访问结束的条件,在访问某一层的同时将该层的所有节点的子节点压入数组,在访问完某一层之后,检查是否还有新的层次可以访问,直到访问完所有的层次.

    首先将根节点1压入数组,并将游标cur置为0,数组下标从0开始,游标Last置为1.
    cur<last,说明此层(第一层)尚未被访问,隐藏,依次访问Cur到Last之间的所有节点。知道Cur==Last,说明该层以及访问完,此时数组中还有未被访问到的节点,则输出换行符,并将Last定位于新一行的末尾(即数组当前最后一个元素的下一位)。
    继续依次往下访问其他层次的节点,直到访问完所有的层次。
    void printNodeByLevel2(Node* root)
    {
        if(root==NULL) return ;
        vector<Node*> vec;//这里是有STL的vector代替数组,可利用其动态扩展
        vec.push_back(root);
        int cur=0;
        int last=1;
        while(cur<vec.size())
        {
            last=vec.size();//新的一行访问开始,重新定位last与当前行最后一个节点的下一个位置
            while(cur<last)
            {
                cout<<vec[cur]->data<<" ";
                if(vec[cur]->left)
                    vec.push_back(vec[cur]->left);
                if(vec[cur]->right)
                    vec.push_back(vec[cur]->right);
    
                cur++;
            }
            cout<<endl;//当cur==last,说明该层访问结束
        }
    }

    广度优先搜索

    书中没有提及,本问题其实是以广度优先搜索(breath-first search, BFS)去遍历一个树结构。广度优先搜索的典型实现是使用队列(queue)。其伪代码如下:

    enqueue(Q, root)

    do
        node = dequeue(Q)
        process(node) //如把内容列印
        for each child of node
            enqueue(Q, child)
    while Q is not empty

    书上的解法,事实上也使用了一个队列。但本人认为,使用vector容器,较不直觉,而且其空间复杂度是O(n)。

    如果用队列去实现BFS,不处理换行,能简单翻译伪代码为C++代码:

    void PrintBFS(Node* root) {
        queue<Node*> Q;
        Q.push(root);
        do {
            Node *node = Q.front();
            Q.pop();
            cout << node->data << " ";
            if (node->pLeft)
                Q.push(node->pLeft);
            if (node->pRight)
                Q.push(node->pRight);
        }
        while (!Q.empty());
    }

    本人觉得这样的算法实现可能比较清楚,而且空间复杂度只需O(m),m为树中最多节点的层的节点数量。最坏的情况是当二叉树为完整,m = n/2。

    之后的难点在于如何换行。

    第一个尝试,利用了两个队列,一个储存本层的节点,另一个储存下层的节点。遍历本层的节点,把其子代节点排入下层队列。本层遍历完毕后,就可换行,并交换两个队列。

    void PrintNodeByLevel(Node* root) {
        deque<Node*> Q1, Q2;
        Q1.push_back(root);
        do {
            do {
                Node* node = Q1.front();
                Q1.pop_front();
                cout << node->data << " ";
                if (node->pLeft)
                    Q2.push_back(node->pLeft);
                if (node->pRight)
                    Q2.push_back(node->pRight);
            } while (!Q1.empty());
            cout << endl;
            Q1.swap(Q2); 
        } while(!Q1.empty());
    }
    View Code

    本实现使用deque而不是queue,因为deque才支持swap()操作。注意,swap()是O(1)的操作,实际上只是交换指针。

    这实现要用两个循环(书上的实现也是),并且用了两个队列。能够只用一个循环、一个队列么?

    本人的尝试之二

    换行问题其实在于如何表达一层的结束。书上采用了游标,而第一个尝试则用了两个队列。本人想到第三个可行方案,是把一个结束信号放进队列里。由于使用queue<Node*>,可以插入一个空指针去表示一层的遍历结束。

    void printNodeByLevel3(Node* root)
    {
        queue<Node*> q;
        q.push(root);
        q.push(0);
        do{
            Node* node=q.front(); q.pop();
            if(node)
            {
                cout<<node->data<<" ";
                if(node->left)
                    q.push(node->left);
                if(node->right)
                    q.push(node->right);
            }
            else if(!q.empty()) //一定要是else if
            {
                q.push(0);
                cout<<endl;
            }
        }while(!q.empty());
    }

    上面的代码很巧妙,当node为0时,该层已经结束。但是有个问题,要检测是否还有节点。

    这个实现的代码很贴近之前的PrintBFS(),也只有一个循环。注意一点,当发现空指针(结束信号)时,要检查队列内是否还有节点,如果没有的话还插入新的结束信号,则会做成死循环。

    参考:

    http://www.cnblogs.com/miloyip/archive/2010/05/12/binary_tree_traversal.html

    扩展问题:如果要求按深度从下到上访问二叉树,每层的访问顺序仍然是从左向右,如果从右向左呢?

    依然是按层遍历二叉树,只是要求从下往上访问,并且每一层中结点的访问顺序为从右向左

    分析:只要层与层之间加入哑元素(NULL),然后逆序输出队列Q即可

    第一步:给每一层之间添加哑元素NULL

    复制代码
    void PrintNodeByLevel(Node *root)
    {
    int last;
    Node *p;

    if(!root) return ;

    InitQueue();
    EnQueue(root);
    EnQueue(NULL);
    while(front < rear - 1)
    {
    last = rear - 1;
    while(front < last)
    {
    p = DeQueue();
    if(p->lChild) EnQueue(p->lChild);
    if(p->rChild) EnQueue(p->rChild);
    }
    EnQueue(NULL);
    front = last + 1;
    }
    }
    复制代码

    第二步:逆序输出队列Q

    复制代码
    for(int i = rear - 2 ; i >= 0 ; i--)
    {
    if(Q[i] == NULL)
    printf(" ");
    else
    printf("%c", Q[i]->chValue);
    }
    复制代码

    百度一道面试题

    输出二叉树第 m 层的第 k 个节点值(m, k 均从 0 开始计数)

    先看百度笔试的这道原题

    2.给定以下二叉树:
    struct node_t
    {
       node_t *left, *right;
       int value;
    };
    要求编写函数 node_t* foo(node_t *node, unsigned int m, unsigned int k);
    输出以 node 为根的二叉树第 m 层的第 k 个节点值.
    (level, k 均从 0 开始计数)
    注意:
    1)  此树不是完全二叉树;
    2)  所谓的第K个节点,是本层中从左到右的第K个节点

    不合要求的做法:递归,利用||从左往右的计算顺序以及其真值关系

    int printMK(Node* root,int m,int k,int *cnt)
    {
        if(!root || m<0)
            return 0;
        if(m==0)
        {
            if(*cnt==k)
            {
                cout<<root->data<<endl;
                return 1;
            }
            *cnt+=1;
            return 0;
        }
        return printMK(root->left,m-1,k,cnt) || printMK(root->right,m-1,k,cnt);
    }

    int *cnt=new int(0);
    printMK(T,2,1,cnt);

    解法:

    void printMK2(BiTree T,int m,int k)
    {
        int num=0;
         int level=0;
        queue<Node*> q;
        q.push(T);
        q.push(0);
        do{
            Node* node=q.front(); q.pop();
            
            if(node)
            {
                num++; 
                if(level==m&& num-1==k) { cout<<node->data<<" "; break;}
                if(node->left)
                    q.push(node->left);
                if(node->right)
                    q.push(node->right);
            }
            else if(!q.empty())
            {
                level++;
                num=0;
                q.push(0);
                cout<<endl;
            }
        }while(!q.empty());
    }

    参考:http://www.cppblog.com/flyinghearts/archive/2010/08/16/123548.html

    关于这个题目leetcode有一题很相似,解法更好:
    LeetCode Problem: Populating Next Right Pointers in Each Node, Level traversal of binary tree
    http://blog.unieagle.net/2012/12/24/leetcode-problem-populating-next-right-pointers-in-each-node-level-traversal-of-binary-tree/

  • 相关阅读:
    【LeetCode】17. Letter Combinations of a Phone Number
    【LeetCode】16. 3Sum Closest
    【LeetCode】15. 3Sum 三个数和为0
    【LeetCode】14. Longest Common Prefix 最长前缀子串
    【LeetCode】13. Roman to Integer 罗马数字转整数
    【LeetCode】12. Integer to Roman 整型数转罗马数
    【LeetCode】11. Container With Most Water
    【LeetCode】10. Regular Expression Matching
    Models of good programmer
    RSA Algorithm
  • 原文地址:https://www.cnblogs.com/youxin/p/3288281.html
Copyright © 2011-2022 走看看