(二叉樹相關算法實現-iOS)

什麼是二叉樹?

在計算機科學中,二叉樹是每個節點最多有兩個子樹的樹結構。通常子樹被稱作“左子樹”和“右子樹”,左子樹和右子樹同時也是二叉樹。二叉樹的子樹有左右之分,並且次序不能任意顛倒。二叉樹是遞歸定義的,所以一般二叉樹的相關題目也都可以使用遞歸的思想來解決,當然也有一些可以使用非遞歸的思想解決,我下面列出的一些算法有些採用了遞歸,有些是非遞歸的。

什麼是二叉排序樹?

二叉排序樹又叫二叉查找樹或者二叉搜索樹,它首先是一個二叉樹,而且必須滿足下面的條件:

1)若左子樹不空,則左子樹上所有結點的值均小於它的根節點的值;

2)若右子樹不空,則右子樹上所有結點的值均大於它的根結點的值

3)左、右子樹也分別爲二叉排序樹

4)沒有鍵值相等的節點(?可能是因爲不好處理鍵值相等的節點到底是左節點還是右節點吧)

概念就介紹這麼多,都是來自網上,下面主要看算法和具體實現代碼。

二叉樹節點定義

採用單項鍊表的形式,只從根節點指向孩子節點,不保存父節點。

複製代碼
/**
 *  二叉樹節點
 */
@interface BinaryTreeNode : NSObject

/**
 *  值
 */
@property (nonatomic, assign) NSInteger value;
/**
 *  左節點
 */
@property (nonatomic, strong) BinaryTreeNode *leftNode;
/**
 *  右節點
 */
@property (nonatomic, strong) BinaryTreeNode *rightNode;

@end
複製代碼

創建二叉排序樹

二叉樹中左右節點值本身沒有大小之分,所以如果要創建二叉樹,就需要考慮如何處理某個節點是左節點還是右節點,如何終止某個子樹而切換到另一個子樹。 因此我選擇了二叉排序樹,二叉排序樹中對於左右節點有明確的要求,程序可以自動根據鍵值大小自動選擇是左節點還是右節點。

複製代碼
/**
 *  創建二叉排序樹
 *  二叉排序樹:左節點值全部小於根節點值,右節點值全部大於根節點值
 *
 *  @param values 數組
 *
 *  @return 二叉樹根節點
 */
+ (BinaryTreeNode *)createTreeWithValues:(NSArray *)values {
    
    BinaryTreeNode *root = nil;
    for (NSInteger i=0; i<values.count; i++) {
        NSInteger value = [(NSNumber *)[values objectAtIndex:i] integerValue];
        root = [BinaryTree addTreeNode:root value:value];
    }
    return root;
}

/**
 *  向二叉排序樹節點添加一個節點
 *
 *  @param treeNode 根節點
 *  @param value    值
 *
 *  @return 根節點
 */
+ (BinaryTreeNode *)addTreeNode:(BinaryTreeNode *)treeNode value:(NSInteger)value {
    //根節點不存在,創建節點
    if (!treeNode) {
        treeNode = [BinaryTreeNode new];
        treeNode.value = value;
        NSLog(@"node:%@", @(value));
    }
    else if (value <= treeNode.value) {
        NSLog(@"to left");
        //值小於根節點,則插入到左子樹
        treeNode.leftNode = [BinaryTree addTreeNode:treeNode.leftNode value:value];
    }
    else {
        NSLog(@"to right");
        //值大於根節點,則插入到右子樹
        treeNode.rightNode = [BinaryTree addTreeNode:treeNode.rightNode value:value];
    }
    
    return treeNode;
}
複製代碼

二叉樹中某個位置的節點

類似索引操作,按層次遍歷,位置從0開始算。

複製代碼
/**
 *  二叉樹中某個位置的節點(按層次遍歷)
 *
 *  @param index    按層次遍歷樹時的位置(從0開始算)
 *  @param rootNode 樹根節點
 *
 *  @return 節點
 */
