Object-c 二叉樹的遍歷(前序、中序、後序以及非遞歸遍歷)

  • 二叉樹的結構
    二叉樹是樹的特殊形式,它包含結點值(可空),左孩子結點(可空),右孩子結點(可空)。空樹即三者均爲空,當任一結點只有左孩子或右孩子時,這顆樹的結構就與鏈表類似了。
  • 定義一個二叉樹的結點代碼清單如下:
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface TreeNode : NSObject

@property(nonatomic,assign) NSInteger value;
@property(nonatomic,strong,nullable) TreeNode* leftChild;
@property(nonatomic,strong,nullable) TreeNode* rightChild;

-(instancetype) initWithData:(NSInteger) data;


@end

NS_ASSUME_NONNULL_END

  • 創建一顆二叉樹代碼清單如下:
-(TreeNode*) createTree:(NSArray *)data{
    TreeNode* p1 = [[TreeNode alloc] initWithData:1];
    TreeNode* p2 = [[TreeNode alloc] initWithData:2];
    TreeNode* p3 = [[TreeNode alloc] initWithData:3];
    TreeNode* p4 = [[TreeNode alloc] initWithData:4];
    TreeNode* p5 = [[TreeNode alloc] initWithData:5];
    TreeNode* p6 = [[TreeNode alloc] initWithData:6];
    TreeNode* p7 = [[TreeNode alloc] initWithData:7];
    TreeNode* p8 = [[TreeNode alloc] initWithData:8];
    TreeNode* p9 = [[TreeNode alloc] initWithData:9];
    p1.leftChild = p2;
    p1.rightChild = p3;
    p2.leftChild = p4;
    p2.rightChild = p5;
    p3.leftChild = p6;
    p3.rightChild = p7;
    p4.leftChild = p8;
    p4.rightChild = p9;
    self.root = p1;
    return p1;
}

對應的樹抽象結構爲:
Untitled Diagram-5.png

  • 二叉樹的前序遍歷(遞歸)規則爲:根,左,右

1.先訪問根結點,如果有左孩子結點再訪問左孩子結點,如果有右孩子結點再訪問右孩子結點。
2.然後再把左孩子結點當作根結點,重複一次步驟1。
3.再把右孩子結點當作根結點,重複一次步驟1。
4.直到當前結點爲葉子結點爲止。
前序遍歷的結果爲:1,2,4,8,9,5,3,6,7

*前序遍歷(遞歸方式)代碼爲:

-(void) preOrder:(TreeNode *)root{
    if (!root) {
        return;
    }
    [self visitedNode:root];
    if(root.leftChild){
        [self preOrder:root.leftChild];
    }
    if(root.rightChild){
        [self preOrder:root.rightChild];
    }
}
  • 前序遍歷(非遞歸藉助棧實現)具體實現邏輯如下:

1.根結點入棧
2.while棧不爲空時,取棧頂元素(假設爲e)並訪問。判斷e是否有左孩子結點,如果有則入棧,判斷e是否有右孩子結點,如果有則入棧。

  • 前序遍歷(非遞歸藉助棧實現)代碼清單爲:
/// 非遞歸實現前序遍歷
/// @param root 根結點
-(void) iterativePreOrder:(TreeNode*) root{
    
    if (!self.stack) {
        self.stack = [[Stack alloc] initWithSize:100];
    }
    TreeNode* p = root;
    if(p){
        printf(" \n前序非遞歸遍歷的結果爲:\n ");
        [self.stack push:p];
        while (![self.stack isEmpty]) {
            p = (TreeNode*)[self.stack pop];
            [self visitedNode:p];
            
            if(p.rightChild){
                [self.stack push:p.rightChild];
            }
            if(p.leftChild){
                [self.stack push:p.leftChild];
            }
            
        }
    }
}
  • 二叉樹的中序遍歷(遞歸)規則爲:左,根,右

1.對樹中任一結點判斷當前結點是否有左孩子,如果有左孩子,繼續尋找,直到當前結點沒有左孩子爲止才訪問。
2.訪問當前結點的父結點。
3.訪問當前結點的右孩子結點。
4.直到當前結點爲葉子結點爲止。
中序遍歷的結果爲:8,4,9,5,2,1,6,3,7

  • 二叉樹的中序遍歷(遞歸)代碼爲:
