二叉樹——遍曆篇(c++)

來源:互聯網
上載者:User

標籤:span   遍曆二叉樹   順序   方便   基本   廣度   邏輯   優先   設定   

二叉樹——遍曆篇

二叉樹很多演算法題都與其遍曆相關,筆者經過大量學習並進行了思考和總結,寫下這篇二叉樹的遍曆篇。

1、二叉樹資料結構及訪問函數
#include <stdio.h>#include <iostream>#include <stack>using namespace std;struct BTNode{    int value;    struct BTNode *left, *right;    BTNode(int value_) :value(value_),left(NULL),right(NULL){};};//訪問函數:可根據實際進行修改void visit(BTNode* node){    cout << node->value << " ";}
2、二叉樹的遍曆——深度優先遍曆(DFS)(先序、中序、後序)2.1、具體遍曆順序
  • 先序遍曆:訪問根節點、先序遍曆左孩子、先序遍曆右孩子 (根、左、右)
  • 中序遍曆:中序遍曆左孩子、訪問根節點、中序遍曆右孩子 (左、根、右)
  • 後序遍曆:後序遍曆左孩子、後序遍曆右孩子、訪問根節點 (左、右、根)
2.2、遞迴遍曆
/** * 先序遍曆二叉樹*/void PreOrder(BTNode* root){    if (root)    {        visit(root);        PreOrder(root->left);        PreOrder(root->right);    }   }/*** 中序遍曆二叉樹*/void InOrder(BTNode* root){    if (root)    {        InOrder(root->left);        visit(root);        InOrder(root->right);    }}/*** 後序遍曆二叉樹*/void PostOrder(BTNode* root){    if (root)    {        PostOrder(root->left);        PostOrder(root->right);        visit(root);    }}
2.3、非遞迴遍曆——藉助棧
  • 藉助棧,可以實現非遞迴遍曆。
  • 在這裡三種非遞迴遍曆都總結和介紹一種演算法思路,其棧中儲存的節點可以用於路徑搜尋類的題目,即儲存著從根節點到當前訪問節點最短路徑的所有節點資訊,以*標記。
  • 介紹僅用於遍曆訪問的簡單思路,棧中資訊難以應用於搜尋路徑類的題目。
2.31 先序非遞迴遍曆* PreOrder_1a

演算法過程:

由先序遍曆過程可知,先序遍曆的開始節點是根節點,然後用指標p 指向當前要處理的節點,沿著二叉樹的左下方逐一訪問,並將它們一一進棧,直至棧頂節點為最左下節點,指標p為空白。此時面臨兩種情況。(1)對棧頂節點的右子樹訪問(如果有的話,且未被訪問),對右子樹進行同樣的處理;(2)若無右子樹,則用指標last記錄最後一個訪問的節點,指向棧頂節點,彈棧。如此重複操作,直至棧空為止。