+ (BinaryTreeNode *)treeNodeAtIndex:(NSInteger)index inTree:(BinaryTreeNode *)rootNode {
    //按層次遍歷
    if (!rootNode || index < 0) {
        return nil;
    }
    
    NSMutableArray *queueArray = [NSMutableArray array]; //數組當成隊列
    [queueArray addObject:rootNode]; //壓入根節點
    while (queueArray.count > 0) {
        
        BinaryTreeNode *node = [queueArray firstObject];
        if (index == 0) {
            return node;
        }
        [queueArray removeObjectAtIndex:0]; //彈出最前面的節點,仿照隊列先進先出原則
        index--; //移除節點,index減少
        
        if (node.leftNode) {
            [queueArray addObject:node.leftNode]; //壓入左節點
        }
        if (node.rightNode) {
            [queueArray addObject:node.rightNode]; //壓入右節點
        }
    }
    //層次遍歷完,仍然沒有找到位置,返回nil
    return nil;
}
複製代碼

先序遍歷

先訪問根,再遍歷左子樹,再遍歷右子樹。典型的遞歸思想。

複製代碼
/**
 *  先序遍歷
 *  先訪問根,再遍歷左子樹,再遍歷右子樹
 *
 *  @param rootNode 根節點
 *  @param handler  訪問節點處理函數
 */
+ (void)preOrderTraverseTree:(BinaryTreeNode *)rootNode handler:(void(^)(BinaryTreeNode *treeNode))handler {
    if (rootNode) {
        
        if (handler) {
            handler(rootNode);
        }
        
        [self preOrderTraverseTree:rootNode.leftNode handler:handler];
        [self preOrderTraverseTree:rootNode.rightNode handler:handler];
    }
}
複製代碼

調用方法如下:(用到了block)

NSMutableArray *orderArray = [NSMutableArray array];
[BinaryTree preOrderTraverseTree:root handler:^(BinaryTreeNode *treeNode) {
     [orderArray addObject:@(treeNode.value)];
}];
NSLog(@"先序遍歷結果:%@", [orderArray componentsJoinedByString:@","]);

 

中序遍歷

先遍歷左子樹,再訪問根,再遍歷右子樹。

對於二叉排序樹來說,中序遍歷得到的序列是一個從小到大排序好的序列。

複製代碼
/**
 *  中序遍歷
 *  先遍歷左子樹,再訪問根,再遍歷右子樹
 *
 *  @param rootNode 根節點
 *  @param handler  訪問節點處理函數
 */
+ (void)inOrderTraverseTree:(BinaryTreeNode *)rootNode handler:(void(^)(BinaryTreeNode *treeNode))handler {
    if (rootNode) {
        [self inOrderTraverseTree:rootNode.leftNode handler:handler];
        
        if (handler) {
            handler(rootNode);
        }
        
        [self inOrderTraverseTree:rootNode.rightNode handler:handler];
    }
}
複製代碼

 

後序遍歷

先遍歷左子樹,再遍歷右子樹,再訪問根

複製代碼
/**
 *  後序遍歷
 *  先遍歷左子樹,再遍歷右子樹,再訪問根
 *
 *  @param rootNode 根節點
 *  @param handler  訪問節點處理函數
 */
+ (void)postOrderTraverseTree:(BinaryTreeNode *)rootNode handler:(void(^)(BinaryTreeNode *treeNode))handler {
    if (rootNode) {
        [self postOrderTraverseTree:rootNode.leftNode handler:handler];
        [self postOrderTraverseTree:rootNode.rightNode handler:handler];
        
        if (handler) {
            handler(rootNode);
        }
    }
}
複製代碼

 

層次遍歷

按照從上到下、從左到右的次序進行遍歷。先遍歷完一層,再遍歷下一層,因此又叫廣度優先遍歷。需要用到隊列,在OC裏可以用可變數組來實現。

複製代碼
/**
 *  層次遍歷(廣度優先)
 *
 *  @param rootNode 二叉樹根節點
 *  @param handler  訪問節點處理函數
 */
