0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

4中二叉樹(shù)的遍歷方式介紹

lviY_AI_shequ ? 來(lái)源:未知 ? 作者:胡薇 ? 2018-04-27 17:23 ? 次閱讀

對(duì)于一種數(shù)據(jù)結(jié)構(gòu)而言,遍歷是常見(jiàn)操作。二叉樹(shù)是一種基本的數(shù)據(jù)結(jié)構(gòu),是一種每個(gè)節(jié)點(diǎn)的兒子數(shù)目都不多于2的樹(shù)。二叉樹(shù)的節(jié)點(diǎn)聲明如下:

typedef struct TreeNode *PtrToNode;

typedef struct TreeNode *BinTree;

struct TreeNode

{

int Data; //為簡(jiǎn)單起見(jiàn),不妨假設(shè)樹(shù)節(jié)點(diǎn)的元素為int型

BinTree Left;

BinTree Right;

};

二叉樹(shù)的遍歷主要有先序遍歷,中序遍歷,后序遍歷,層序遍歷四種方式,下面一一介紹。

1. 先序遍歷

在先序遍歷中,對(duì)節(jié)點(diǎn)的訪問(wèn)工作是在它的左右兒子被訪問(wèn)之前進(jìn)行的。換言之,先序遍歷訪問(wèn)節(jié)點(diǎn)的順序是根節(jié)點(diǎn)-左兒子-右兒子。由于樹(shù)可以通過(guò)遞歸來(lái)定義,所以樹(shù)的常見(jiàn)操作用遞歸實(shí)現(xiàn)常常是方便清晰的。遞歸實(shí)現(xiàn)的代碼如下:

void PreOrderTraversal(BinTree BT)

{

if( BT )

{

printf(“%d\n”, BT->Data); //對(duì)節(jié)點(diǎn)做些訪問(wèn)比如打印

PreOrderTraversal(BT->Left); //訪問(wèn)左兒子

PreOrderTraversal(BT->Right); //訪問(wèn)右兒子

}

}

由遞歸代碼可以看出,該遞歸為尾遞歸(尾遞歸即遞歸形式在函數(shù)末尾或者說(shuō)在函數(shù)即將返回前)。尾遞歸的遞歸調(diào)用需要用棧存儲(chǔ)調(diào)用的信息,當(dāng)數(shù)據(jù)規(guī)模較大時(shí)容易越出棧空間。雖然現(xiàn)在大部分的編譯器能夠自動(dòng)去除尾遞歸,但是即使如此,我們不妨自己去除。非遞歸先序遍歷算法基本思路:使用堆棧

a. 遇到一個(gè)節(jié)點(diǎn),訪問(wèn)它,然后把它壓棧,并去遍歷它的左子樹(shù);

b. 當(dāng)左子樹(shù)遍歷結(jié)束后,從棧頂彈出該節(jié)點(diǎn)并將其指向右兒子,繼續(xù)a步驟;

c. 當(dāng)所有節(jié)點(diǎn)訪問(wèn)完即最后訪問(wèn)的樹(shù)節(jié)點(diǎn)為空且??諘r(shí),停止。

實(shí)現(xiàn)代碼如下:

void PreOrderTraversal(BinTree BT)

{

BinTree T = BT;

Stack S = CreatStack(MAX_SIZE); //創(chuàng)建并初始化堆棧S

while(T || !IsEmpty(S))

{

while(T) //一直向左并將沿途節(jié)點(diǎn)訪問(wèn)(打印)后壓入堆棧

{

printf("%d\n", T->Data);

Push(S, T);

T = T->Left;

}

if (!IsEmpty(S))

{

T = Pop(S); //節(jié)點(diǎn)彈出堆棧

T = T->Right; //轉(zhuǎn)向右子樹(shù)

}

}

}

2. 中序遍歷

中序遍歷的遍歷路徑與先序遍歷完全一樣。其實(shí)現(xiàn)的思路也與先序遍歷非常相似。其主要的不同點(diǎn)是訪問(wèn)節(jié)點(diǎn)順序不同:中序遍歷是訪問(wèn)完所有左兒子后再訪問(wèn)根節(jié)點(diǎn),最后訪問(wèn)右兒子,即為左兒子-根節(jié)點(diǎn)-右兒子。

遞歸實(shí)現(xiàn)的代碼如下:

void InOrderTraversal(BinTree BT)

