二叉树入门

去年二叉树算法的事情闹的沸沸扬扬,起因是Homebrew 的作者 @Max Howell 在 twitter 上发表了如下一内容:

Google: 90% of our engineers use the software you wrote (Homebrew), but you can’t invert a binary tree on a whiteboard so fuck off.

事情大概是说,Max Howell 去 Google 面试,面试官说:虽然在 Google 有 90% 的工程师用你写的 Homebrew,但是你居然不能在白板上写出翻转二叉树的代码,所以滚蛋吧。
虽然我不是科班出身,但是因为工作原因,算法也有所涉猎,虽然懂的不多,但是Max大神居然答不出二叉树的问题,作为小透明还是应该复习下二叉树的相关基础知识的。

什么是二叉树

在计算机科学中,二叉树是每个节点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。二叉树常被用于实现二叉查找树和二叉堆。
——百度百科
二叉树的子树有左右之分,并且次序不能任意颠倒。二叉树是递归定义的,所以一般二叉树的相关题目也都可以使用递归的思想来解决,当然也有一些可以使用非递归的思想解决.

什么是二叉排序树?

二叉排序树又叫二叉查找树或者二叉搜索树,它首先是一个二叉树,而且必须满足下面的条件:
1)若左子树不空,则左子树上所有结点的值均小于它的根节点的值;
2)若右子树不空,则右子树上所有结点的值均大于它的根结点的值
3)左、右子树也分别为二叉排序树
4)没有键值相等的节点
以上概念来自网上,下面上代码
可以看我封装的二叉树分类,会比文章里稍微清晰些
Masazumi柒的github——二叉树学习

二叉树节点定义

采用单项链表的形式,只从根节点指向孩子节点,不保存父节点。

将二叉树节点设为model,以下为.h
/**
 *  二叉树节点
 */
@interface BinaryTreeNode : NSObject

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

创建二叉排序树

二叉树中左右节点值本身没有大小之分,所以如果要创建二叉树,就需要考虑如何处理某个节点是左节点还是右节点,如何终止某个子树而切换到另一个子树。 因此我选择了二叉排序树,二叉排序树中对于左右节点有明确的要求,程序可以自动根据键值大小自动选择是左节点还是右节点。(以下默认先为.h文件中的方法声明,后为.m的方法实现)

/**
 *  创建二叉排序树
 *  二叉排序树:左节点值全部小于根节点值,右节点值全部大于根节点值
 *
 *  @param values 数组
 *
 *  @return 二叉树根节点
 */
+ (TyBinaryTreeNode *)createTreeWithValues:(NSArray *)values;
/**
 *  向二叉排序树节点添加一个节点
 *
 *  @param treeNode 根节点
 *  @param value    值
 *
 *  @return 根节点
 */
+ (TyBinaryTreeNode *)addTreeNode:(TyBinaryTreeNode *)treeNode value:(NSInteger)value;

#pragma mark - 创建二叉排序树
+ (TyBinaryTreeNode *)createTreeWithValues:(NSArray *)values {
    
    TyBinaryTreeNode *root = nil;
    for (NSInteger i=0; i<values.count; i++) {
        NSInteger value = [(NSNumber *)[values objectAtIndex:i] integerValue];
        root = [TyBinaryTree addTreeNode:root value:value];
    }
    return root;
}

#pragma mark -  向二叉排序树节点添加一个节点
+ (TyBinaryTreeNode *)addTreeNode:(TyBinaryTreeNode *)treeNode value:(NSInteger)value {
    //根节点不存在,创建节点
    if (!treeNode) {
        treeNode = [TyBinaryTreeNode new];
        treeNode.value = value;
        NSLog(@"node:%@", @(value));
    }
    else if (value <= treeNode.value) {
        NSLog(@"to left");
        //值小于根节点,则插入到左子树
        treeNode.leftNode = [TyBinaryTree addTreeNode:treeNode.leftNode value:value];
    }
    else {
        NSLog(@"to right");
        //值大于根节点,则插入到右子树
        treeNode.rightNode = [TyBinaryTree addTreeNode:treeNode.rightNode value:value];
    }
    
    return treeNode;
}

二叉树中某个位置的节点

类似索引操作,按层次遍历,位置从0开始算。

/**
 *  二叉树中某个位置的节点(按层次遍历)
 *
 *  @param index    按层次遍历树时的位置(从0开始算)
 *  @param rootNode 树根节点
 *
 *  @return 节点
 */
+ (TyBinaryTreeNode *)treeNodeAtIndex:(NSInteger)index inTree:(TyBinaryTreeNode *)rootNode;