+ (void)levelTraverseTree:(BinaryTreeNode *)rootNode handler:(void(^)(BinaryTreeNode *treeNode))handler {
    if (!rootNode) {
        return;
    }
    
    NSMutableArray *queueArray = [NSMutableArray array]; //數組當成隊列
    [queueArray addObject:rootNode]; //壓入根節點
    while (queueArray.count > 0) {
        
        BinaryTreeNode *node = [queueArray firstObject];
        
        if (handler) {
            handler(node);
        }
        
        [queueArray removeObjectAtIndex:0]; //彈出最前面的節點,仿照隊列先進先出原則
        if (node.leftNode) {
            [queueArray addObject:node.leftNode]; //壓入左節點
        }
        if (node.rightNode) {
            [queueArray addObject:node.rightNode]; //壓入右節點
        }
    }
}
複製代碼

 

二叉樹的深度

二叉樹的深度定義爲:從根節點到葉子結點依次經過的結點形成樹的一條路徑,最長路徑的長度爲樹的深度。

1)如果根節點爲空,則深度爲0;

2)如果左右節點都是空,則深度爲1;

3)遞歸思想:二叉樹的深度=max(左子樹的深度,右子樹的深度)+ 1

複製代碼
/**
 *  二叉樹的深度
 *
 *  @param rootNode 二叉樹根節點
 *
 *  @return 二叉樹的深度
 */
+ (NSInteger)depthOfTree:(BinaryTreeNode *)rootNode {
    if (!rootNode) {
        return 0;
    }
    if (!rootNode.leftNode && !rootNode.rightNode) {
        return 1;
    }
    
    //左子樹深度
    NSInteger leftDepth = [self depthOfTree:rootNode.leftNode];
    //右子樹深度
    NSInteger rightDepth = [self depthOfTree:rootNode.rightNode];
    
    return MAX(leftDepth, rightDepth) + 1;
}
複製代碼

 

二叉樹的寬度

二叉樹的寬度定義爲各層節點數的最大值。

複製代碼
/**
 *  二叉樹的寬度
 *
 *  @param rootNode 二叉樹根節點
 *
 *  @return 二叉樹寬度
 */
+ (NSInteger)widthOfTree:(BinaryTreeNode *)rootNode {
    if (!rootNode) {
        return 0;
    }
    
    NSMutableArray *queueArray = [NSMutableArray array]; //數組當成隊列
    [queueArray addObject:rootNode]; //壓入根節點
    NSInteger maxWidth = 1; //最大的寬度,初始化爲1(因爲已經有根節點)
    NSInteger curWidth = 0; //當前層的寬度
    
    while (queueArray.count > 0) {
        
        curWidth = queueArray.count;
        //依次彈出當前層的節點
        for (NSInteger i=0; i<curWidth; i++) {
            BinaryTreeNode *node = [queueArray firstObject];
            [queueArray removeObjectAtIndex:0]; //彈出最前面的節點,仿照隊列先進先出原則
            //壓入子節點
            if (node.leftNode) {
                [queueArray addObject:node.leftNode];
            }
            if (node.rightNode) {
                [queueArray addObject:node.rightNode];
            }
        }
        //寬度 = 當前層節點數
        maxWidth = MAX(maxWidth, queueArray.count);
    }
    
    return maxWidth;
}
複製代碼

 二叉樹的所有節點數

遞歸思想:二叉樹所有節點數=左子樹節點數+右子樹節點數+1

複製代碼
/**
 *  二叉樹的所有節點數
 *
 *  @param rootNode 根節點
 *
 *  @return 所有節點數
 */
+ (NSInteger)numberOfNodesInTree:(BinaryTreeNode *)rootNode {
    if (!rootNode) {
        return 0;
    }
    //節點數=左子樹節點數+右子樹節點數+1(根節點)
    return [self numberOfNodesInTree:rootNode.leftNode] + [self numberOfNodesInTree:rootNode.rightNode] + 1;
}
複製代碼