{

if(BT)

{

InOrderTraversal(BT->Left);

printf("%d\n", BT->Data);

InOrderTraversal(BT->Right);

}

}

非遞歸輔助棧實(shí)現(xiàn)代碼如下:

void InOrderTraversal(BinTree BT)

{

BinTree T = BT;

Stack S = CreatStack(MaxSize); //創(chuàng)建并初始化堆棧S

while(T || !IsEmpty(S))

{

while(T) //一直向左并將沿途節(jié)點(diǎn)壓入堆棧

{

Push(S,T);

T = T->Left;

}

if(!IsEmpty(S))

{

T = Pop(S); //節(jié)點(diǎn)彈出堆棧

printf("%d\n", T->Data); //(訪問(wèn)) 打印結(jié)點(diǎn)

T = T->Right; //轉(zhuǎn)向右子樹(shù)

}

}

}

非遞歸不用輔助棧實(shí)現(xiàn)中序遍歷:

試設(shè)計(jì)一個(gè)非遞歸算法,按中根順序遍歷非線索二叉樹(shù),但不得用任何輔助棧。在執(zhí)行算法期間,允許改變左孩子指針和右孩子指針的值。

算法:右線索化+回溯

若當(dāng)前樹(shù)的根節(jié)點(diǎn)p有左孩子且未被線索化:將其左孩子的最右結(jié)點(diǎn)(可為左孩子本身)指向p,即右線索化,然后p = p->lChild;

若p有左孩子但已被線索化,說(shuō)明該p是回溯上來(lái)的,即左孩子已經(jīng)被訪問(wèn)了,則釋放線索化的指針;

若p無(wú)左孩子,打印p,向上回溯(即p = p->rChild)。

代碼如下:

/*

輸入:ABDH##I##E##CF#J##G##

*/

#include

typedef struct BTNode* Position;

typedef Position BTree;

typedef char ElementType;

struct BTNode {

ElementType data;

Position lChild, rChild;

};

BTree CreateBTree(void);

void Inorder(BTree bt);

int main()

{

BTree bt = CreateBTree();

Inorder(bt);

return 0;

}

void Inorder(BTree bt)

{

Position p = bt;

while (p)

{

Position pLeft = p->lChild;

if (pLeft)

{

while (pLeft->rChild && pLeft->rChild != p) //找到以p為根結(jié)點(diǎn)的樹(shù)的最右孩子

pLeft = pLeft->rChild;

if (pLeft->rChild == NULL) //線索化

{

pLeft->rChild = p;

p = p->lChild;

continue;

}

else //線索化后已被訪問(wèn)

{

pLeft->rChild = NULL; //釋放指向根節(jié)點(diǎn)(祖先)的指針

}

}

printf("%c ", p->data); //打印

p = p->rChild; //向上回溯或者轉(zhuǎn)向右子樹(shù)

}

printf("\n");

}

BTree CreateBTree() //按照先序序列建立二叉樹(shù)

{

BTree bt = NULL;

char ch;

scanf("%c", &ch);

if (ch != '#') //'#'代表空節(jié)點(diǎn)

{

bt = new BTNode;

bt->data = ch;

bt->lChild = CreateBTree();

bt->rChild = CreateBTree();

}

return bt;

}

運(yùn)行結(jié)果:

參考博客:http://m.blog.csdn.net/blog/Raito__/40618257

3. 后序遍歷

后序遍歷與中序遍歷,先序遍歷的路徑也完全一樣。主要的不同點(diǎn)是后序遍歷訪問(wèn)節(jié)點(diǎn)的順序是先訪問(wèn)左兒子和右兒子,最后訪問(wèn)節(jié)點(diǎn),即左兒子-右兒子-根節(jié)點(diǎn)。

遞歸實(shí)現(xiàn)思路與中序遍歷和先序遍歷相似,代碼如下:

void PostOrderTraversal(BinTree BT)

{

if (BT)

{

PostOrderTraversal(BT->Left);

PostOrderTraversal(BT->Right);

printf("%d\n", BT->Data);

}

}

后序遍歷的非遞歸實(shí)現(xiàn)

思路一:

