1. 程式人生 > >非遞迴前序中序後序遍歷二叉樹

非遞迴前序中序後序遍歷二叉樹

二叉樹的遍歷問題是二叉樹的基本問題,也是在資料結構中比較常見的問題,二叉樹的遍歷分為前序遍歷、中序遍歷和後序遍歷,其中這些某序指的是每棵子樹根節點的位置,對於左右分支來說,順序永遠都是先左後右。我相信學過資料結構的姑娘和小夥子一定對這個都有所瞭解,並且也都能寫出對已知二叉樹進行遍歷的程式碼。

所以提到二叉樹前中後序遍歷最先想到的演算法是什麼呢? 我猜絕大多數人首先想到的都是遞迴的方法,我也是哦,一說到前中後序就遞迴,這多簡單啊,直接將返回值按照所需的順序組合就行了。可是呢演算法就是個千變萬化的東西,如果就是不許你用遞迴那該怎麼辦呢,所以學會用非遞迴的方法來前中後序遍歷二叉樹也是非常重要滴,至少我失敗的面試經驗是醬告訴我的。。。

廢話不多說,我們來看思路和程式碼(程式碼是C++的哦)

一、非遞迴前序遍歷

前序遍歷顧名思義就是根節點在前,也就是遍歷的結果是 根節點 + 左子樹 + 右子樹。如果幾個例子試一下我們會發現有這樣一個規律,就是如果一個節點它有左子節點,那它的下一個前序遍歷結果一定是它的左子樹,那如果沒有左子樹呢? 那就往上一步一步的“回退”,找到第一個含有右子節點的節點,把它的右子節點設為當前節點,為什麼要這麼做呢,那是因為左邊遍歷完了就輪到右邊了吧,這就是前序遍歷的定義。然後我們不斷的重複以上步驟,就完成了遍歷。

那這個過程怎麼才能不遞迴的實現呢,關鍵在於那個“回退”的過程,這時我們就想到在資料結構中有一個先入後出的神奇結構,沒錯那就是棧,我們只要在遍歷左子樹的時候壓棧,而需要去找右子樹的時候彈棧,這樣就完成了之前的“回退”過程。

原理分析完了就放程式碼吧:

vector<int> PreOrder(TreeNode* root)
{
	if(root == NULL) return vector<int>();
	stack<TreeNode*> s;
	vector<int> res;
	s.push(root);
	res.push_back(root->val);
	TreeNode* cur = root->left;
	while(!s.empty() || cur != NULL)
	{
		while(cur != NULL)//遍歷左子節點直到葉子節點
		{
			s.push(cur);
			res.push_back(cur->val);
			cur = cur->left;
		}
		cur = s.top()->right;//將當前節點設為最近右子節點
		s.pop();
	}
	return res;
}

值得注意的是,在將當前節點設為右子節點的時候,需要將當前子樹的根節點從棧中彈出,因為它已經完成使命了,留著會造成混亂和死迴圈。外括號的判斷條件也不用漏掉cur != NULL,因為當根節點被彈出,右子樹為當前節點時,棧是空的,不加這個判斷條件會是根節點的右子樹無法被遍歷哦。

二、非遞迴中序遍歷

非遞迴中序遍歷和前序遍歷其實很像的,中序遍歷是 左子樹 + 根節點 + 右子樹,我們在之前進行前序遍歷的時候,是在根節點入棧的時候,且在訪問左子樹之前,訪問根節點將內容取出,好啦那現在我們的順序變了,中序遍歷的時候根節點要在左子樹的後面了這該怎麼辦呢?這時候我們發現之前的前序遍歷有一個根節點彈棧的過程,這個彈棧的步驟正好是在訪問完左子樹接下來馬上就要訪問右子樹的時候,而這個地方恰好就是中序遍歷中訪問根節點的時機,哇真是好巧啊,那乾脆入棧的時候不要訪問,彈棧的時候再訪問根節點就好了,只要做小小的改動,就可以完成中序遍歷了,那我們來看一下程式碼吧:

vector<int> InOrder(TreeNode* root)
{
	if(root == NULL) return vector<int>();
	stack<TreeNode*> s;
	vector<int> res;
	s.push(root);
	TreeNode* cur = root->left;
	while(!s.empty() || cur != NULL)
	{
		while(cur != NULL)
		{
			s.push(cur);
			cur = cur->left;
		}
		cur = s.top()->right;
		res.push_back(s.top()->val);//在彈棧時訪問根節點
		s.pop();
	}
	return res;
}


通過對比我們發現,中序遍歷和前序遍歷唯一的區別就是根節點訪問的時機,果然通過修改訪問時機就能完成前中序遍歷的自如轉換呢~