二叉樹某層中的節點數

1)根節點爲空,則節點數爲0;

2)層爲1,則節點數爲1(即根節點)

3)遞歸思想:二叉樹第k層節點數=左子樹第k-1層節點數+右子樹第k-1層節點數

複製代碼
/**
 *  二叉樹某層中的節點數
 *
 *  @param level    層
 *  @param rootNode 根節點
 *
 *  @return 層中的節點數
 */
+ (NSInteger)numberOfNodesOnLevel:(NSInteger)level inTree:(BinaryTreeNode *)rootNode {
    if (!rootNode || level < 1) { //根節點不存在或者level<0
        return 0;
    }
    if (level == 1) { //level=1,返回1(根節點)
        return 1;
    }
    //遞歸:level層節點數 = 左子樹level-1層節點數+右子樹level-1層節點數
    return [self numberOfNodesOnLevel:level-1 inTree:rootNode.leftNode] + [self numberOfNodesOnLevel:level-1 inTree:rootNode.rightNode];
} 
複製代碼

二叉樹葉子節點數

葉子節點,又叫終端節點,是左右子樹都是空的節點。

複製代碼
/**
 *  二叉樹葉子節點數
 *
 *  @param rootNode 根節點
 *
 *  @return 葉子節點數
 */
+ (NSInteger)numberOfLeafsInTree:(BinaryTreeNode *)rootNode {
    if (!rootNode) {
        return 0;
    }
    //左子樹和右子樹都是空,說明是葉子節點
    if (!rootNode.leftNode && !rootNode.rightNode) {
        return 1;
    }
    //遞歸:葉子數 = 左子樹葉子數 + 右子樹葉子數
    return [self numberOfLeafsInTree:rootNode.leftNode] + [self numberOfLeafsInTree:rootNode.rightNode];
}
複製代碼

 

二叉樹最大距離(二叉樹的直徑)

二叉樹中任意兩個節點都有且僅有一條路徑,這個路徑的長度叫這兩個節點的距離。二叉樹中所有節點之間的距離的最大值就是二叉樹的直徑。

有一種解法,把這個最大距離劃分了3種情況:

1)這2個節點分別在根節點的左子樹和右子樹上,他們之間的路徑肯定經過根節點,而且他們肯定是根節點左右子樹上最遠的葉子節點(他們到根節點的距離=左右子樹的深度)。

2)這2個節點都在左子樹上

3)這2個節點都在右子樹上

綜上,只要取這3種情況中的最大值,就是二叉樹的直徑。

複製代碼
/**
 *  二叉樹最大距離(直徑)
 *
 *  @param rootNode 根節點
 *
 *  @return 最大距離
 */
+ (NSInteger)maxDistanceOfTree:(BinaryTreeNode *)rootNode {
    if (!rootNode) {
        return 0;
    }
//    方案一:(遞歸次數較多,效率較低)
    //分3種情況:
    //1、最遠距離經過根節點:距離 = 左子樹深度 + 右子樹深度
    NSInteger distance = [self depthOfTree:rootNode.leftNode] + [self depthOfTree:rootNode.rightNode];
    //2、最遠距離在根節點左子樹上,即計算左子樹最遠距離
    NSInteger disLeft = [self maxDistanceOfTree:rootNode.leftNode];
    //3、最遠距離在根節點右子樹上,即計算右子樹最遠距離
    NSInteger disRight = [self maxDistanceOfTree:rootNode.rightNode];
    
    return MAX(MAX(disLeft, disRight), distance);
}
複製代碼

這個方案效率較低,因爲計算子樹的深度和最遠距離是分開遞歸的,存在重複遞歸遍歷的情況。其實一次遞歸,就可以分別計算出深度和最遠距離,於是有了第二種方案:

複製代碼
/**
 *  二叉樹最大距離(直徑)
 *
 *  @param rootNode 根節點
 *
 *  @return 最大距離
 */