對(duì)于一個(gè)節(jié)點(diǎn)而言,要實(shí)現(xiàn)訪問(wèn)順序?yàn)樽髢鹤?右兒子-根節(jié)點(diǎn),可以利用后進(jìn)先出的棧,在節(jié)點(diǎn)不為空的前提下,依次將根節(jié)點(diǎn),右兒子,左兒子壓棧。故我們需要按照根節(jié)點(diǎn)-右兒子-左兒子的順序遍歷樹(shù),而我們已經(jīng)知道先序遍歷的順序是根節(jié)點(diǎn)-左兒子-右兒子,故只需將先序遍歷的左右調(diào)換并把訪問(wèn)方式打印改為壓入另一個(gè)棧即可。最后一起打印棧中的元素。代碼如下:

void PostOrderTraversal(BinTree BT)

{

BinTree T = BT;

Stack S1 = CreatStack(MAX_SIZE); //創(chuàng)建并初始化堆棧S1

Stack S2 = CreatStack(MAX_SIZE); //創(chuàng)建并初始化堆棧S2

while(T || !IsEmpty(S1))

{

while(T) //一直向右并將沿途節(jié)點(diǎn)訪問(wèn)(壓入S2)后壓入堆棧S1

{

Push(S2, T);

Push(S1, T);

T = T->Right;

}

if (!IsEmpty(S1))

{

T = Pop(S1); //節(jié)點(diǎn)彈出堆棧

T = T->Left; //轉(zhuǎn)向左子樹(shù)

}

}

while(!IsEmpty(S2)) //訪問(wèn)(打?。㏒2中元素

{

T = Pop(S2);

printf("%d\n", T->Data);

}

}

思路一的優(yōu)點(diǎn)是由于利用了先序遍歷的思想,代碼較簡(jiǎn)潔,思路較清晰。缺點(diǎn)是需要用一個(gè)棧來(lái)存儲(chǔ)樹(shù)的所有節(jié)點(diǎn),空間占用較大。

思路二:

要訪問(wèn)一個(gè)節(jié)點(diǎn)的條件上一個(gè)訪問(wèn)的節(jié)點(diǎn)是右兒子。我們可以增加一個(gè)變量Prev來(lái)判斷當(dāng)前節(jié)點(diǎn)Curr的上一個(gè)節(jié)點(diǎn)與它的關(guān)系來(lái)執(zhí)行相應(yīng)的操作。

若Prev為空(Curr節(jié)點(diǎn)是根節(jié)點(diǎn))或者Prev是Curr的父節(jié)點(diǎn),將Curr節(jié)點(diǎn)的左孩子和右孩子分別壓入棧;

若Prev是Curr的左兒子,則將Curr的右兒子壓入棧;

否則Prev是Curr的右兒子,訪問(wèn)Curr;

代碼如下:

void PostOrderTraversal(BinTree BT)

{

if(BT == NULL)

return ;

Stack S = CreatStack(MAX_SIZE);

BinTree Prev = NULL , Curr = NULL; //初始化

s.push(BT);

while(!IsEmpty(S))

{

Curr = Top(S); //將棧頂元素賦給Curr

if(Prev == NULL || Prev->Left == Curr || Prev->Right == Curr) //若Prev為NULL或是Curr的父節(jié)點(diǎn)

{

if(Curr->Left != NULL)

Push(S, Curr->Left);

else if(Curr->Right != NULL)

Push(S, Curr->Right);

}

else if(Curr->Left == Prev) //若Prev是Curr的左兒子

{

if(Curr->Right != NULL)

Push(S, Curr->Right);

}

else

{

printf("%d\n", Curr->Data); //訪問(wèn)當(dāng)前節(jié)點(diǎn)

Pop(S); //訪問(wèn)后彈出

}

Prev = Curr; //處理完當(dāng)前節(jié)點(diǎn)后將Curr節(jié)點(diǎn)變?yōu)镻rev節(jié)點(diǎn)

}

}

4. 層序遍歷

二叉樹(shù)遍歷的核心問(wèn)題是二維結(jié)構(gòu)的線性化。我們通過(guò)節(jié)點(diǎn)訪問(wèn)其左右兒子時(shí),存在的問(wèn)題是訪問(wèn)左兒子后,右兒子怎么訪問(wèn)。因此我們需要一個(gè)存儲(chǔ)結(jié)構(gòu)保存暫時(shí)不訪問(wèn)的節(jié)點(diǎn)。前面三種遍歷方式的非遞歸實(shí)現(xiàn),我們是通過(guò)堆棧來(lái)保存。事實(shí)上也可以通過(guò)隊(duì)列來(lái)保存。

