二叉樹(shù)遍歷算法

摘要:二叉樹(shù)主要有3種遍歷算法,分為為先序纹笼、中序纹份、后序。本文對(duì)二叉樹(shù)的3種遍歷算法的遍歷規(guī)則進(jìn)行介紹廷痘,并給出3種遍歷算法的遞歸和迭代版本的C++實(shí)現(xiàn)矮嫉。文章發(fā)出后,我的好朋友指出還有一個(gè)層次遍歷牍疏,我將在文章最后介紹蠢笋。

1. 二叉樹(shù)遍歷

如圖1所示,一顆二叉樹(shù)T由根節(jié)點(diǎn)V鳞陨、左子樹(shù)L和右子樹(shù)R構(gòu)成昨寞。


圖1 二叉樹(shù)示意圖

則二叉樹(shù)T的遍歷指:按照某種次序訪問(wèn)樹(shù)中各節(jié)點(diǎn)瞻惋,每個(gè)節(jié)點(diǎn)被訪問(wèn)恰好一次。二叉樹(shù)T的先序援岩、中序歼狼、后序遍歷的結(jié)果如圖2所示。


圖2 二叉樹(shù)的先序享怀、中序羽峰、后序遍歷

從圖中可以看出,先序添瓷、中序梅屉、后序的區(qū)別在于局部根節(jié)點(diǎn)的訪問(wèn)時(shí)機(jī),而對(duì)于左右孩子的訪問(wèn)鳞贷,其訪問(wèn)順序總是滿足先左后右的規(guī)則坯汤。下面舉例說(shuō)明二叉樹(shù)的遍歷規(guī)則,一棵二叉樹(shù)的結(jié)構(gòu)如圖3所示搀愧,則其遍歷結(jié)果如下:
先序遍歷結(jié)果:A B D E G H C F
中序遍歷結(jié)果:D B G H E A C F
后序遍歷結(jié)果:D H G E B F C A


圖3 一顆普通的二叉樹(shù)

2. 二叉樹(shù)節(jié)點(diǎn)

在實(shí)現(xiàn)二叉樹(shù)的遍歷算法之前惰聂,我們先創(chuàng)建一個(gè)二叉樹(shù)節(jié)點(diǎn)類。二叉樹(shù)節(jié)點(diǎn)是二叉樹(shù)的基本組成單位咱筛,節(jié)點(diǎn)與節(jié)點(diǎn)之間有父子關(guān)系搓幌、兄弟關(guān)系等,由于節(jié)點(diǎn)的數(shù)據(jù)類型不確定迅箩,所以采用模板實(shí)現(xiàn)二叉樹(shù)的節(jié)點(diǎn)類溉愁。

/*
二叉樹(shù)節(jié)點(diǎn)
*/
#define BiNodePos(T) BiNode<T>*
template<typename T> 
struct BiNode{
    BiNodePos(T) parent= nullptr, lChild = nullptr, rChild = nullptr;
    T data; int height;
    BiNode(T data, BiNodePos(T) parent){
        this->data = data;
        this->parent = parent;
    }
    //子樹(shù)規(guī)模
    int size() {
        int s = 1;//計(jì)入本身
        if (lChild) s += lChild.size();//遞歸計(jì)入左子樹(shù)規(guī)模
        if (rChild) s += rChild.size();//遞歸計(jì)入右子樹(shù)規(guī)模
        return s;
    }
    //插入左孩子
    BiNodePos(T) insertAsLC(const T &) {
        return lChild = new BiNode(e, this);
    }
    //插入右孩子
    BiNodePos(T) insertAsRC(const T &) {
        return rChild = new BiNode(e, this);
    }
};

3. 二叉樹(shù)遍歷算法的實(shí)現(xiàn)

3.1. 遞歸版本

#include <stack>
using namespace std;

//先序遍歷
template<typename T, typename VST >
void preTraverse(BiNodePos(T) x, VST& visit)
{
    if (!x) return;
    visit(x->data);
    preTraverse(x->lChild);
    preTraverse(x->rChild);
}//O(n)
//中序遍歷
template<typename T, typename VST >
void midTraverse(BiNodePos(T) x, VST& visit)
{
    if (!x) return;
    midTraverse(x->lChild);
    visit(x->data);
    midTraverse(x->rChild);
}//O(n)
//后序遍歷
template<typename T, typename VST >
void postTraverse(BiNodePos(T) x, VST& visit)
{
    if (!x) return;
    postTraverse(x->lChild);
    postTraverse(x->rChild);
    visit(x->data);
}//O(n)