三、非遞迴後序遍歷

之前我們一起看了非遞迴前序遍歷和中序遍歷的思路和寫法,我們發現他們之間有著很大的聯絡,連思路基本上都差不多,那這樣就簡單了,後序遍歷也就改改就可以了對吧

但是等一下,是這樣嗎?後序遍歷是 左子樹 + 右子樹 + 根節點 這樣的訪問順序,在我們之前的思路中在訪問右子樹之前就已經把根節點彈棧了,也就是說訪問完右子樹後是無法找到其根節點的。那我們就不彈棧那不就行了,但是這樣的話下一次訪問的還是棧頂元素,我們不知道這個棧頂元素的右子樹是不是已經被遍歷過了,那說到這的話你可能就會想了,那我加一個標記不就行了,如果訪問過該節點的右子樹,就給他做個標記,下次訪問到他的時候只是把值輸出然後不再訪問右子樹不就行了,嗯,看來我們已經找到後序遍歷的思路了,那就開始寫吧,這個標記我在這裡用了一根bool型別的棧來儲存

vector<int> PostOrder(TreeNode* root)
{
	if(root == NULL) return vector<int>();
	stack<TreeNode*> s;
	stack<bool> isFirst;//儲存是否是第一次被訪問
	vector<int> res;
	s.push(root);
	isFirst.push(true);
	TreeNode* cur = root->left;
	while(!s.empty() || cur != NULL)
	{
		while(cur != NULL)
		{
			s.push(cur);
			isFirst.push(true);
			cur = cur->left;
		}
		if(isFirst.top())//如果第一次被訪問更新標記,更新當前節點為右子樹
		{
			isFirst.pop();
			isFirst.push(false);
			cur = s.top()->right;
		}
		else//如果已經被訪問過一次,則返回值且彈出
		{
			res.push_back(s.top()->val);
			isFirst.pop();
			s.pop();
		}
	}
	return res;
}


上面的程式碼中如果已經被訪問過一次,也就是else的部分為什麼不更新當前節點的位置呢,那是因為我們不知道之前的節點中是不是也有被訪問過的,貿然更新節點位置可能會出錯,所以不更新的話下次迴圈還是會直接判斷當前棧頂節點是否被訪問過,直到遇到第一個未被訪問過的節點時,才將當前節點更新為右子樹。

上面的程式碼是用另一個棧實現的,當然實現方式有很多,可以通過改變樹節點的結構,增加一個是否訪問過的屬性,但是這種做法在已給出樹的結構的情況下是不允許的。還可以用一個pair的棧來直接將節點和是否訪問的資訊存下來,可以縮減幾行程式碼哈哈,還有其他的方式就不一一列舉了。

以上舉出的三個非遞迴遍歷的例子只是最普通的求解思路,如有錯誤請各位留言指正,蟹蟹:)

相關推薦

C++實現

