C++算法1-4 非递归遍历树的一点研究(习题5.82,5.83)

第五章 递归与树 提到了非递归方法遍历树。作者@Robert Sedgewick把代码写的非常简洁,但是可惜只给出了前序遍历的代码。中序和后序则做成了习题。遗憾的是网上没能找到这本书的习题答案。而网上能找到的非递归算法代码大多是打着C++的名义的C语言代码,与作者的思想也是大相径庭(也许本质是一样的,只是我没看太懂)。无奈之下本人对作者在书中对非递归算法思想的描述(见下图)进行了一番推敲。并写出了一个不太那么像C的C++代码。经过简单测试,可以得到正确结果,但是很显然是不成熟的。

在中序遍历中,我的理解是第一次读到栈顶节点时并不访问它(这里的访问就是输出它的值)而是按照其右孩子,自身,左孩子的顺序压栈,当再次遇到该元素时才访问。这里考虑到当某个节点没有孩子时就立即访问,所以当压入其自身时,我的做法不是压入其自身,而是压入一个new出来的与该节点的值相等但是没有孩子的新节点。这样第二次弹出的不是节点自己而是这个new出来的新节点。显然这种方法会导致内存泄漏。

后序遍历也遇到了相同的问题,弹出一个节点,如果这个节点是第一次被弹出,则按照自身,右孩子,左孩子的顺序压栈,当第二次弹出自己时,则说明其左右子树上的所有节点均已访问完毕,这一步就是要访问自己了。这里没再使用上述new一个新节点的方法,而是直接修改node类,给它添加了一个布尔值属性,作为一个flag。对每个栈顶节点判断一下,如果是第二次,就直接访问。

这两种方法我都不喜欢。

代码基于对图中三个图的理解,我对画圈的理解是该节点的左右孩子尚未压栈。

这是作者的前序遍历非递归算法。下面我自己的中序和后序代码均是试图向作者的这种思想看齐。




下面是我的c++代码:

#include <iostream>
#include <stack>
#include <queue>

class node
{
public :
    node (int v) : val(v), l(nullptr), r(nullptr), childrenAlreadyInStack(false) {}

    void addLeft(node *lc){l = lc;}
    void addRight(node *rc){ r = rc;}
    int getVal() {return val;}
    node* getl(){return l;}
    node* getr(){return r;}

    bool childrenAlreadyInStack;
//    void setFlag(bool b){childrenAlreadyInStack = b;}
//    bool getFlag() {return childrenAlreadyInStack;}
private:
    int val;
    node *l, *r;
//    bool childrenAlreadyInStack;
};

void visit(node * n)
{
    std::cout << static_cast<char>(n->getVal() )<< std::endl;
}

void preorder_walk(node *r, void visit(node*))
{
    if (r == nullptr)
        return ;
    visit(r);
    preorder_walk(r->getl(), visit);
    preorder_walk(r->getr(), visit);
}

void inorder_walk(node* r, void visit(node*))
{
    if (r == nullptr)
        return;
    inorder_walk(r->getl(), visit);
    visit(r);
    inorder_walk(r->getr(), visit);
}

void postorder_walk(node* r, void visit(node*))
{
    if (r == nullptr)
        return ;
    postorder_walk(r->getl(), visit);
    postorder_walk(r->getr(), visit);
    visit(r);
}

//栈顶元素出栈,然后依次压入其右孩子,左孩子
void preorder_nonrecurse(node *r, void visit(node *))
{
    std::stack<node*> nodeStack;
    nodeStack.push(r);
    while (!nodeStack.empty())
    {
        node * n = nodeStack.top();     nodeStack.pop();
        visit(n);
        if (n->getr() != nullptr) nodeStack.push(n->getr());
        if (n->getl() != nullptr) nodeStack.push(n->getl());
    }
}

//栈顶元素出栈后(出栈但并未访问),依次压入其右孩子,自身,左孩子。
//也即第一次遇到一个元素时,压入其右孩子,自身,左孩子,第二次遇到该元素时,说明已访问完毕其左子树。
void inorder_nonrecurse(node* r, void visit(node*))
{
    std::stack<node*> nodeStack;
    nodeStack.push(r);
    while (!nodeStack.empty())
    {
        node* n = nodeStack.top();      nodeStack.pop();    //出栈,但并未访问。
        if (n->getr() != nullptr) nodeStack.push(n->getr());
                                                    //再次压入自身时实际压入的是一个元素值相同但左右孩子均为空的新节点。
                                                    //这样当该节点再次出栈时就不会重复压入其左右孩子,而是直接访问。问题是内存会泄漏。
        if (n->getl() != nullptr || n->getr() != nullptr)   nodeStack.push(new node(n->getVal()));
        else    visit(n);

        if (n->getl() != nullptr) nodeStack.push(n->getl());
    }
}

//压入右孩子,左孩子。为避免上面提到的重复压入孩子和会带来内存泄漏的问题,这里直接修改了node,给node添加了一个bool值
//对于栈顶元素,如果该元素bool值为true,即说明已经是第二次遇到它,也即它的左右孩子已经入栈(且已经访问完毕),这次就是该访问它了。
void postorder_nonreverse(node* r, void visit(node*))
{
    std::stack<node*> nodeStack;
    nodeStack.push(r);
    while (!nodeStack.empty())
    {
        node * n = nodeStack.top();

        if (n->childrenAlreadyInStack)
           { visit(n); nodeStack.pop(); continue;}

        if (n->getr() != nullptr) nodeStack.push(n->getr());
        if (n->getl() != nullptr) nodeStack.push(n->getl());
        n->childrenAlreadyInStack = true;
    }
}

//按照层次顺序遍历与非递归的前序遍历非常相似。唯一的不同是将stack改为queue。
void levelByLevel_walk(node* r, void visit(node*))
{
    std::queue<node*> nodeQueue;
    nodeQueue.push(r);
    while (!nodeQueue.empty())
    {
        node *n = nodeQueue.front();    nodeQueue.pop();
        visit(n);
        if (n->getl() != nullptr) nodeQueue.push(n->getl());
        if (n->getr() != nullptr) nodeQueue.push(n->getr());
    }
}

int main()
{
    //构建图中的树
    node root('E'), nD('D'), nB('B'), nA('A'),
            nC('C'), nF('F'), nG('G'), nH('H');
    root.addLeft(&nD);
    root.addRight(&nH);
    nD.addLeft(&nB);
    nB.addLeft(&nA);
    nB.addRight(&nC);
    nH.addLeft(&nF);
    nF.addRight(&nG);

    std::cout <<"preorder: " << std::endl;
    preorder_walk(&root, visit);
    std::cout << "preorder without recurse: " << std::endl;
    preorder_nonrecurse(&root, visit);

    std::cout <<"inorder: " << std::endl;
    inorder_walk(&root, visit);

    std::cout << "inorder without recurse:  " << std::endl;
    inorder_nonrecurse(&root, visit);

    std::cout << "postorder: " << std::endl;
    postorder_walk(&root, visit);

    std::cout << "postorder without recurse: " << std::endl;
    postorder_nonreverse(&root, visit);

    std::cout << "层次遍历: " << std::endl;
    levelByLevel_walk(&root, visit);

    std::cout << std::endl << "done. press any key to exit.";
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值