3.2. 迭代版本

遞歸版本的時(shí)間復(fù)雜度是O(n),理論上遞歸遍歷算法已經(jīng)事件復(fù)雜度已經(jīng)最小了沙热。但實(shí)際上,由于遞歸實(shí)例必須具有通用的格式罢缸,所以在運(yùn)行棧中篙贸,每一個(gè)遞歸實(shí)例對(duì)應(yīng)的一幀不可能做到足夠的小。而針對(duì)于具體的問(wèn)題枫疆,我們完全可以通過(guò)精巧的設(shè)計(jì)使得每一幀做到足夠小的爵川,所以,我們有必要將遞歸版本轉(zhuǎn)化為迭代版本息楔。

#include <stack>
using namespace std;

template<typename T, typename VST>
static void visitAlongLeftBranch(BiNodePos(T) x, VST& visit, stack<BiNodePos(T)> &s)
{
    while (x!=nullptr)
    {
        visit(x->data);
        s.push(x->rChild);
        x = x->lChild;
    }
}
template<typename T, typename VST >
void preIterationTraverse(BiNodePos(T) x, VST& visit)
{
    stack<BiNodePos(T)> s;
    while (true)
    {
        visitAlongLeftBranch(x, visit, s);
        if (s.empty) break;
        x = s.top();
        s.pop();
    }
}//O(n)
template<typename T>
static void goAlongLeftBranch(BiNodePos(T) x,stack<BiNodePos(T)> &s) 
{
    while (x!=nullptr)
    {
        s.push(x);
        x = x->lChild;
    }
}
template<typename T, typename VST >
void inIterationTraverse(BiNodePos(T) x, VST& visit)
{
    stack<BiNodePos(T)> s;
    while (true)
    {
        goAlongLeftBranch(x, s);
        if (s.empty) break;
        x = s.top();
        s.pop();
        visit(x->data);
        x = x->rChild;
    }
}//O(n)

template < typename T> 
static void gotoLVFL(stack<BiNodePos(T)> s) {
    BiNodePos(T) node;
    while (node = s.top()) {
        if (node->lChild){
            if (node->rChild)
                s->push(node->rChild);
            s->push(node->lChild);
        }
        else
            s->push(node->rChild);
    }
    s.pop();
}
template<typename T, typename VST >
void postIterationTraverse(BiNodePos(T) x, VST& visit)
{
    stack<BiNodePos(T)> s;
    if (x!=nullptr) s.push(x);
    while (!s.empty())
    {
        if (s.top() != x->parent)
            gotoLVFL<T>(s);
        x = s.top();
        visit(x->data);
    }
}//O(n)

4. 層次遍歷

首先寝贡,感謝大佬幫我指正問(wèn)題。
層次遍歷是指:按層次遍歷樹(shù)的所有節(jié)點(diǎn)(即逐層地值依,從左到右訪問(wèn)所有節(jié)點(diǎn))圃泡。下面是層次遍歷的迭代實(shí)現(xiàn)。

#include <deque>
using namespace std;

template<typename T, typename VST >
void levelIterationTraverse(BiNodePos(T) x, VST& visit)
{
    deque<BiNodePos(T)> q;
    q.push_back(x);
    while (!q.empty())
    {
        x = q.front(); q.pop_front();
        visit(x.data);
        if (x->lChild) q.push_back(x->lChild);
        if (x->rChild) q.push_back(x->rChild);
    }
}//O(n)

5. Java版本的實(shí)現(xiàn)(補(bǔ)充)

class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
    public TreeNode(int val) {
        this.val = val;
    }
}
public void preOrder(TreeNode root)
{
    Stack<TreeNode> stack = new Stack<TreeNode>();
    while(root != null || !stack.empty())
    {
        while(root != null)
        {
            System.out.print(root.val + " ");
            stack.push(root);
            root = root.left;
        }
        if(!stack.empty())
        {
            root = stack.pop();
            root = root.right;
        }
    }
}