#pragma mark - 二叉树中某个位置的节点(按层次遍历)
+ (TyBinaryTreeNode *)treeNodeAtIndex:(NSInteger)index inTree:(TyBinaryTreeNode *)rootNode {
    //按层次遍历
    if (!rootNode || index < 0) {
        return nil;
    }
    
    NSMutableArray *queueArray = [NSMutableArray array]; //数组当成队列
    [queueArray addObject:rootNode]; //压入根节点
    while (queueArray.count > 0) {
        
        TyBinaryTreeNode *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:(TyBinaryTreeNode *)rootNode handler:(void(^)(TyBinaryTreeNode *treeNode))handler;

#pragma mark - 先序遍历
+ (void)preOrderTraverseTree:(TyBinaryTreeNode *)rootNode handler:(void(^)(TyBinaryTreeNode *treeNode))handler {
    if (rootNode) {
        
        if (handler) {
            handler(rootNode);
        }
        
        [self preOrderTraverseTree:rootNode.leftNode handler:handler];
        [self preOrderTraverseTree:rootNode.rightNode handler:handler];
    }
}
/* 调用方法
 NSMutableArray *orderArray = [NSMutableArray array];
 [TyBinaryTree preOrderTraverseTree:root handler:^(BinaryTreeNode *treeNode) {
 [orderArray addObject:@(treeNode.value)];
 }];
 
 */

中序遍历

先遍历左子树,再访问根,再遍历右子树。
对于二叉排序树来说,中序遍历得到的序列是一个从小到大排序好的序列。

/**
 *  中序遍历
 *  先遍历左子树,再访问根,再遍历右子树
 *
 *  @param rootNode 根节点
 *  @param handler  访问节点处理函数
 */
+ (void)inOrderTraverseTree:(TyBinaryTreeNode *)rootNode handler:(void(^)(TyBinaryTreeNode *treeNode))handler;

#pragma mark - 中序遍历
+ (void)inOrderTraverseTree:(TyBinaryTreeNode *)rootNode handler:(void(^)(TyBinaryTreeNode *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:(TyBinaryTreeNode *)rootNode handler:(void(^)(TyBinaryTreeNode *treeNode))handler;

#pragma mark -  后序遍历
+ (void)postOrderTraverseTree:(TyBinaryTreeNode *)rootNode handler:(void(^)(TyBinaryTreeNode *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:(TyBinaryTreeNode *)rootNode handler:(void(^)(TyBinaryTreeNode *treeNode))handler;

#pragma mark -  层次遍历(广度优先)
+ (void)levelTraverseTree:(TyBinaryTreeNode *)rootNode handler:(void(^)(TyBinaryTreeNode *treeNode))handler {
    if (!rootNode) {
        return;
    }
    
    NSMutableArray *queueArray = [NSMutableArray array]; //数组当成队列
    [queueArray addObject:rootNode]; //压入根节点
    while (queueArray.count > 0) {
        
        TyBinaryTreeNode *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:(TyBinaryTreeNode *)rootNode;

#pragma mark - 二叉树的深度
+ (NSInteger)depthOfTree:(TyBinaryTreeNode *)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:(TyBinaryTreeNode *)rootNode;

#pragma mark - 二叉树的宽度
+ (NSInteger)widthOfTree:(TyBinaryTreeNode *)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++) {
            TyBinaryTreeNode *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:(TyBinaryTreeNode *)rootNode;

#pragma mark - 二叉树的所有节点数
+ (NSInteger)numberOfNodesInTree:(TyBinaryTreeNode *)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:(TyBinaryTreeNode *)rootNode;

#pragma mark - 二叉树某层中的节点数
+ (NSInteger)numberOfNodesOnLevel:(NSInteger)level inTree:(TyBinaryTreeNode *)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:(TyBinaryTreeNode *)rootNode;

#pragma mark - 二叉树叶子节点数
+ (NSInteger)numberOfLeafsInTree:(TyBinaryTreeNode *)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:(TyBinaryTreeNode *)rootNode;

//#pragma mark - 二叉树最大距离(直径)
//+ (NSInteger)maxDistanceOfTree:(TyBinaryTreeNode *)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);
//}
#pragma mark - 二叉树最大距离(直径)
+ (NSInteger)maxDistanceOfTree:(TyBinaryTreeNode *)rootNode {
    if (!rootNode) {
        return 0;
    }
    //    方案2:将计算节点深度和最大距离放到一次递归中计算,方案一是分别单独递归计算深度和最远距离
    TyTreeNodeProperty *p = [self propertyOfTreeNode:rootNode];
    return p.distance;
}

#pragma mark  计算树节点的最大深度和最大距离
+ (TyTreeNodeProperty *)propertyOfTreeNode:(TyBinaryTreeNode *)rootNode {
    
    if (!rootNode) {
        return nil;
    }
    
    TyTreeNodeProperty *left = [self propertyOfTreeNode:rootNode.leftNode];
    TyTreeNodeProperty *right = [self propertyOfTreeNode:rootNode.rightNode];
    TyTreeNodeProperty *p = [TyTreeNodeProperty 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:(TyBinaryTreeNode *)treeNode inTree:(TyBinaryTreeNode *)rootNode;

#pragma mark - 二叉树中某个节点到根节点的路径
+ (NSArray *)pathOfTreeNode:(TyBinaryTreeNode *)treeNode inTree:(TyBinaryTreeNode *)rootNode {
    NSMutableArray *pathArray = [NSMutableArray array];
    [self isFoundTreeNode:treeNode inTree:rootNode routePath:pathArray];
    return pathArray;
}
#pragma mark 查找某个节点是否在树中
+ (BOOL)isFoundTreeNode:(TyBinaryTreeNode *)treeNode inTree:(TyBinaryTreeNode *)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 最近的公共父节点
 */
+ (TyBinaryTreeNode *)parentOfNode:(TyBinaryTreeNode *)nodeA andNode:(TyBinaryTreeNode *)nodeB inTree:(TyBinaryTreeNode *)rootNode;

#pragma mark - 二叉树中两个节点最近的公共父节点
+ (TyBinaryTreeNode *)parentOfNode:(TyBinaryTreeNode *)nodeA andNode:(TyBinaryTreeNode *)nodeB inTree:(TyBinaryTreeNode *)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:(TyBinaryTreeNode *)nodeA toNode:(TyBinaryTreeNode *)nodeB inTree:(TyBinaryTreeNode *)rootNode;

#pragma mark - 二叉树中两个节点之间的路径
+ (NSArray *)pathFromNode:(TyBinaryTreeNode *)nodeA toNode:(TyBinaryTreeNode *)nodeB inTree:(TyBinaryTreeNode *)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:(TyBinaryTreeNode *)nodeA toNode:(TyBinaryTreeNode *)nodeB inTree:(TyBinaryTreeNode *)rootNode;

#pragma mark - 二叉树两个节点之间的距离
+ (NSInteger)distanceFromNode:(TyBinaryTreeNode *)nodeA toNode:(TyBinaryTreeNode *)nodeB inTree:(TyBinaryTreeNode *)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 翻转后的树根节点(其实就是原二叉树的根节点)
 */
+ (TyBinaryTreeNode *)invertBinaryTree:(TyBinaryTreeNode *)rootNode;

#pragma mark - 翻转二叉树(又叫:二叉树的镜像)
+ (TyBinaryTreeNode *)invertBinaryTree:(TyBinaryTreeNode *)rootNode {
    if (!rootNode) {
        return nil;
    }
    if (!rootNode.leftNode && !rootNode.rightNode) {
        return rootNode;
    }
    
    [self invertBinaryTree:rootNode.leftNode];
    [self invertBinaryTree:rootNode.rightNode];
    
    TyBinaryTreeNode *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:(TyBinaryTreeNode *)rootNode;

#pragma mark - 是否完全二叉树
+ (BOOL)isCompleteBinaryTree:(TyBinaryTreeNode *)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) {
        TyBinaryTreeNode *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:(TyBinaryTreeNode *)rootNode;

#pragma mark - 是否满二叉树
+ (BOOL)isFullBinaryTree:(TyBinaryTreeNode *)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:(TyBinaryTreeNode *)rootNode;

#pragma mark - 是否平衡二叉树
+ (BOOL)isAVLBinaryTree:(TyBinaryTreeNode *)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;
}

参考文章

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • 树的概述 树是一种非常常用的数据结构,树与前面介绍的线性表,栈,队列等线性结构不同,树是一种非线性结构 1.树的定...
    Jack921阅读 4,434评论 1 31
  • 四、树与二叉树 1. 二叉树的顺序存储结构 二叉树的顺序存储就是用数组存储二叉树。二叉树的每个结点在顺序存储中都有...
    MinoyJet阅读 1,505评论 0 7
  • B树的定义 一棵m阶的B树满足下列条件: 树中每个结点至多有m个孩子。 除根结点和叶子结点外,其它每个结点至少有m...
    文档随手记阅读 13,177评论 0 25
  • 树和二叉树 1、树的定义 树(Tree)是由一个 或 多个结点 组成的有限集合T,且满足: ①有且仅有一个称为根的...
    利伊奥克儿阅读 1,349评论 0 1
  • 第一面,勇于攀登: 当今社会,有太多人是含着金钥匙出生,过着衣食无忧,饭来张口,衣来伸手的生活。虽是成年人,过着"...
    卉蓝洁阅读 875评论 10 6