隊(duì)列實(shí)現(xiàn)的基本思路:遍歷從根節(jié)點(diǎn)開(kāi)始,首先將根節(jié)點(diǎn)入隊(duì),然后執(zhí)行循環(huán):節(jié)點(diǎn)出隊(duì),訪問(wèn)(訪問(wèn))根節(jié)點(diǎn),將左兒子入隊(duì),將右兒子入隊(duì),直到隊(duì)列為空停止。

這種遍歷方式的結(jié)果是將二叉樹(shù)從上到下,從左至右一層一層的遍歷,即層序遍歷,代碼實(shí)現(xiàn)如下:

void LevelOrderTraversal(BinTree BT)

{

BinTree T;

Queue Q; //聲明一個(gè)隊(duì)列

if (BT == NULL)

return; //如果樹(shù)為空,直接返回

Q = CreatQueue(MAX_SIZE); //創(chuàng)建并初始化隊(duì)列

AddQ(Q, BT); //將根節(jié)點(diǎn)入隊(duì)

while (!IsEmpty(Q))

{

T = DeleteQ(Q); //節(jié)點(diǎn)出隊(duì)

printf("%d\n", T->Data); //訪問(wèn)出隊(duì)的節(jié)點(diǎn)

if (T->Left) AddQ(Q, T->Left); //若左兒子不為空,將其入隊(duì)

if (T->Right) AddQ(Q, T->Right) //若右兒子不為空,將其入隊(duì)

}

}

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 節(jié)點(diǎn)
    +關(guān)注

    關(guān)注

    0

    文章

    220

    瀏覽量

    24465
  • 二叉樹(shù)
    +關(guān)注

    關(guān)注

    0

    文章

    74

    瀏覽量

    12355

原文標(biāo)題:二叉樹(shù)的遍歷:先序中序后序遍歷的遞歸與非遞歸實(shí)現(xiàn)及層序遍歷