+ (NSInteger)maxDistanceOfTree:(BinaryTreeNode *)rootNode {
    if (!rootNode) {
        return 0;
    }
//    方案2:將計算節點深度和最大距離放到一次遞歸中計算,方案一是分別單獨遞歸計算深度和最遠距離
    TreeNodeProperty *p = [self propertyOfTreeNode:rootNode];
    return p.distance;
}

/**
 *  計算樹節點的最大深度和最大距離
 *
 *  @param rootNode 根節點
 *
 *  @return TreeNodeProperty
 */
+ (TreeNodeProperty *)propertyOfTreeNode:(BinaryTreeNode *)rootNode {
    
    if (!rootNode) {
        return nil;
    }
    
    TreeNodeProperty *left = [self propertyOfTreeNode:rootNode.leftNode];
    TreeNodeProperty *right = [self propertyOfTreeNode:rootNode.rightNode];
    TreeNodeProperty *p = [TreeNodeProperty new];
    //節點的深度depth = 左子樹深度、右子樹深度中最大值+1(+1是因爲根節點佔了1個depth)
    p.depth = MAX(left.depth, right.depth) + 1;
    //最遠距離 = 左子樹最遠距離、右子樹最遠距離和橫跨左右子樹最遠距離中最大值
    p.distance = MAX(MAX(left.distance, right.distance), left.depth+right.depth);
    
    return p;
}
複製代碼

二叉樹中某個節點到根節點的路徑

既是尋路問題,又是查找節點問題。

定義一個存放路徑的棧(不是隊列了,但是還是用可變數組來實現的)

1)壓入根節點,再從左子樹中查找(遞歸進行的),如果未找到,再從右子樹中查找,如果也未找到,則彈出根節點,再遍歷棧中上一個節點。

2)如果找到,則棧中存放的節點就是路徑所經過的節點。

複製代碼
/**
 *  二叉樹中某個節點到根節點的路徑
 *
 *  @param treeNode 節點
 *  @param rootNode 根節點
 *
 *  @return 存放路徑節點的數組
 */
+ (NSArray *)pathOfTreeNode:(BinaryTreeNode *)treeNode inTree:(BinaryTreeNode *)rootNode {
    NSMutableArray *pathArray = [NSMutableArray array];
    [self isFoundTreeNode:treeNode inTree:rootNode routePath:pathArray];
    return pathArray;
}

/**
 *  查找某個節點是否在樹中
 *
 *  @param treeNode 待查找的節點
 *  @param rootNode 根節點
 *  @param path  根節點到待查找節點的路徑
 *
 *  @return YES:找到,NO:未找到
 */
+ (BOOL)isFoundTreeNode:(BinaryTreeNode *)treeNode inTree:(BinaryTreeNode *)rootNode routePath:(NSMutableArray *)path {
    
    if (!rootNode || !treeNode) {
        return NO;
    }
    
    //找到節點
    if (rootNode == treeNode) {
        [path addObject:rootNode];
        return YES;
    }
    //壓入根節點,進行遞歸
    [path addObject:rootNode];
    //先從左子樹中查找
    BOOL find = [self isFoundTreeNode:treeNode inTree:rootNode.leftNode routePath:path];
    //未找到,再從右子樹查找
    if (!find) {
        find = [self isFoundTreeNode:treeNode inTree:rootNode.rightNode routePath:path];
    }
    //如果2邊都沒查找到,則彈出此根節點
    if (!find) {
        [path removeLastObject];
    }
    
    return find;
}
複製代碼

二叉樹中兩個節點最近的公共父節點

首先需要明白,根節點肯定是二叉樹中任意兩個節點的公共父節點(不一定是最近的),因此二叉樹中2個節點的最近公共父節點一定在從根節點到這個節點的路徑上。因此我們可以先分別找到從根節點到這2個節點的路徑,再從這兩個路徑中找到最近的公共父節點。