void NoRecursePreTraverse(BiTree tree){ stack<BiNode *> stack; BiNode *node = tree; while(node != NULL || !stack.empty

(C語言版)演算法——包含和層次,和層次共八種

#include <stdlib.h> #include <stdio.h> #include "BiTree.h" #include "LinkStack.h" #include "LinkQueue.h" //初始化二叉樹(含根節點) void InitBiTree(pBiTr

資料結構實驗-C語言-二叉樹的建立,後序遍歷遞迴演算法和非遞迴演算法,求葉子結點數目,求二叉樹深度,判斷二叉樹是否相似,求二叉樹左右子互換,二叉樹序遍歷的演算法,判斷二叉樹是否是完全二叉樹

1.實驗目的 熟練掌握二叉樹的二叉連結串列儲存結構的C語言實現。掌握二叉樹的基本操作-前序、中序、後序遍歷二叉樹的三種方法。瞭解非遞迴遍歷過程中“棧”的作用和狀態,而且能靈活運用遍歷演算法實現二叉樹的其它操作。 2.實驗內容 (1)二叉樹的二叉連結串列的建立 (2)二叉樹的前、中、後

二叉樹的遍歷問題是二叉樹的基本問題,也是在資料結構中比較常見的問題,二叉樹的遍歷分為前序遍歷、中序遍歷和後序遍歷,其中這些某序指的是每棵子樹根節點的位置,對於左右分支來說,順序永遠都是先左後右。我相信學過資料結構的姑娘和小夥子一定對這個都有所瞭解,並且也都能寫出對已知二叉

的學習——(構建、根據序列、序列構建

前言 最近兩個星期一直都在斷斷續續的學習二叉樹的資料結構,昨晚突然有點融匯貫通的感覺,這裡記錄一下吧 題目要求 給定前序序列,abc##de#g##f###,構建二叉樹,並且用遞迴和非遞迴兩種方法去做前序,中序和後序遍歷 二叉樹的資料結構 #define STACKSI

,, ( && 的棧 && 棧的線索)

先簡單介紹下線索二叉樹,線索二叉樹不需要額外的空間便可以O(n)遍歷二叉樹,它充分利用了節點的空指標,若當前結點的左孩子不為空則其左指標指向左孩子結點,否則指向當前節點的前驅;若當前結點的右孩子不為空則其右指標指向右孩子結點,否則指向當前節點的後繼。具體看程式碼實現 前序遍

002 &演算法)

class TreeNode: self.val = x self.left = None self.right = None 遞迴演算法: 前序遍歷二叉樹 class Solution: def preOrderTrave

JAVA 先、層

定義一個二叉樹 package com.niuke.binaryTree; public class binaryTree { int data; binaryTree left; binaryTree right; public binaryTree(int

Leetcode-----

中序遍歷二叉樹 題目連結:中序遍歷二叉樹 解法一: 遞迴遍歷比較簡單 public List<Integer> inorderTraversal(TreeNode root) { List<Integer> result = new ArrayLi

的(

#include<stdio.h> #include<stdlib.h> typedef struct node { char data; struct node *lchild,*rchild; }bintnode; typedef struct

【LeetCode】 230. Kth Smallest Element in a BST(

因為這是一棵二叉搜尋樹,所以找它的第 kkk 小值,就是這棵二叉搜尋樹的中序遍歷之後的第 kkk 個數,所以只需要將這棵樹進行中序遍歷即可,下面程式碼是非遞迴形式的二叉樹中序遍歷。 程式碼如下: /**

資料結構-----演算法(利用堆疊實現)

一、非遞迴後序遍歷演算法思想 後序遍歷的非遞迴演算法中節點的進棧次數是兩個,即每個節點都要進棧兩次,第二次退棧的時候才訪問節點。 第一次進棧時,在遍歷左子樹的過程中將"根"節點進棧,待左子樹訪問完後,回溯的節點退棧,即退出這個"根"節點,但不能立即訪問,只能藉助於這個"根"

演算法詳解

注意學習這個演算法需要隨時可以在腦海中輸出二叉樹的中序遍歷的序列 舉例: 如上圖,我們就看到一棵二叉樹:那麼我們是不是馬上可以想到這棵二叉樹的中序遍歷序列是什麼呢? 我直接給出答案:D B EF A G H C I 我們如果不適用遞迴中序遍歷二叉樹

非遞迴後序遍歷演算法思想   後序遍歷的非遞迴演算法中節點的進棧次數是兩個,即每個節點都要進棧兩次,第二次退棧的時候才訪問節點。   第一次進棧時,在遍歷左子樹的過程中將”根”節點進棧,待左子樹訪問完後,回溯的節點退棧,即退出這個”根”節點,但不能立即訪

的四種方式:+棧、Morris(還有一種單棧和雙棧的不同版本)

本文參考: 參考文章1 參考文章2 程式碼中加入了一些自己的理解 /* 二叉樹的四種遍歷方式 */ #include <iostream> #include <stack> using namespace std; // 二叉樹

資料結構-實現

最近在看關於樹結構方面的東西,想著實現二叉樹的遍歷操作。層序,先序,中序都還好,後序就比較麻煩,下面的地址很好的解釋了遞迴與非遞迴的實現方法,在此給出另一種非遞迴實現後序遍歷二叉樹的方法,通過複雜化資料結構,使得演算法更簡單。 http://blog.csdn.net/pi

演算法 c語言)

#include "stdio.h" #include "string.h" #include "malloc.h" #define NULL 0 #define MAXSIZE 30 typedef struct BiTNode      //定義二叉樹資料結構 {  

水平 程式碼包含和水平四種

水平遍歷二叉樹要求一層一層從上往下從左往右遍歷,例如: 上面二叉樹的遍歷順序為:2、4、5、1、3 思路:利用佇列先進先出的性質 1、將根節點放入佇列 2、while迴圈佇列,只要佇列不為空,就取出第一個節點。獲取資料 3、將第二步取出的節點的左子節點和右子節點

java深度,求葉子節點個數,層次

import java.util.ArrayDeque; import java.util.Queue; public class CreateTree { /** * @param args */ public static void main(Stri

已知()

已知後序遍歷和中序遍歷重建二叉樹和已知前序遍歷和中序遍歷求後序遍歷的時候已經說明思路,這裡直接貼程式碼 # -*- coding:utf-8 -*- class Solution(object