void PreOrder_1a(BTNode *root){    if (NULL == root) return;    stack<BTNode*> stack;    BTNode *p = root;    BTNode *last = NULL;    do {        while (p)        {            visit(p);            stack.push(p);            p = p->left;        }        //此時p = NULL,棧頂節點為左子樹最左節點        if (!stack.empty())        {            BTNode *t = stack.top();            if (t->right != NULL && t->right != last)            {                p = t->right;            }            else            {//若無右子樹,則指標P仍為空白,則不斷彈棧(沿著雙親方向)尋找有未被訪問的右子樹的節點                last = t;                stack.pop();              }        }    } while (!stack.empty());}
* PreOrder_1b

演算法過程:此演算法與PreOrder_1a有異曲同工之處,巧妙之處在於對上述兩種情況的處理。指標p記錄當前棧頂結點的前一個已訪問的結點。若無右子樹、或者右子樹已被訪問,則用指標p記錄當前棧頂節點,彈棧,不斷沿著雙親方向尋找有未訪問右子樹的節點,找到即退出迴圈,否則直至棧空。當棧頂節點的右孩子是p時,則將cur指向右孩子,設定flag =0 ,退出當前搜尋迴圈(不斷彈棧,搜尋有右節點且未被訪問的祖先節點),然後對右子樹進行同樣的處理。如此反覆操作,直至棧空為止。

void PreOrder_1b(BTNode *root){    if (NULL == root) return;    stack<BTNode*>stack;    int flag;    BTNode *cur = root,*p;    do{        while (cur)        {            visit(cur);            stack.push(cur);            cur = cur->left;        }     //執行到此處時,棧頂元素沒有左孩子或左子樹均已訪問過        p = NULL;      //p指向棧頂結點的前一個已訪問的結點        flag = 1;      //表示*cur的左孩子已訪問或者為空白        while (!stack.empty() && flag == 1)        {            cur = stack.top();            if (cur->right == p)  //表示右孩子結點為空白或者已經訪問完右孩子結點            {                stack.pop();                p = cur;    //p指向剛訪問過的結點            }            else            {                cur = cur->right; //cur指向右孩子結點                flag = 0;       //設定未被訪問的標記            }        }    } while (!stack.empty());}
PreOrder_2a && PreOrder_2b

PreOrder_2a 和 PreOrder_2b 演算法思路大體相同,PreOrder_2a 實現比較簡潔

演算法過程:

用指標p指向當前要處理的節點,沿著左下方向逐一訪問並壓棧,直至指標P為空白,棧頂節點為最左下節點。然後p指向棧頂節點的右節點(不管是否空);若右節點為空白,則繼續彈棧。若右節點非空,則按上述同樣處理右節點。如此重複操作直至棧空為止。

缺陷:PreOrder_2 當訪問棧頂節點的右節點時,會丟失當前棧頂節點資訊,導致從根節點到當前棧頂節點的右節點路徑不完整。

優點:演算法思路清晰易懂,邏輯簡單。

void PreOrder_2a(BTNode *root){    if (NULL == root) return;    BTNode *p = root;    stack<BTNode*> stack;    while (p || !stack.empty())    {        if (p)        {            visit(p);            stack.push(p);            p = p->left;        }        else        {            BTNode *top = stack.top();            p = top->right;             stack.pop();        }    }}void PreOrder_2b(BTNode *root){    if (NULL == root) return;    BTNode *p = root;    stack<BTNode*> stack;    while (!stack.empty() ||p)    {        while (p)        {            visit(p);            stack.push(p);            p = p->left;        }        if (!stack.empty())        {            BTNode *top = stack.top();            p = top->right;             stack.pop();        }    } }
PreOrder_3

演算法過程:

用指標p指向當前要處理的節點。先把根節點壓棧,棧非空時進入迴圈,出棧棧頂節點並訪問,然後按照先序遍曆先左後右的逆過程把當前節點的右節點壓棧(如果有的話),再把左節點壓棧(如果有的話)。如此重複操作,直至棧空為止。

特點:棧頂節點儲存的是先序遍曆下一個要訪問的節點,棧儲存的所有節點不是根到要訪問節點的路徑。

void PreOrder_3(BTNode *root){    if (NULL == root) return;    BTNode *p = root;    stack<BTNode *> stack;    stack.push(p);    while (!stack.empty())    {        p = stack.top();        stack.pop();        visit(p);        if (p->right)            stack.push(p->right);        if (p->left)            stack.push(p->left);    }}
2.32 中序非遞迴遍曆

中序遍曆非遞迴演算法要把握訪問棧頂節點的時機。

* InOrder_1

演算法過程:

用指標cur指向當前要處理的節點。先掃描(並非訪問)根節點的所有左節點並將它們一一進棧,直至棧頂節點為最左下節點,指標cur為空白。

此時主要分兩種情況。(1)棧頂節點的左節點為空白或者左節點已訪問,訪問棧頂節點(訪問位置很重要!),若有右節點則將cur指向右節點,退出當前while迴圈,對右節點進行上述同樣的操作,若無右節點則用指標p記錄站頂節點並彈棧;(2)站頂節點的右節點為空白或已訪問,用指標p記錄站頂節點並彈棧。

內部第二個while迴圈可稱為訪問搜尋迴圈,在棧頂節點的左節點為空白或者左節點已訪問的情況下,訪問棧頂節點,若有右節點,則退出迴圈,否則不斷彈棧。

如此重複操作,直至棧空為止。

void InOrder_1(BTNode *root){    if (NULL == root) return;    stack<BTNode *>stack;    BTNode *cur = root,*p = NULL;    int flag = 1;    do{        while (cur){            stack.push(cur);            cur = cur->left;        }     //執行到此處時,棧頂元素沒有左孩子或左子樹均已訪問過        p = NULL;      //p指向棧頂結點的前一個已訪問的結點        flag = 1;      //表示*cur的左孩子已訪問或者為空白        while (!stack.empty() && flag)        {            cur = stack.top();            if (cur->left == p) //左節點為空白 或者左節點已訪問            {                visit(cur);     //訪問當前棧頂節點                if (cur->right) //若有右節點  當前節點指向右節點,並退出當前迴圈,進入上面的壓棧迴圈                {                    cur = cur->right;                    flag = 0;   //flag = 0 標記右節點的左子樹未訪問                }                else           //當前節點沒有右節點,P記錄訪問完的當前節點,彈棧                {                    p = cur;                    stack.pop();                }            }            else      // 此時 cur->right == P 即訪問完右子樹 ,P記錄訪問完的當前節點,彈棧            {                p = cur;                stack.pop();            }        }    } while (!stack.empty());}
InOrder_2a && InOrder_2b

演算法過程:
用指標p指向當前要處理的節點。先掃描(並非訪問)根節點的所有左節點並將它們一一進棧,當無左節點時表示棧頂節點無左子樹,然後出棧這個節點,並訪問它,將p指向剛出棧節點的右孩子,對右孩子進行同樣的處理。如此重複操作,直至棧空為止。
需要注意的是:當節點*p的所有左下節點入棧後,這時的棧頂節點要麼沒有左子樹,要麼其左子樹已訪問,就可以訪問棧頂節點了!
InOrder_2a 、 InOrder_2b 與 PreOrder_1a 、PreOrder_1b 代碼基本相同,唯一不同的是訪問節點的時機,把握好可方便理解和記憶。

void InOrder_2a(BTNode *root){    if (NULL == root) return;    BTNode *p = root;    stack<BTNode *>stack;    while (p || !stack.empty())    {        while (p)        {            stack.push(p);            p = p->left;        }        if (!stack.empty())        {            p = stack.top();            visit(p);            stack.pop();        }    }}void InOrder_2b(BTNode *root){    if (NULL == root) return;    stack<BTNode *>stack;    BTNode *p = root;    while (p || !stack.empty())    {        while (p)        {            stack.push(p);            p = p->left;        }        if (!stack.empty())        {            p = stack.top();            visit(p);            p = p->right;            stack.pop();        }    }}
2.33 後序非遞迴遍曆*PostOrder_1
演算法過程:
用指標cur指向當前要處理的節點。先掃描(並非訪問)根節點的所有左節點並將它們一一進棧,直至棧頂節點為最左下節點,指標cur為空白。此時有兩種情況。(1)棧頂節點的右節點為空白或已訪問,訪問當前棧頂節點(訪問時機很重要!),用指標p儲存剛剛訪問過的節點(初值為NULL),彈棧;(2)棧頂節點有未被訪問的右節點,設定flag,退出當前訪問搜尋迴圈。如此重複處理,直至棧空為止。
void PostOrder_1(BTNode *root){    if (NULL == root) return;    stack<BTNode*>stack;    int flag;    BTNode *cur = root, *p;    do{        while (cur)        {            stack.push(cur);            cur = cur->left;        }      //執行到此處時,棧頂元素沒有左孩子或左子樹均已訪問過        p = NULL;      //p指向棧頂結點的前一個已訪問的結點        flag = 1;      //表示*cur的左孩子已訪問或者為空白        while (!stack.empty() && flag == 1)        {            cur = stack.top();            if (cur->right == p)            {//表示右孩子結點為空白,或者已經訪問cur的右子樹(p必定是後序遍曆cur的右子樹最後一個訪問節點)                visit(cur);                p = cur;    //p指向剛訪問過的結點                stack.pop();            }            else            {                cur = cur->right; //cur指向右孩子結點                flag = 0;        //表示*cur的左孩子尚未訪問過            }        }    } while (!stack.empty());}
PostOrder_2

演算法過程:
先把根節點壓棧,用指標p記錄上一個被訪問的節點。在棧為空白時進入迴圈,取出棧頂節點。
此時有兩種情況:
(1)訪問當前節點,注意把握訪問的時機,如果當前節點是葉子節點,訪問當前節點;如果上一個訪問的節點是當前節點的左節點(說明無右節點),訪問當前節點;如果上一個訪問的節點是當前節點的右節點(說明左右節點都有),訪問當前節點;指標p記錄當前訪問的節點,彈棧。 所以,只有當前節點是葉子節點,或者上一個訪問的節點是當前節點的左節點(無右) 或右節點(左右都有) ,才可以訪問當前節點。
(2)壓棧。 後序遍曆順序為 左、右、根,按照逆序,先把右壓棧,再把左壓棧(如果有的話)。
如此重複操作,直至棧空為止。

void PostOrder_2(BTNode* root){    if (NULL == root) return;    BTNode *cur = root, *p=NULL;    stack<BTNode*> stack;    stack.push(root);    while (!stack.empty())    {        cur = stack.top();        if (cur->left == NULL && cur->right == NULL || (p!= NULL && (cur->left==p || cur->right == p)))        {            visit(cur);            stack.pop();            p = cur;        }        else        {            if (cur->right)                stack.push(cur->right);            if (cur->left)                stack.push(cur->left);        }    }}
3、二叉樹的遍曆——廣度優先遍曆(BFS)
  • 二叉樹的廣度優先遍曆,就是層次遍曆,藉助隊列實現

在進行層次遍曆時,對某一層的節點訪問完後,再按照對它們的訪問次序對各個節點的左、右孩子順序訪問。這樣一層一層進行,先訪問的節點其左、右孩子也要先訪問,與隊列的操作原則比較吻合,且符合廣度優先搜尋的特點。

演算法過程:先將根節點進隊,在隊不空時迴圈;從隊列中出列一個節點,訪問它;若它有左孩子節點,將左孩子節點進隊;若它有右孩子節點,將右孩子節點進隊。如此重複操作直至隊空為止。

  void LevelOrder(BTNode *root)  {    if (NULL == root) return;    queue<BTNode*> queue;    queue.push(root);    BTNode* p;    while (!queue.empty())    {        p = queue.front();        queue.pop();        visit(p);        if (p->left)            queue.push(p->left);        if (p->right)            queue.push(p->right);    }  }
原創所有,轉載務必註明出處。

?

二叉樹——遍曆篇(c++)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.