文章出處:【微信號(hào):AI_shequ,微信公眾號(hào):人工智能愛(ài)好者社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    基于二叉樹(shù)的時(shí)序電路測(cè)試序列設(shè)計(jì)

    為了實(shí)現(xiàn)時(shí)序電路狀態(tài)驗(yàn)證和故障檢測(cè),需要事先設(shè)計(jì)一個(gè)輸入測(cè)試序列?;?b class='flag-5'>二叉樹(shù)節(jié)點(diǎn)和樹(shù)枝的特性,建立時(shí)序電路狀態(tài)二叉樹(shù),按照電路二叉樹(shù)節(jié)點(diǎn)(狀態(tài))與樹(shù)枝(輸入)的層次邏輯
    發(fā)表于 07-12 13:57 ?0次下載
    基于<b class='flag-5'>二叉樹(shù)</b>的時(shí)序電路測(cè)試序列設(shè)計(jì)

    二叉樹(shù)層次遍歷算法的驗(yàn)證

    實(shí)現(xiàn)二叉樹(shù)的層次遍歷算法,并對(duì)用”A(B(D,E(H(J,K(L,M(,N))))),C(F,G(,I)))”創(chuàng)建的二叉樹(shù)進(jìn)行測(cè)試。
    發(fā)表于 11-28 01:05 ?2113次閱讀
    <b class='flag-5'>二叉樹(shù)</b>層次<b class='flag-5'>遍歷</b>算法的驗(yàn)證

    詳解電源二叉樹(shù)到底是什么

    作為數(shù)據(jù)結(jié)構(gòu)的基礎(chǔ),樹(shù)分很多種,像 AVL 樹(shù)、紅黑樹(shù)二叉搜索樹(shù)....今天我想分享的是關(guān)于二叉樹(shù)
    的頭像 發(fā)表于 06-06 15:05 ?1w次閱讀
    詳解電源<b class='flag-5'>二叉樹(shù)</b>到底是什么

    C語(yǔ)言二叉樹(shù)代碼免費(fèi)下載

    本文檔的主要內(nèi)容詳細(xì)介紹的是C語(yǔ)言二叉樹(shù)代碼免費(fèi)下載。
    發(fā)表于 08-27 08:00 ?1次下載

    面試算法之重建二叉樹(shù)

    那么問(wèn)題來(lái)了,只知道前序遍歷能不能反推二叉樹(shù)呢?我們就試一下,比如題目中所述,{1,2,4,7,3,5,6,8},根據(jù)前序遍歷,根、左、右,1 肯定是 根節(jié)點(diǎn),那么一下2,
    的頭像 發(fā)表于 11-27 15:59 ?2580次閱讀

    面試二叉樹(shù)看這11個(gè)就夠了

    根據(jù)前、遍歷的特點(diǎn),(根左右、左根右),先根據(jù)前序遍歷確定根節(jié)點(diǎn),然后在遍歷知道該根節(jié)點(diǎn)的左右樹(shù)
    的頭像 發(fā)表于 11-27 16:25 ?3350次閱讀

    二叉樹(shù)操作的相關(guān)知識(shí)和代碼詳解

    見(jiàn)的二叉樹(shù)操作作個(gè)總結(jié): 前序遍歷,遍歷,后序遍歷; 層次遍歷; 求
    的頭像 發(fā)表于 12-12 11:04 ?2068次閱讀
    <b class='flag-5'>二叉樹(shù)</b>操作的相關(guān)知識(shí)和代碼詳解

    二叉樹(shù)的前序遍歷非遞歸實(shí)現(xiàn)

    我們之前說(shuō)了二叉樹(shù)基礎(chǔ)及二叉的幾種遍歷方式及練習(xí)題,今天我們來(lái)看一下二叉樹(shù)的前序遍歷非遞歸實(shí)現(xiàn)。
    的頭像 發(fā)表于 05-28 13:59 ?1980次閱讀

    C語(yǔ)言數(shù)據(jù)結(jié)構(gòu):什么是二叉樹(shù)?

    完全二叉樹(shù):完全二叉樹(shù)是效率很高的數(shù)據(jù)結(jié)構(gòu)。對(duì)于深度為K,有n個(gè)節(jié)點(diǎn)的二叉樹(shù),當(dāng)且僅當(dāng)每一個(gè)節(jié)點(diǎn)都與深度為K的滿二叉樹(shù)編號(hào)從1至n的節(jié)點(diǎn)一
    的頭像 發(fā)表于 04-21 16:20 ?2575次閱讀

    怎么就能構(gòu)造成二叉樹(shù)呢?

    一直跟著公眾號(hào)學(xué)算法的錄友 應(yīng)該知道,我在二叉樹(shù):構(gòu)造二叉樹(shù)登場(chǎng)!,已經(jīng)講過(guò),只有 序與后序 和 序和前序 可以確定一顆唯一的二叉樹(shù)
    的頭像 發(fā)表于 07-14 11:20 ?1631次閱讀

    二叉樹(shù)的最大深度

    精簡(jiǎn)之后的代碼根本看不出是哪種遍歷方式,也看不出遞歸三部曲的步驟,所以如果對(duì)二叉樹(shù)的操作還不熟練,盡量不要直接照著精簡(jiǎn)代碼來(lái)學(xué)。
    的頭像 發(fā)表于 07-26 11:28 ?1109次閱讀

    使用C語(yǔ)言代碼實(shí)現(xiàn)平衡二叉樹(shù)

    這篇博客主要總結(jié)平衡二叉樹(shù),所以,二叉排序樹(shù)知識(shí)不會(huì)提及,但是會(huì)用到。
    的頭像 發(fā)表于 09-21 11:00 ?1127次閱讀

    二叉樹(shù)的代碼實(shí)現(xiàn)

    二叉樹(shù)的主要操作有遍歷,例如有先序遍歷、遍歷、后序遍歷。在
    的頭像 發(fā)表于 01-18 10:41 ?1251次閱讀
    <b class='flag-5'>二叉樹(shù)</b>的代碼實(shí)現(xiàn)

    C++自定義二叉樹(shù)并輸出二叉樹(shù)圖形

    使用C++構(gòu)建一個(gè)二叉樹(shù)并輸出。
    的頭像 發(fā)表于 01-10 16:29 ?1780次閱讀
    C++自定義<b class='flag-5'>二叉樹(shù)</b>并輸出<b class='flag-5'>二叉樹(shù)</b>圖形

    解析LeetCode第226號(hào)題目:反轉(zhuǎn)二叉樹(shù)

    *簡(jiǎn)單講就是把每個(gè)節(jié)點(diǎn)的左子樹(shù)和右子樹(shù)進(jìn)行交換** 。 顯然,這需要我們能夠遍歷二叉樹(shù)。 那么遍歷二叉樹(shù)就有兩種經(jīng)典的解法:深度優(yōu)先
    的頭像 發(fā)表于 02-17 14:52 ?882次閱讀
    解析LeetCode第226號(hào)題目:反轉(zhuǎn)<b class='flag-5'>二叉樹(shù)</b>