複製代碼
/**
 *  二叉樹中兩個節點最近的公共父節點
 *
 *  @param nodeA    第一個節點
 *  @param nodeB    第二個節點
 *  @param rootNode 二叉樹根節點
 *
 *  @return 最近的公共父節點
 */
+ (BinaryTreeNode *)parentOfNode:(BinaryTreeNode *)nodeA andNode:(BinaryTreeNode *)nodeB inTree:(BinaryTreeNode *)rootNode {
    if (!rootNode || !nodeA || !nodeB) {
        return nil;
    }
    if (nodeA == nodeB) {
        return nodeA;
    }
    //從根節點到節點A的路徑
    NSArray *pathA = [self pathOfTreeNode:nodeA inTree:rootNode];
    //從根節點到節點B的路徑
    NSArray *pathB = [self pathOfTreeNode:nodeB inTree:rootNode];
    //其中一個節點不在樹中,則沒有公共父節點
    if (pathA.count == 0 || pathB == 0) {
        return nil;
    }
    //從後往前推,查找第一個出現的公共節點
    for (NSInteger i = pathA.count-1; i>=0; i--) {
        for (NSInteger j = pathB.count - 1; j>=0; j--) {
            if ([pathA objectAtIndex:i] == [pathB objectAtIndex:j]) {
                //找到
                return [pathA objectAtIndex:i];
            }
        }
    }
    return nil;
}
複製代碼

二叉樹中兩個節點之間的路徑

從查找最近公共父節點衍生出來的。

複製代碼
/**
 *  二叉樹中兩個節點之間的路徑
 *
 *  @param nodeA    第一個節點
 *  @param nodeB    第二個節點
 *  @param rootNode 二叉樹根節點
 *
 *  @return 兩個節點間的路徑
 */
+ (NSArray *)pathFromNode:(BinaryTreeNode *)nodeA toNode:(BinaryTreeNode *)nodeB inTree:(BinaryTreeNode *)rootNode {
    if (!rootNode || !nodeA || !nodeB) {
        return nil;
    }
    NSMutableArray *path = [NSMutableArray array];
    if (nodeA == nodeB) {
        [path addObject:nodeA];
        [path addObject:nodeB];
        return path;
    }
    //從根節點到節點A的路徑
    NSArray *pathA = [self pathOfTreeNode:nodeA inTree:rootNode];
    //從根節點到節點B的路徑
    NSArray *pathB = [self pathOfTreeNode:nodeB inTree:rootNode];
    //其中一個節點不在樹中,則沒有路徑
    if (pathA.count == 0 || pathB == 0) {
        return nil;
    }
    //從後往前推,查找第一個出現的公共節點
    for (NSInteger i = pathA.count-1; i>=0; i--) {
        [path addObject:[pathA objectAtIndex:i]];
        for (NSInteger j = pathB.count - 1; j>=0; j--) {
            //找到公共父節點,則將pathB中後面的節點壓入path
            if ([pathA objectAtIndex:i] == [pathB objectAtIndex:j]) {
                j++; //j++是爲了避開公共父節點
                while (j<pathB.count) {
                    [path addObject:[pathB objectAtIndex:j]];
                    j++;
                }
                
                return path;
            }
        }
    }
    return nil;
}
複製代碼

二叉樹兩個節點之間的距離

可以從兩個節點之間的路徑衍生出來。

複製代碼
/**
 *  二叉樹兩個節點之間的距離
 *
 *  @param nodeA    第一個節點
 *  @param nodeB    第二個節點
 *  @param rootNode 二叉樹根節點
 *
 *  @return 兩個節點間的距離(-1:表示沒有找到路徑)
 */
