C++算法1-4 非递归遍历树的一点研究(习题5.82,5.83)
2014-11-20 20:11
211 查看
第五章 递归与树 提到了非递归方法遍历树。作者@Robert Sedgewick把代码写的非常简洁,但是可惜只给出了前序遍历的代码。中序和后序则做成了习题。遗憾的是网上没能找到这本书的习题答案。而网上能找到的非递归算法代码大多是打着C++的名义的C语言代码,与作者的思想也是大相径庭(也许本质是一样的,只是我没看太懂)。无奈之下本人对作者在书中对非递归算法思想的描述(见下图)进行了一番推敲。并写出了一个不太那么像C的C++代码。经过简单测试,可以得到正确结果,但是很显然是不成熟的。
在中序遍历中,我的理解是第一次读到栈顶节点时并不访问它(这里的访问就是输出它的值)而是按照其右孩子,自身,左孩子的顺序压栈,当再次遇到该元素时才访问。这里考虑到当某个节点没有孩子时就立即访问,所以当压入其自身时,我的做法不是压入其自身,而是压入一个new出来的与该节点的值相等但是没有孩子的新节点。这样第二次弹出的不是节点自己而是这个new出来的新节点。显然这种方法会导致内存泄漏。
后序遍历也遇到了相同的问题,弹出一个节点,如果这个节点是第一次被弹出,则按照自身,右孩子,左孩子的顺序压栈,当第二次弹出自己时,则说明其左右子树上的所有节点均已访问完毕,这一步就是要访问自己了。这里没再使用上述new一个新节点的方法,而是直接修改node类,给它添加了一个布尔值属性,作为一个flag。对每个栈顶节点判断一下,如果是第二次,就直接访问。
这两种方法我都不喜欢。
代码基于对图中三个图的理解,我对画圈的理解是该节点的左右孩子尚未压栈。
这是作者的前序遍历非递归算法。下面我自己的中序和后序代码均是试图向作者的这种思想看齐。
下面是我的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; }
相关文章推荐
- 个人对kobject的一点研究
- EasyUI的一点使用与研究(弹出框的居中与刷新)
- json的一点小研究
- c++ 递归问题 研究
- 算法导论 习题10.4-5 二叉树的遍历(非递归,O(1)存储)
- Amf3+socket开发网络游戏或应用的一点研究心得
- 由backBarButtonItem引起的navigationItem的一点研究
- Linux驱动学习3(个人对kobject的一点研究)
- 函数调用和尾递归的一点认识
- 递归的一点思考
- 关于JQueryMobile中Slider的一点研究 博客分类:
- 全排列_蓝桥杯问题+一点对递归的看法
- 非递归遍历树
- java中位运算的一点研究
- 做C Programming Language的习题,突发奇想研究如何消除任意多空格或\t,并把格式转换为每个单词占一行
- 算法导论 习题10.4-5 二叉树的遍历(非递归,O(1)存储)
- String和StringBuffer的一点研究
- oracle控制文件的一点研究
- 简单的递归遍历树
- PageMethods方法的一点研究