一个很容易引发血案的数据结构 - 经典二叉树问题
下面是一些二叉树的经典算法,写一写就当复习了。其中的二叉树递归、非递归遍历,由遍历结果构造二叉树等,都是最基础的东西,而二叉树转链表,路径求和等是在leetcode上遇到,则是希望和不小心点错了才进来的朋友们分享分享。二叉树能玩的远不止这些,还有太多代码没有整理到一起,以后再贴。
0 二叉树节点的定义
下面定义的节点是不带parent指针的,因为迄今为止我遇见的树,还都是没有parent指针的,除了红黑树。即使有parent有时会方便一些(比如path sum),但是没有它也可以想办法。
template <class T> struct tnode { T val; tnode * l; tnode * r; tnode(T _v, tnode * _l=0, tnode * _r=0):val(_v), l(_l), r(_r){} };
1 递归遍历
template<class T> void inorderTraversal(tnode<T> * r) { if(!r) return; inorderTraversal(r->l); cout <<r->val<<ends; inorderTraversal(r->r); } template<class T> void postorderTraversal(tnode<T> * r) { if(!r) return; postorderTraversal(r->l); postorderTraversal(r->r); cout <<r->val<<ends; } template<class T> void preorderTraversal(tnode<T> * r) { if(!r) return; cout <<r->val<<ends; preorderTraversal(r->l); preorderTraversal(r->r); }
2 删除一个二叉树最简单的办法当然也是递归了。写上下面这个函数是为了提醒自己,new了一定要记得delete。
template<class T> void erase(tnode<T> * r) { if(!r) return; erase(r->l); erase(r->r); delete r; }
3 非递归遍历
简单的记忆方法:
先序:访问、压右指针、左路下降、遇空弹栈 (但注意下面代码中初始栈中的监视哨)。
中序:左路下降并一路压栈、遇空弹栈往右走
后序:一个栈记录指针,一个栈记录即将访问的是该指针的左子树还是右子树;左路下降并压栈,遇空弹栈,往右边走,走完右边再访问父节点。
template<class T> void preorderTraversal_stack(tnode<T> * r) { stack<tnode<T> *> s; tnode<T> * p = r; s.push(0); while(p) { cout <<p->val<<ends; if(p->r) s.push(p->r); if(p->l) p = p->l; else { p = s.top(); s.pop(); } } } template<class T> void inorderTraversal_stack(tnode<T> * r) { stack<tnode<T> *> stk; tnode<T> * p = r, * q; while(stk.size() || p) { if(p) { stk.push(p); p = p->l; } else { p = stk.top(); stk.pop(); cout <<p->val<<ends; p = p->r; } } } template<class T> void postorderTraversal_stack(tnode<T> * r) { stack<tnode<T> *> s; stack<char> f; tnode<T> * p = r; while(s.size() || p) { if(p) { s.push(p); f.push(0); p = p->l; } else { p = s.top(); if(f.top() == 0) { f.top() = 1; p = p->r; } else { cout <<p->val<<ends; s.pop(); f.pop(); p=0; } } } }
4 二叉树变成单链表(先序顺序),right指向下一个节点,left指向空。
记得有道题是将二叉树变成有序的双向链表,注意此题与之不同。
void flatten(tnode<int> *r) { stack<tnode<int> *> s; s.push(0); tnode<int> *p = r; while(r) { if(r != p) { p->l = 0; p = p->r = r; } if(r->r) s.push(r->r); if(r->l) r = r->l; else { r = s.top(); s.pop(); } } }
5 二叉树转双链表(中序)。如果是二叉搜索树,按中序转换成的双链表当然直接就是有序的了。
简单写了个递归版本,把左子树变双链表,把右子树变双链表,然后“通通连起来”~
template <class T> tnode<T> * tree2list(tnode<T> * root) { if(root == 0) return 0; if(root->l==0) return root; tnode<T> * l = tree2list(root->l); tnode<T> * r = tree2list(root->r); if(l) { while(l->r) l = l->r; root->l = l; l->r = root; } if(r) { root->r = r; r->l = root; } while(root->l) root = root->l; return root; }
6,已知中序遍历和先序遍历,构造出二叉树(未考虑有重复值的情况)。
下面代码用的是递归方法,传值都用引用了,是为了调用的效率能快一些。当然其实可以用iterator代替下标from和to了。
此外虽然在http://www.cnblogs.com/zhchngzng/articles/4097798.html中我猛喷了vector,但有些题是在leetcode上做的,为了submit方便所以只好跟着用。
int find(vector<int> & v, int from, int to, int target) { for(; from<=to; from++) if(v[from] == target) return from; return -1; } tnode<int> *buildTreeFromPreAndIn(vector<int> &preorder, int prefrom, int preto, vector<int> &inorder, int infrom, int into) { if(preto < prefrom || into < infrom || prefrom < 0 || infrom < 0 || preto >= preorder.size() || into >= inorder.size()) return 0; tnode<int> * r = new tnode<int>(preorder[prefrom]); int it = find(inorder, infrom, into, r->val); if(it == -1) return 0; tnode<int> * rl = buildTreeFromPreAndIn(preorder, prefrom+1, prefrom+(it-infrom), inorder, infrom, it-1); tnode<int> * rr = buildTreeFromPreAndIn(preorder, prefrom+(it-infrom)+1, preto, inorder, it+1, into); r->r = rr; r->l = rl; return r; } tnode<int> *buildTreeFromPreAndIn(vector<int> &preorder, vector<int> &inorder) { if(!preorder.size() || !inorder.size() || preorder.size() != inorder.size()) return 0; return buildTreeFromPreAndIn(preorder, 0, preorder.size()-1, inorder, 0, inorder.size()-1); }
7,已知中序遍历和后序遍历,构造二叉树(未考虑有重复值的情况)
tnode<int> *buildTreeFromInAndPost(vector<int> &inorder, int infrom, int into, vector<int> &postorder, int postfrom, int postto) { if(into < infrom || postto < postfrom || infrom < 0 || postfrom < 0 || into >= inorder.size() || postto >= postorder.size()) return 0; tnode<int> * r = new tnode<int>(postorder[postto]); int it = find(inorder, infrom, into, r->val); if(it == -1) return 0; tnode<int> * rl = buildTreeFromInAndPost(inorder, infrom, it-1, postorder, postfrom, postfrom+(it-infrom)-1); tnode<int> * rr = buildTreeFromInAndPost(inorder, it+1, into, postorder, postfrom+(it-infrom), postto-1); r->r = rr; r->l = rl; return r; } tnode<int> *buildTreeFromInAndPost(vector<int> &preorder, vector<int> &inorder) { if(!preorder.size() || !inorder.size() || preorder.size() != inorder.size()) return 0; return buildTreeFromInAndPost(preorder, 0, preorder.size()-1, inorder, 0, inorder.size()-1); }
8,计算每条完全路径的加和
因为是求加和,我干脆用traits限定只能用于T为常规变量的二叉树。想要用path这个东西来做别的事,比如计算Max sum within the path什么的只要稍微改改就好啦。这不是重点,重点是下面:
template <class T> vector<vector<T>> pathSum(tnode<T> * p, const T & target) { vector<vector<T>> ret; if(is_pod<T>::value == 0) return ret; vector<tnode<T>*> tr_ptr; vector<T> tr_val; T sum = 0; stack<tnode<T> *> s; s.push(0); while(p) { tr_ptr.push_back(p); sum += p->val; tr_val.push_back(p->val); if(p->r) s.push(p); if(p->l) p = p->l; else { if(!p->r && sum == target) { ret.push_back(tr_val); } p = s.top(); if(!p) break; s.pop(); while(tr_ptr.back()!=p) { sum -= tr_val.back(); tr_val.pop_back(); tr_ptr.pop_back(); } p = p->r; } } return ret; }
9 以上代码的简单测试
main() { ios::sync_with_stdio(0); tnode<int> * root; root = new tnode<int> (1, new tnode<int> (2, new tnode<int> (4, 0, new tnode<int> (8)), new tnode<int> (5, new tnode<int> (7), 0)), new tnode<int> (3, 0, new tnode<int> (6))); /* vector<vector<int>> ret = pathSum(root, 15); for(int i=0; i<ret.size(); i++) { for(int j=0; j<ret[i].size(); j++) cout <<ret[i][j]<<" "; cout <<endl; } erase(root); // */ /* postorderTraversal_stack(root); cout <<endl; flatten(root); preorderTraversal_stack(root); cout <<endl; erase(root); // */ /* int n = 8; int pre[] = {1,2,4,8,5,7,3,6}; int in[] = {4,8,2,7,5,1,3,6}; int post[]= {8,4,7,5,2,6,3,1}; vector<int> preorder(pre, pre+n); vector<int> inorder(in, in+n); vector<int> postorder(post, post+n); root = buildTreeFromPreAndIn(preorder, inorder); preorderTraversal(root); cout <<endl; erase(root); root = buildTreeFromInAndPost(inorder, postorder); preorderTraversal(root); cout <<endl; erase(root); // */ }
10 Binary Search Tree Iterator: (nonrecursive inorder travesal)
1 class BSTIterator { 2 public: 3 stack<TreeNode*> s; 4 void pushn(TreeNode * p) 5 { 6 for(; p; p=p->left) 7 s.push(p); 8 } 9 10 BSTIterator(TreeNode *root) { 11 pushn(root); 12 } 13 14 /** @return whether we have a next smallest number */ 15 bool hasNext() { 16 return s.size(); 17 } 18 19 /** @return the next smallest number */ 20 int next() { 21 TreeNode * p = s.top(); 22 int nVal = p->val; 23 s.pop(); 24 pushn(p->right); 25 return nVal; 26 } 27 };
二叉树能干的事情远不止这些,未完待续。