+ (NSInteger)distanceFromNode:(BinaryTreeNode *)nodeA toNode:(BinaryTreeNode *)nodeB inTree:(BinaryTreeNode *)rootNode {
    if (!rootNode || !nodeA || !nodeB) {
        return -1;
    }
    if (nodeA == nodeB) {
        return 0;
    }
    //從根節點到節點A的路徑
    NSArray *pathA = [self pathOfTreeNode:nodeA inTree:rootNode];
    //從根節點到節點B的路徑
    NSArray *pathB = [self pathOfTreeNode:nodeB inTree:rootNode];
    //其中一個節點不在樹中,則沒有路徑
    if (pathA.count == 0 || pathB == 0) {
        return -1;
    }
    //從後往前推,查找第一個出現的公共節點
    for (NSInteger i = pathA.count-1; i>=0; i--) {
        for (NSInteger j = pathB.count - 1; j>=0; j--) {
            //找到公共父節點
            if ([pathA objectAtIndex:i] == [pathB objectAtIndex:j]) {
                //距離=路徑節點數-1 (這裏要-2,因爲公共父節點重複了一次)
                return (pathA.count - i) + (pathB.count - j) - 2;
            }
        }
    }
    return -1;
}
複製代碼

 

翻轉二叉樹

你會翻轉二叉樹嗎?如果不會,那對不起,我們不會錄用你!

翻轉二叉樹,又叫求二叉樹的鏡像,就是把二叉樹的左右子樹對調(當然是遞歸的)

複製代碼
/**
 *  翻轉二叉樹(又叫:二叉樹的鏡像)
 *
 *  @param rootNode 根節點
 *
 *  @return 翻轉後的樹根節點(其實就是原二叉樹的根節點)
 */
+ (BinaryTreeNode *)invertBinaryTree:(BinaryTreeNode *)rootNode {
    if (!rootNode) {
        return nil;
    }
    if (!rootNode.leftNode && !rootNode.rightNode) {
        return rootNode;
    }
    
    [self invertBinaryTree:rootNode.leftNode];
    [self invertBinaryTree:rootNode.rightNode];
    
    BinaryTreeNode *tempNode = rootNode.leftNode;
    rootNode.leftNode = rootNode.rightNode;
    rootNode.rightNode = tempNode;
    
    return rootNode;
}
複製代碼

判斷二叉樹是否完全二叉樹

完全二叉樹定義爲:若設二叉樹的高度爲h,除第h層外,其它各層的結點數都達到最大個數,第h層有葉子結點,並且葉子結點都是從左到右依次排布。

完全二叉樹必須滿足2個條件:

1)如果某個節點的右子樹不爲空,則它的左子樹必須不爲空

2)如果某個節點的右子樹爲空,則排在它後面的節點必須沒有孩子節點

這裏還需要理解“排在它後面的節點”,回頭看看層次遍歷算法,我們就能知道在層次遍歷時,是從上到下從左到右遍歷的,先將根節點彈出隊列,再壓入孩子節點,因此“排在它後面的節點”有2種情況:

1)同層次的後面的節點

2)同層次的前面的節點的孩子節點(因爲遍歷前面的節點時,會彈出節點,同時將孩子節點壓入隊列)

通過上面的分析,我們可以設置一個標誌位flag,當子樹滿足完全二叉樹時,設置flag=YES。當flag=YES而節點又破壞了完全二叉樹的條件,那麼它就不是完全二叉樹。

複製代碼
/**
 *  是否完全二叉樹
 *  完全二叉樹:若設二叉樹的高度爲h,除第h層外,其它各層的結點數都達到最大個數,第h層有葉子結點,並且葉子結點都是從左到右依次排布
 *
 *  @param rootNode 根節點
 *
 *  @return YES:是完全二叉樹,NO:不是完全二叉樹
 */
