二叉树的前序,中序,后序遍历
1. 二叉树中很多的操作都可以从对二叉树的遍历入手,在遍历的基础上对树中节点进行相关的操作或做相关的信息统计。本文主要讨论二叉树的三种遍历算法。
2. 定义二叉树的结构体如下:
1 struct Node 2 { 3 int m_data; 4 Node* m_lChild; 5 Node* m_rChild; 6 Node(int data=0,Node* lChild=NULL,Node* rChild=NULL) 7 :m_data(data),m_lChild(lChild),m_rChild(rChild){} 8 };
3. 递归遍历代码很容易写,如下:
1 //递归前序遍历 2 void PreOrderTra(Node* pRoot) 3 { 4 if (pRoot) 5 { 6 cout<<pRoot->m_data<<" "; 7 PreOrderTra(pRoot->m_lChild); 8 PreOrderTra(pRoot->m_rChild); 9 } 10 } 11 12 //递归中序遍历 13 void InOrderTra(Node* pRoot) 14 { 15 if (pRoot) 16 { 17 InOrderTra(pRoot->m_lChild); 18 cout<<pRoot->m_data<<" "; 19 InOrderTra(pRoot->m_rChild); 20 } 21 } 22 23 //递归后序遍历 24 void PostOrderTra(Node* pRoot) 25 { 26 if (pRoot) 27 { 28 PostOrderTra(pRoot->m_lChild); 29 PostOrderTra(pRoot->m_rChild); 30 cout<<pRoot->m_data<<" "; 31 } 32 }
4. 递归算法代码简洁,容易理解。但是递归自身存在效率问题及栈溢出问题。下面分别给出三种算法的非递归模式。
5. 前序遍历的非递归算法。这是三种遍历算法中最容易写的一个算法,代码如下:
1 //非递归前序遍历算法 2 void NRPreOrderTra(Node* pRoot) 3 { 4 if (!pRoot) 5 { 6 return ; 7 } 8 stack<Node*> s; 9 s.push(pRoot); 10 while (!s.empty()) 11 { 12 Node* tmp=s.top(); 13 s.pop(); 14 cout<<tmp->m_data<<" "; 15 if (tmp->m_rChild) 16 { 17 s.push(tmp->m_rChild); 18 } 19 if (tmp->m_lChild) 20 { 21 s.push(tmp->m_lChild); 22 } 23 } 24 }
分析:定义一个栈stack s模拟递归中的栈,每次先访问根节点,然后将右子节点压栈,最后将左子节点压栈。这样出栈时左子节点先出栈,右子节点后出栈。循环结束的条件就是栈为空,即所有节点都已经访问过。
6. 中序遍历的非递归算法。方法一:需要修改树节点的数据结构,在结构中加入visited标志位。中序遍历的过程:第一次访节点的时候,需要将节点的左子节点压栈,并将当前节点访问标志位置为true,第二次访问节点时,说明节点左子树已经访问完毕,此时打印节点信息,并将节点弹出栈,然后将节点的右子树压栈。代码如下:
1 struct Node 2 { 3 int m_data; 4 Node* m_lChild; 5 Node* m_rChild; 6 bool visited; 7 Node(int data=0,Node* lChild=NULL,Node* rChild=NULL) 8 :m_data(data),m_lChild(lChild),m_rChild(rChild),visited(false){} 9 }; 10 11 void NRInOrderTra(Node* pRoot) 12 { 13 if (!pRoot) 14 { 15 return ; 16 } 17 stack<Node*> s; 18 s.push(pRoot); 19 while (!s.empty()) 20 { 21 Node* tmp=s.top(); 22 if (tmp!=NULL) 23 { 24 if (!tmp->visited) 25 { 26 s.push(tmp->m_lChild); 27 } 28 else 29 { 30 s.pop(); 31 cout<<tmp->m_data<<" "; 32 s.push(tmp->m_rChild); 33 } 34 } 35 else 36 { 37 s.pop(); 38 if (!s.empty()) 39 { 40 s.top()->visited=true; 41 } 42 } 43 } 44 }
分析:在上述代码中为了减少对节点左右子树是否为空的判断,将NULL空子树也压入栈,容易想到栈中绝不会出现相邻元素都是NULL的情况。在弹栈的时候对是否为NULL进行判断,如果为NULL,则直接出栈,并将栈顶元素的visited位置为true。这种方法比较简单,但是缺点是需要需改原有的数据结构。
方法二:不需要修改数据结构。使用一个current指针,最初current指针值为root。访问它的左节点,并将左节点压栈,直到到达NULL节点,说明已经到达叶子节点。然后弹出栈顶元素,将current置为栈顶元素,输出current的值,然后将current置为current的右子节点。如此反复进行。
1 void NRInOrderTra(Node* pRoot) 2 { 3 if (!pRoot) 4 { 5 return ; 6 } 7 stack<Node*> s; 8 Node* current=pRoot; 9 while (!s.empty() || current) 10 { 11 if(current) 12 { 13 s.push(current); 14 current=current->m_lChild; 15 } 16 else 17 { 18 current=s.top(); 19 s.pop(); 20 cout<<current->m_data<<" "; 21 current=current->m_rChild; 22 } 23 } 24 }
注意:上述算法关键是current每一步的赋值问题。
7. 非递归后序遍历。方法一:通过数据结构中添加visited标志位来实现。主要思想:第一次访问节点时,将左节点压入堆栈;第二次访问节点时,说明左节点已经访问完,将右节点压入堆栈;第三次访问节点时,说明左右节点都已经访问完成,输出这个节点的值,从栈中弹出这个节点。算法终止条件:栈为空。
方法二:由于没有指向父节点的指针,所以只能使用栈来实现。我们使用一个prev指针来记录上一次访问的节点,curr为栈顶节点即当前访问的节点。
如果pre是curr的parent,那么向下遍历树。这种情况下就访问curr的左节点,如果左节点不为空,将左节点压栈;else如果右节点不为空,将右节点压栈。如果左右节点都为空,打印curr的值,将它弹出栈。
如果pre是curr的左节点,我们正在从左子树向上访问树。此时如果curr的右子树不为空,那么将右子节点压栈;如果右子树为空,那么输出curr的值,并将curr弹出栈。
如果pre是curr的右节点,那么输出curr的值,并将curr弹出栈。
代码如下:
1 void NRPostOrderTra(Node* pRoot) 2 { 3 if (!pRoot) 4 { 5 return ; 6 } 7 stack<Node*> s; 8 s.push(pRoot); 9 Node* prev=NULL; 10 while (!s.empty()) 11 { 12 Node* curr=s.top(); 13 //从上向下访问树 14 if (!prev || prev->m_lChild==curr || prev->m_rChild==curr) 15 { 16 if (curr->m_lChild) 17 { 18 s.push(curr->m_lChild); 19 } 20 else if (curr->m_rChild) 21 { 22 s.push(curr->m_rChild); 23 } 24 else 25 { 26 cout<<curr->m_data<<" "; 27 s.pop(); 28 } 29 } 30 //从左子树向上访问树 31 else if (curr->m_lChild==prev) 32 { 33 if (curr->m_rChild) 34 { 35 s.push(curr->m_rChild); 36 } 37 else 38 { 39 cout<<curr->m_data<<" "; 40 s.pop(); 41 } 42 } 43 //从右子树向上访问树 44 else if (curr->m_rChild==prev) 45 { 46 cout<<curr->m_data<<" "; 47 s.pop(); 48 } 49 prev=curr; 50 } 51 }
上述代码虽然逻辑上很清晰,但是存在很多冗余代码,即cout<<curr->m_data<<" "; s.pop();出现多次,代码可以作如下精简:
1 void NRPostOrderTra(Node* pRoot) 2 { 3 if (!pRoot) 4 { 5 return ; 6 } 7 stack<Node*> s; 8 s.push(pRoot); 9 Node* prev=NULL; 10 while (!s.empty()) 11 { 12 Node* curr=s.top(); 13 //从上向下访问树 14 if (!prev || prev->m_lChild==curr || prev->m_rChild==curr) 15 { 16 if (curr->m_lChild) 17 { 18 s.push(curr->m_lChild); 19 } 20 else if (curr->m_rChild) 21 { 22 s.push(curr->m_rChild); 23 } 24 } 25 //从左子树向上访问树 26 else if (curr->m_lChild==prev) 27 { 28 if (curr->m_rChild) 29 { 30 s.push(curr->m_rChild); 31 } 32 } 33 //从右子树向上访问树 34 else 35 { 36 cout<<curr->m_data<<" "; 37 s.pop(); 38 } 39 prev=curr; 40 } 41 }
即将三种输出情况全放到最后一个else循环中。
方法三:这种方法比较新颖。需要用到两个stack。第一个的压栈顺序时:根--》左节点--》右节点。其出栈方式是:根先出栈,然后是右节点--》左节点。第二个栈的压栈顺序是第一个栈的出栈顺序:根--》右节点--》左节点。那么第二个栈的出栈顺序为:左节点--》右节点--》根。这正是后序遍历的顺序。代码如下:
1 void NRPostOrderTra(Node* pRoot) 2 { 3 if (!pRoot) 4 { 5 return ; 6 } 7 stack<Node*> s; 8 stack<Node*> output; 9 s.push(pRoot); 10 while (!s.empty()) 11 { 12 Node* curr=s.top(); 13 output.push(curr); 14 s.pop(); 15 if (curr->m_lChild) 16 { 17 s.push(curr->m_lChild); 18 } 19 if (curr->m_rChild) 20 { 21 s.push(curr->m_rChild); 22 } 23 } 24 while (!output.empty()) 25 { 26 cout<<output.top()->m_data<<" "; 27 output.pop(); 28 } 29 }
复杂度分析:方法二和方法三时间复杂度都是O(n)。方法二的空间复杂度是O(h),h为树的高度。方法三需要两个栈,空间复杂度为O(n)。
8. 附上可执行所有代码:
1 #include <iostream> 2 #include <cassert> 3 #include <string> 4 #include <fstream> 5 #include <vector> 6 #include <queue> 7 #include <stack> 8 using namespace std; 9 10 struct Node 11 { 12 int m_data; 13 Node* m_lChild; 14 Node* m_rChild; 15 Node(int data=0,Node* lChild=NULL,Node* rChild=NULL) 16 :m_data(data),m_lChild(lChild),m_rChild(rChild){} 17 }; 18 19 //struct Node 20 //{ 21 // int m_data; 22 // Node* m_lChild; 23 // Node* m_rChild; 24 // bool visited; 25 // Node(int data=0,Node* lChild=NULL,Node* rChild=NULL) 26 // :m_data(data),m_lChild(lChild),m_rChild(rChild),visited(false){} 27 //}; 28 // 29 //void NRInOrderTra(Node* pRoot) 30 //{ 31 // if (!pRoot) 32 // { 33 // return ; 34 // } 35 // stack<Node*> s; 36 // s.push(pRoot); 37 // while (!s.empty()) 38 // { 39 // Node* tmp=s.top(); 40 // if (tmp!=NULL) 41 // { 42 // if (!tmp->visited) 43 // { 44 // s.push(tmp->m_lChild); 45 // } 46 // else 47 // { 48 // s.pop(); 49 // cout<<tmp->m_data<<" "; 50 // s.push(tmp->m_rChild); 51 // } 52 // } 53 // else 54 // { 55 // s.pop(); 56 // if (!s.empty()) 57 // { 58 // s.top()->visited=true; 59 // } 60 // } 61 // } 62 //} 63 //方法二不需要返回值来构造一棵树,传递指针的地址来改变指针的指向 64 //这种方法可以修改为:传递指针的引用来改变指针的指向: 65 //void CreateTree(Node* &pNode,vector<int>::iterator &begin,vector<int>::iterator end) 66 void CreateTree(Node** pNode,vector<int>::iterator &begin,vector<int>::iterator end) 67 { 68 if (*begin!=-1) 69 { 70 *pNode=new Node(*begin); 71 if (*pNode) 72 { 73 if (++begin!=end) 74 { 75 CreateTree(&((*pNode)->m_lChild),begin,end); 76 } 77 if (++begin!=end) 78 { 79 CreateTree(&((*pNode)->m_rChild),begin,end); 80 } 81 } 82 } 83 } 84 85 Node* CreateTree(const char* fileName) 86 { 87 ifstream inFile; 88 inFile.open(fileName); 89 int value; 90 vector<int> vec; 91 while (inFile>>value) 92 { 93 vec.push_back(value); 94 } 95 if (vec.empty()) 96 { 97 return NULL; 98 } 99 Node* pRoot=NULL; 100 //pRoot=CreateTree(pRoot,vec.begin(),vec.end()); 101 CreateTree(&pRoot,vec.begin(),vec.end()); 102 return pRoot; 103 } 104 105 //递归前序遍历 106 void PreOrderTra(Node* pRoot) 107 { 108 if (pRoot) 109 { 110 cout<<pRoot->m_data<<" "; 111 PreOrderTra(pRoot->m_lChild); 112 PreOrderTra(pRoot->m_rChild); 113 } 114 } 115 116 //递归中序遍历 117 void InOrderTra(Node* pRoot) 118 { 119 if (pRoot) 120 { 121 InOrderTra(pRoot->m_lChild); 122 cout<<pRoot->m_data<<" "; 123 InOrderTra(pRoot->m_rChild); 124 } 125 } 126 127 //递归后序遍历 128 void PostOrderTra(Node* pRoot) 129 { 130 if (pRoot) 131 { 132 PostOrderTra(pRoot->m_lChild); 133 PostOrderTra(pRoot->m_rChild); 134 cout<<pRoot->m_data<<" "; 135 } 136 } 137 138 139 void NRInOrderTra(Node* pRoot) 140 { 141 if (!pRoot) 142 { 143 return ; 144 } 145 stack<Node*> s; 146 Node* current=pRoot; 147 while (!s.empty() || current) 148 { 149 if(current) 150 { 151 s.push(current); 152 current=current->m_lChild; 153 } 154 else 155 { 156 current=s.top(); 157 s.pop(); 158 cout<<current->m_data<<" "; 159 current=current->m_rChild; 160 } 161 } 162 } 163 164 //非递归前序遍历算法 165 void NRPreOrderTra(Node* pRoot) 166 { 167 if (!pRoot) 168 { 169 return ; 170 } 171 stack<Node*> s; 172 s.push(pRoot); 173 while (!s.empty()) 174 { 175 Node* tmp=s.top(); 176 s.pop(); 177 cout<<tmp->m_data<<" "; 178 if (tmp->m_rChild) 179 { 180 s.push(tmp->m_rChild); 181 } 182 if (tmp->m_lChild) 183 { 184 s.push(tmp->m_lChild); 185 } 186 } 187 } 188 189 //void NRPostOrderTra(Node* pRoot) 190 //{ 191 // if (!pRoot) 192 // { 193 // return ; 194 // } 195 // stack<Node*> s; 196 // s.push(pRoot); 197 // Node* prev=NULL; 198 // while (!s.empty()) 199 // { 200 // Node* curr=s.top(); 201 // //从上向下访问树 202 // if (!prev || prev->m_lChild==curr || prev->m_rChild==curr) 203 // { 204 // if (curr->m_lChild) 205 // { 206 // s.push(curr->m_lChild); 207 // } 208 // else if (curr->m_rChild) 209 // { 210 // s.push(curr->m_rChild); 211 // } 212 // else 213 // { 214 // cout<<curr->m_data<<" "; 215 // s.pop(); 216 // } 217 // } 218 // //从左子树向上访问树 219 // else if (curr->m_lChild==prev) 220 // { 221 // if (curr->m_rChild) 222 // { 223 // s.push(curr->m_rChild); 224 // } 225 // else 226 // { 227 // cout<<curr->m_data<<" "; 228 // s.pop(); 229 // } 230 // } 231 // //从右子树向上访问树 232 // else if (curr->m_rChild==prev) 233 // { 234 // cout<<curr->m_data<<" "; 235 // s.pop(); 236 // } 237 // prev=curr; 238 // } 239 //} 240 241 //void NRPostOrderTra(Node* pRoot) 242 //{ 243 // if (!pRoot) 244 // { 245 // return ; 246 // } 247 // stack<Node*> s; 248 // s.push(pRoot); 249 // Node* prev=NULL; 250 // while (!s.empty()) 251 // { 252 // Node* curr=s.top(); 253 // //从上向下访问树 254 // if (!prev || prev->m_lChild==curr || prev->m_rChild==curr) 255 // { 256 // if (curr->m_lChild) 257 // { 258 // s.push(curr->m_lChild); 259 // } 260 // else if (curr->m_rChild) 261 // { 262 // s.push(curr->m_rChild); 263 // } 264 // } 265 // //从左子树向上访问树 266 // else if (curr->m_lChild==prev) 267 // { 268 // if (curr->m_rChild) 269 // { 270 // s.push(curr->m_rChild); 271 // } 272 // } 273 // //从右子树向上访问树 274 // else 275 // { 276 // cout<<curr->m_data<<" "; 277 // s.pop(); 278 // } 279 // prev=curr; 280 // } 281 //} 282 283 //使用双栈的代码 284 void NRPostOrderTra(Node* pRoot) 285 { 286 if (!pRoot) 287 { 288 return ; 289 } 290 stack<Node*> s; 291 stack<Node*> output; 292 s.push(pRoot); 293 while (!s.empty()) 294 { 295 Node* curr=s.top(); 296 output.push(curr); 297 s.pop(); 298 if (curr->m_lChild) 299 { 300 s.push(curr->m_lChild); 301 } 302 if (curr->m_rChild) 303 { 304 s.push(curr->m_rChild); 305 } 306 } 307 while (!output.empty()) 308 { 309 cout<<output.top()->m_data<<" "; 310 output.pop(); 311 } 312 } 313 314 //打印二叉树中某层次的节点,从左到右 315 //根节点为第0层 316 //成功返回1,失败返回0 317 int PrintNodeAtLevel(Node* root,int level) 318 { 319 if (!root || level<0) 320 { 321 return 0; 322 } 323 if (level==0) 324 { 325 cout<<root->m_data<<" "; 326 return 1; 327 } 328 return PrintNodeAtLevel(root->m_lChild,level-1) | PrintNodeAtLevel(root->m_rChild,level-1); 329 } 330 331 //求二叉树的深度 332 int Depth(Node* root) 333 { 334 if (!root) 335 { 336 return 0; 337 } 338 return 1+(max(Depth(root->m_lChild),Depth(root->m_rChild))); 339 } 340 341 //利用二叉树的深度及打印二叉树的某层次的节点来 342 //层次遍历二叉树 343 void LevelTra1(Node* root) 344 { 345 if (!root) 346 { 347 return; 348 } 349 int depth=Depth(root); 350 for (int i=0;i<depth;i++) 351 { 352 PrintNodeAtLevel(root,i); 353 cout<<endl; 354 } 355 } 356 357 //当不知道二叉树的深度时,利用PrintNodeAtLevel的返回值 358 //来结束循环 359 void LevelTra2(Node* root) 360 { 361 if (!root) 362 { 363 return; 364 } 365 int depth=Depth(root); 366 for (int i=0;;i++) 367 { 368 if (!PrintNodeAtLevel(root,i)) 369 { 370 break; 371 } 372 cout<<endl; 373 } 374 } 375 376 //非递归,利用queue记录节点信息来层次 377 //遍历二叉树。但是这种做法,怎样才能做 378 //到访问每层之后打印一个换行呢? 379 void LevelTra3(Node* root) 380 { 381 if (!root) 382 { 383 return; 384 } 385 queue<Node*> m_queue; 386 m_queue.push(root); 387 while (!m_queue.empty()) 388 { 389 Node* cur=m_queue.front(); 390 cout<<cur->m_data<<" "; 391 m_queue.pop(); 392 if (cur->m_lChild) 393 { 394 m_queue.push(cur->m_lChild); 395 } 396 if (cur->m_rChild) 397 { 398 m_queue.push(cur->m_rChild); 399 } 400 } 401 cout<<endl; 402 } 403 404 //编程之美上的方法:cur和last 405 //两个下标太巧妙了!!! 406 void LevelTra4(Node* root) 407 { 408 if (!root) 409 { 410 return; 411 } 412 vector<Node*> vec; 413 vec.push_back(root); 414 int cur=0; 415 int last=1; 416 while (cur<vec.size()) 417 { 418 last=vec.size(); 419 while (cur<last) 420 { 421 cout<<vec[cur]->m_data<<" "; 422 if (vec[cur]->m_lChild) 423 { 424 vec.push_back(vec[cur]->m_lChild); 425 } 426 if (vec[cur]->m_rChild) 427 { 428 vec.push_back(vec[cur]->m_rChild); 429 } 430 cur++; 431 } 432 cout<<endl; 433 } 434 } 435 436 //编程之美上的扩展问题2: 437 //从下往上层次遍历,并且每一层从右向左输出 438 void ReverseLevelTra1(Node* root) 439 { 440 if (!root) 441 { 442 return; 443 } 444 vector<Node*> vec; 445 vec.push_back(root); 446 vec.push_back(NULL); 447 int cur=0; 448 int last=2; 449 while (cur<vec.size()-1) 450 { 451 last=vec.size(); 452 while (cur<last) 453 { 454 if (vec[cur] && vec[cur]->m_lChild) 455 { 456 vec.push_back(vec[cur]->m_lChild); 457 } 458 if (vec[cur] && vec[cur]->m_rChild) 459 { 460 vec.push_back(vec[cur]->m_rChild); 461 } 462 cur++; 463 } 464 vec.push_back(NULL); 465 } 466 vec.pop_back(); 467 vec.pop_back(); 468 for (int i=vec.size()-1;i>=0;i--) 469 { 470 if (vec[i]) 471 { 472 cout<<vec[i]->m_data<<" "; 473 } 474 else 475 { 476 cout<<endl; 477 } 478 } 479 cout<<endl; 480 } 481 482 483 //编程之美上的扩展问题1: 484 //从下往上层次遍历,并且每一层从左向右输出 485 void ReverseLevelTra2(Node* root) 486 { 487 if (!root) 488 { 489 return; 490 } 491 vector<Node*> vec; 492 vec.push_back(root); 493 vec.push_back(NULL); 494 int cur=0; 495 int last=2; 496 while (cur<vec.size()-1) 497 { 498 last=vec.size(); 499 while (cur<last) 500 { 501 if (vec[cur] && vec[cur]->m_rChild) 502 { 503 vec.push_back(vec[cur]->m_rChild); 504 } 505 if (vec[cur] && vec[cur]->m_lChild) 506 { 507 vec.push_back(vec[cur]->m_lChild); 508 } 509 cur++; 510 } 511 vec.push_back(NULL); 512 } 513 //这里需要注意最后vec中最后连个均为NULL,需要全pop出来 514 vec.pop_back(); 515 vec.pop_back(); 516 for (int i=vec.size()-1;i>=0;i--) 517 { 518 if (vec[i]) 519 { 520 cout<<vec[i]->m_data<<" "; 521 } 522 else 523 { 524 cout<<endl; 525 } 526 } 527 cout<<endl; 528 } 529 530 //利用二叉树的深度及打印二叉树的某层次的节点来 531 //从低向上层次遍历二叉树 532 void ReverseLevelTra3(Node* root) 533 { 534 if (!root) 535 { 536 return; 537 } 538 int depth=Depth(root); 539 for (int i=depth-1;i>=0;i--) 540 { 541 PrintNodeAtLevel(root,i); 542 cout<<endl; 543 } 544 } 545 546 int main() 547 { 548 char* fileName="tree.txt"; 549 Node* pTree=CreateTree(fileName); 550 551 cout<<"递归中序遍历:"; 552 InOrderTra(pTree); 553 cout<<endl; 554 cout<<"非递归中序遍历:"; 555 NRInOrderTra(pTree); 556 cout<<endl; 557 558 cout<<"递归前序遍历:"; 559 PreOrderTra(pTree); 560 cout<<endl; 561 cout<<"非递归前序遍历:"; 562 NRPreOrderTra(pTree); 563 cout<<endl; 564 565 cout<<"递归后序遍历:"; 566 PostOrderTra(pTree); 567 cout<<endl; 568 cout<<"非递归后序遍历:"; 569 NRPostOrderTra(pTree); 570 cout<<endl; 571 572 PrintNodeAtLevel(pTree,2); 573 cout<<endl; 574 cout<<Depth(pTree)<<endl; 575 LevelTra1(pTree); 576 LevelTra2(pTree); 577 LevelTra3(pTree); 578 LevelTra4(pTree); 579 ReverseLevelTra1(pTree); 580 ReverseLevelTra2(pTree); 581 ReverseLevelTra3(pTree); 582 }
tree.txt文件内容:
1 2 4 -1 -1 5 7 -1 -1 8 -1 -1 3 -1 6 -1 -1
生成的树图见《编程之美》P257 图3-23
9. 熟悉三种遍历算法。
参考文章:
http://www.leetcode.com/2010/04/binary-search-tree-in-order-traversal.html
http://www.leetcode.com/2010/10/binary-tree-post-order-traversal.html