void midOrder(TreeNode root){
    Stack<TreeNode> stack = new Stack<TreeNode>();
    while(root != null || !stack.empty())
    {
        while(root != null)
        {
            stack.push(root);
            root = root.left;
        }
        if(!stack.empty())
        {
            root = stack.pop();
            System.out.print(root.val + " ");
            root = root.right;
        }
    }
}

/**
 * 后序遍歷的難點(diǎn)在于:
 * 需要判斷上次訪問(wèn)的節(jié)點(diǎn)是位于左子樹(shù)愿险,還是右子樹(shù)颇蜡。
 * 若是位于左子樹(shù),則需跳過(guò)根節(jié)點(diǎn),先進(jìn)入右子樹(shù)风秤,再回頭訪問(wèn)根節(jié)點(diǎn)鳖目;
 * 若是位于右子樹(shù),則直接訪問(wèn)根節(jié)點(diǎn)
 * @param root
 */
void postOrder(TreeNode root){
    Stack<TreeNode> stack = new Stack<TreeNode>();
    TreeNode curr = root, last = null;
    //先把curr移動(dòng)到左子樹(shù)最下邊
    while (curr != null){
        stack.push(curr);
        curr = curr.left;
    }
    while (!stack.isEmpty()){
        curr = stack.peek();
        //一個(gè)根節(jié)點(diǎn)被訪問(wèn)的前提是:無(wú)右子樹(shù)或右子樹(shù)已被訪問(wèn)過(guò)
        if (curr.right == null || curr.right == last){
            System.out.print(curr.val + " ");
            last = curr;
            stack.pop();
        }
        else {
            //進(jìn)入右子樹(shù)
            curr = curr.right;
            while (curr != null){
                stack.push(curr);
                curr = curr.left;
            }
        }
    }
}
void levelOrder(TreeNode root){
    if (root == null)
        return;
    Queue<TreeNode> queue = new LinkedList<TreeNode>();
    queue.add(root);
    while (!queue.isEmpty()){
        TreeNode node = queue.poll();
        System.out.print(node.val + " ");
        if (node.left != null)
            queue.add(node.left);
        if (node.right != null)
            queue.add(node.right);
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末缤弦,一起剝皮案震驚了整個(gè)濱河市领迈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌碍沐,老刑警劉巖狸捅,帶你破解...
    沈念sama閱讀 221,406評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異抢韭,居然都是意外死亡薪贫,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門刻恭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)瞧省,“玉大人,你說(shuō)我怎么就攤上這事鳍贾“柏遥” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,815評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵骑科,是天一觀的道長(zhǎng)橡淑。 經(jīng)常有香客問(wèn)我,道長(zhǎng)咆爽,這世上最難降的妖魔是什么梁棠? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,537評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮斗埂,結(jié)果婚禮上符糊,老公的妹妹穿的比我還像新娘。我一直安慰自己呛凶,他們只是感情好男娄,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,536評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著漾稀,像睡著了一般模闲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上崭捍,一...
    開(kāi)封第一講書(shū)人閱讀 52,184評(píng)論 1 308
  • 那天尸折,我揣著相機(jī)與錄音,去河邊找鬼殷蛇。 笑死翁授,一個(gè)胖子當(dāng)著我的面吹牛拣播,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播收擦,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼贮配,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了塞赂?” 一聲冷哼從身側(cè)響起泪勒,我...
    開(kāi)封第一講書(shū)人閱讀 39,668評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎宴猾,沒(méi)想到半個(gè)月后圆存,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,212評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡仇哆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,299評(píng)論 3 340
  • 正文 我和宋清朗相戀三年罗售,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了棕洋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片且叁。...
    茶點(diǎn)故事閱讀 40,438評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蜈出,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出延欠,到底是詐尸還是另有隱情陌兑,我是刑警寧澤,帶...
    沈念sama閱讀 36,128評(píng)論 5 349
  • 正文 年R本政府宣布由捎,位于F島的核電站兔综,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏狞玛。R本人自食惡果不足惜软驰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,807評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望心肪。 院中可真熱鬧锭亏,春花似錦、人聲如沸蒙畴。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,279評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)膳凝。三九已至,卻和暖如春恭陡,著一層夾襖步出監(jiān)牢的瞬間蹬音,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,395評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工休玩, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留著淆,地道東北人劫狠。 一個(gè)月前我還...
    沈念sama閱讀 48,827評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像永部,于是被迫代替她去往敵國(guó)和親独泞。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,446評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容