+ (BOOL)isCompleteBinaryTree:(BinaryTreeNode *)rootNode {
    if (!rootNode) {
        return NO;
    }
    //左子樹和右子樹都是空,則是完全二叉樹
    if (!rootNode.leftNode && !rootNode.rightNode) {
        return YES;
    }
    //左子樹是空,右子樹不是空,則不是完全二叉樹
    if (!rootNode.leftNode && rootNode.rightNode) {
        return NO;
    }
    
    //按層次遍歷節點,找到滿足完全二叉樹的條件:
    //條件1:如果某個節點的右子樹不爲空,則它的左子樹必須不爲空
    //條件2:如果某個節點的右子樹爲空,則排在它後面的節點必須沒有孩子節點
    //排在該節點後面的節點有2種:1)同層次的後面的節點 2)同層次的前面的節點的孩子節點(因爲遍歷前面的節點的時候,會將節點從隊列裏pop,同時把它的孩子節點push到隊列裏)
    NSMutableArray *queue = [NSMutableArray array];
    [queue addObject:rootNode];
    BOOL isComplete = NO; //是否已經滿足完全二叉樹
    while (queue.count > 0) {
        BinaryTreeNode *node = [queue firstObject];
        [queue removeObjectAtIndex:0];
        
        //左子樹爲空且右子樹不爲空,則不是完全二叉樹
        if (!node.leftNode && node.rightNode) {
            return NO;
        }
        if (isComplete && (node.leftNode || node.rightNode)) {
            //前面的節點已滿足完全二叉樹,如果還有孩子節點,則不是完全二叉樹
            return NO;
        }
        
        //右子樹爲空,則已經滿足完全二叉樹
        if (!node.rightNode) {
            isComplete = YES;
        }
        
        //壓入
        if (node.leftNode) {
            [queue addObject:node.leftNode];
        }
        if (node.rightNode) {
            [queue addObject:node.rightNode];
        }
    }
    return isComplete;
}
複製代碼

 判斷二叉樹是否滿二叉樹

 滿二叉樹定義爲:除了葉結點外每一個結點都有左右子葉且葉子結點都處在最底層的二叉樹

 滿二叉樹的一個特性是:葉子數=2^(深度-1),因此我們可以根據這個特性來判斷二叉樹是否是滿二叉樹。

複製代碼
/**
 *  是否滿二叉樹
 *  滿二叉樹:除了葉結點外每一個結點都有左右子葉且葉子結點都處在最底層的二叉樹
 *
 *  @param rootNode 根節點
 *
 *  @return YES:滿二叉樹,NO:非滿二叉樹
 */
+ (BOOL)isFullBinaryTree:(BinaryTreeNode *)rootNode {
    if (!rootNode) {
        return NO;
    }
    
    //二叉樹深度
    NSInteger depth = [self depthOfTree:rootNode];
    //二叉樹葉子節點數
    NSInteger leafNum = [self numberOfLeafsInTree:rootNode];
    
    //滿二叉樹特性:葉子數=2^(深度-1)
    if (leafNum == pow(2, (depth - 1))) {
        return YES;
    }
    return NO;
}
複製代碼

判斷二叉樹是否平衡二叉樹

平衡二叉樹定義爲:它是一棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,並且左右兩個子樹都是一棵平衡二叉樹。平衡二叉樹又叫AVL樹。

複製代碼
/**
 *  是否平衡二叉樹
 *  平衡二叉樹:即AVL樹,它是一棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,並且左右兩個子樹都是一棵平衡二叉樹
 *
 *  @param rootNode 根節點
 *
 *  @return YES:平衡二叉樹,NO:非平衡二叉樹
 */
+ (BOOL)isAVLBinaryTree:(BinaryTreeNode *)rootNode {
    static NSInteger height;
    if (!rootNode) {
        height = 0;
        return YES;
    }
    if (!rootNode.leftNode && !rootNode.rightNode) {
        height = 1;
        return YES;
    }
    
    BOOL isAVLLeft = [self isAVLBinaryTree:rootNode.leftNode];
    NSInteger heightLeft = height;
    BOOL isAVLRight = [self isAVLBinaryTree:rootNode.rightNode];
    NSInteger heightRight = height;
    
    height = MAX(heightLeft, heightRight)+1;
    
    if (isAVLLeft && isAVLRight && ABS(heightLeft-heightRight) <= 1) {
        return YES;
    }
    return NO;
}
複製代碼

發佈了95 篇原創文章 · 獲贊 20 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章