-(void) inOrder:(TreeNode *)root{
    if (!root) {
        return;
    }
    if(root.leftChild){
        [self inOrder:root.leftChild];
    }
    [self visitedNode:root];
    if(root.rightChild){
        [self inOrder:root.rightChild];
    }
}
  • 二叉樹的中序遍歷(非遞歸)邏輯如下:

對於任一結點P,
1.若其左孩子不爲空,則將P入棧並將P的左孩子置爲當前的P,然後對當前結點P再進行相同的處理;
2.若其左孩子爲空,則取棧頂元素並進行出棧操作,訪問該棧頂結點,然後將當前的P置爲棧頂結點的右孩子;
3.直到P爲nil或者棧爲空則遍歷結束。

Untitled Diagram-6.png

  • 二叉樹的中序遍歷(非遞歸)代碼清單如下:
/// 非遞歸實現中序遍歷
/// @param root 根結點
-(void) iterativeInOrder:(TreeNode*) root{
    if (!self.stack) {
        self.stack = [[Stack alloc] initWithSize:100];
    }
    printf(" \n中序非遞歸遍歷的結果爲:\n ");
    TreeNode* p = root;
    while (p || ![self.stack isEmpty]) {
        
        while (p) {
            [self.stack push:p];
            p = p.leftChild;
        }
        if (![self.stack isEmpty]) {
            p = (TreeNode*)[self.stack pop];
            [self visitedNode:p];
            p = p.rightChild;
        }
    }
}
  • 二叉樹的後序遍歷(遞歸)規則爲:左,右,根

1.對樹中任一結點判斷當前結點是否有左孩子,如果有左孩子,繼續尋找,直到當前結點沒有左孩子爲止才訪問。
2.訪問當前結點的右孩子結點。
3.訪問當前結點的父親結點。
4.直到當前結點爲葉子結點爲止。
後序遍歷的結果爲:8,9,4,5,2,6,7,3,1

  • 二叉樹的後序遍歷(遞歸)代碼清單:
-(void) postOrder:(TreeNode *)root{
    if (!root) {
        return;
    }
    if(root.leftChild){
        [self postOrder:root.leftChild];
    }
    if(root.rightChild){
        [self postOrder:root.rightChild];
    }
    [self visitedNode:root];
}
  • 二叉樹的後序遍歷(非遞歸)邏輯:

1.要保證根結點在左孩子和右孩子訪問之後才能訪問,因此對於任一結點P,先將其入棧。如果P不存在左孩子和右孩子,則可以直接訪問它;
2.或者P存在左孩子或者右孩子,但是其左孩子和右孩子都已被訪問過了,則同樣可以直接訪問該結點。若非上述兩種情況,則將P的右孩子和左孩子依次入棧,這樣就保證了每次取棧頂元素的時候,左孩子在右孩子前面被訪問,左孩子和右孩子都在根結點前面被訪問。

  • 二叉樹的後序遍歷(非遞歸)代碼清單:
/// 非遞歸實現後序遍歷
/// @param root 根結點
-(void) iterativePostOrder:(TreeNode*) root{
    if (!self.stack) {
           self.stack = [[Stack alloc] initWithSize:100];
     }
    [self.stack push:root];
    TreeNode* cur;
    TreeNode* pre;
    printf(" \n後序非遞歸遍歷的結果爲:\n ");
    while(![self.stack isEmpty]){
        cur = (TreeNode*)[self.stack top];
        
        //沒有孩子結點
        BOOL noChild = !cur.leftChild && !cur.rightChild;
        //通過判斷上一個結點是否爲其中的一個孩子結點時,來判斷孩子結點是否都已經被訪問過
        //如果當前結點存在左右孩子那麼必須等它左右孩子都必需訪問之後才能訪問它
        BOOL lastVisitedNode = pre && ([pre isEqual:cur.leftChild] || [pre isEqual: cur.rightChild]);
        if (noChild || lastVisitedNode) {
            [self visitedNode:cur];
            [self.stack pop];
            pre = cur;
        }else{
            if(cur.rightChild){
                [self.stack push:cur.rightChild];
            }
            if(cur.leftChild){
                [self.stack push:cur.leftChild];
            }
        }
    }
}

-(void) visitedNode:(TreeNode*)node{
    if (node) {
        printf(" %ld ",node.value);
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章