二叉树

165 阅读10分钟

/**
 二叉树
     采用单项链表的形式,只从根节点指向孩子节点,不保存父节点。
 */
@interface BinaryTreeNode : NSObject

/**
 值
 */
@property (nonatomic, assign) NSInteger value;

/**
 左节点
 */
@property (nonatomic, strong) BinaryTreeNode *leftNode;

/**
 右节点
 */
@property (nonatomic, strong) BinaryTreeNode *rightNode;


@end



#pragma mark -- 创建二叉排序树
/**
 创建二叉排序树
 二叉树中左右节点没有大小之分,所以如果要创建二叉树,就需要考虑如何处理某个节点是做节点还是右节点,如何终止某个子树切换到另一个子树。因此我选择了二叉排序树,二叉排序树对于左右节点有明确的要求,程序可以自动根据键值大小自动选择是左节点还是右节点。

 二叉排序树规则:
 1)若左子树不空,则左子树上所有节点的值均小于他的根节点的值
 2)若右子树不空,则右子树上所有节点的值均大于他的根节点的值
 3)没有键值相等的节点
 
 */
+ (BinaryTreeNode *)createTreeWithValues:(NSArray *)values
{
    BinaryTreeNode *root = nil;
    for (NSInteger i = 0; i < values.count; i++) {
        
        NSInteger value = [[values objectAtIndex:i]integerValue];
        root = [BinaryTreeNode 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;
        
    }else if (value <= treeNode.value){
        
        //值小于根节点,插入到左子树
        treeNode.leftNode = [BinaryTreeNode addTreeNode:treeNode.leftNode value:value];
    }else{
        
        //值大于根节点,插入到右子树
        treeNode.rightNode = [BinaryTreeNode addTreeNode:treeNode.rightNode value:value];
    }
    return treeNode;
}


#pragma mark -- 二叉树中某个位置的节点
/**
 查找二叉树中某个位置的节点

 @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;
}



#pragma mark -- 先序遍历
/**
 先序遍历
 先访问跟,再遍历左子树,再遍历右子树

 @param rootNode 根节点
 @param handler 访问节点处理函数
 */
+ (void)preOrderTraverseTree:(BinaryTreeNode *)rootNode handler:(void(^)(BinaryTreeNode *treeNode))handler
{
    if (rootNode) {
        
        if (handler) {
            handler(rootNode);
        }
        
        [BinaryTreeNode preOrderTraverseTree:rootNode.leftNode handler:handler];
        [BinaryTreeNode preOrderTraverseTree:rootNode.rightNode handler:handler];
    }
}


#pragma mark -- 中序遍历
/**
 中序遍历
 先遍历左子树,再访问根节点,再遍历右子树

 @param rootNode 根节点
 @param handler 访问节点处理函数
 */
+ (void)inOrderTraverseTree:(BinaryTreeNode *)rootNode handler:(void(^)(BinaryTreeNode *treeNode))handler
{
    if (rootNode) {
        
        [BinaryTreeNode inOrderTraverseTree:rootNode.leftNode handler:handler];
        
        if (handler) {
            handler(rootNode);
        }
        
        [BinaryTreeNode inOrderTraverseTree:rootNode.rightNode handler:handler];
    }
}


#pragma mark -- 后续遍历
/**
 后序遍历
 先遍历左子树,再遍历右子树,再访问根节点

 @param rootNode 根节点
 @param handler 访问节点处理函数
 */
+ (void)postOrderTraverseTree:(BinaryTreeNode *)rootNode handler:(void(^)(BinaryTreeNode *treeNode))handler
{
    if (rootNode) {
        
        [BinaryTreeNode postOrderTraverseTree:rootNode.leftNode handler:handler];
        
        [BinaryTreeNode postOrderTraverseTree:rootNode.rightNode handler:handler];
        
        if (handler) {
            handler(rootNode);
        }
    }
}


#pragma mark -- 层次遍历
/**
 层次遍历(广度优先)

 @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];  //压入右节点
        }
    }
}


#pragma mark -- 二叉树的深度
/**
 二叉树的深度定义为:从根节点到叶子结点依次经过的结点形成树的一条路径,最长路径的长度为树的深度。
 
 1)如果根节点为空,则深度为0;
 2)如果左右节点都是空,则深度为1;
 3)递归思想:二叉树的深度=max(左子树的深度,右子树的深度)+ 1

 @param rootNode 根节点
 @return 二叉树的深度
 */
+ (NSInteger)depthOfTree:(BinaryTreeNode *)rootNode
{
    if (!rootNode) {    //如果根节点不存在,返回深度0
        return 0;
    }
    
    if (!rootNode.leftNode && !rootNode.rightNode){ //如果节点不存在左右子树,返回深度1
        return 1;
    }
    
    //递归获得左子树的深度
    NSInteger leftDepth = [BinaryTreeNode depthOfTree:rootNode.leftNode];
    
    //递归获得右子树的深度
    NSInteger rightDepth = [BinaryTreeNode depthOfTree:rootNode.rightNode];
    
    //取左右子树的最大值 + 1
    return MAX(leftDepth, rightDepth) + 1;
    
}


#pragma mark -- 二叉树的宽度
/**
 二叉树的宽度定义为各层节点数的最大值

 @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;
}


#pragma mark -- 二叉树的所有节点数
/**
 二叉树的所有节点数 = 左子树节点数 + 右子树节点数 + 1(根节点)

 @param rootNode 根节点
 @return 二叉树所有节点数
 */
+ (NSInteger)numberOfNodesInTree:(BinaryTreeNode *)rootNode
{
    if (!rootNode) {
        return 0;
    }
    
    return [BinaryTreeNode numberOfNodesInTree:rootNode.leftNode] + [BinaryTreeNode numberOfNodesInTree:rootNode.rightNode] + 1;
}


#pragma mark -- 二叉树某层中的节点数
/**
 二叉树某层中的节点数
 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) {
        return 0;
    }
    if (level == 1) {
        return 1;
    }
    
    return [BinaryTreeNode numberOfNodesOnLevel:level-1 inTree:rootNode.leftNode] + [BinaryTreeNode numberOfNodesOnLevel:level-1 inTree:rootNode.rightNode];
}


#pragma mark -- 二叉树叶子节点数
/**
 二叉树叶子节点数
 1)根节点不存在,叶子节点数为0
 2)左子树和右子树都是空(终端节点),说明是叶子节点
 3)递归:叶子数 = 左子树叶子数 + 右子树叶子数

 @param rootNode 根节点
 @return 二叉树叶子节点数
 */
+ (NSInteger)numberOfLeafsInTree:(BinaryTreeNode *)rootNode
{
    if (!rootNode) {
        return 0;
    }
    if (!rootNode.leftNode && !rootNode.rightNode) {
        return 1;
    }
    return [BinaryTreeNode numberOfLeafsInTree:rootNode.leftNode] + [BinaryTreeNode numberOfLeafsInTree:rootNode.rightNode];
}

#pragma mark -- 二叉树最大距离(二叉树的直径)
//方案一
/**
 二叉树中任意两个节点,都有且仅有一条路径,这个路径的长度叫这两个节点的距离。二叉树中所有节点之间距离最长的就是二叉树的直径
 
 有一种解法,把这个最大距离划分为3中情况
 
 1)这2个节点分别在左子树和右子树上,他们之间的路径肯定经过根节点,而且他们肯定是根节点左右子树上最远的叶子节点(他们到根节点的距离 = 左右子树的深度)
 2)这2个节点都在左子树上
 3)这2个节点都在右子树上
 */
/**
 方案一:(递归次数较多,效率较低)
 
 这个方案相率较低,因为计算子树的深度和最远距离是分开递归的,存在重复递归遍历的情况。

 @param rootNode 根节点
 @return 最大距离
 */
+ (NSInteger)maxDistanceOfTree:(BinaryTreeNode *)rootNode
{
    if (!rootNode) {
        return 0;
    }
    //最远距离经过根节点:距离 = 左子树深度 + 右子树深度
    NSInteger distance = [BinaryTreeNode depthOfTree:rootNode.leftNode] + [BinaryTreeNode depthOfTree:rootNode.rightNode];
    //最远距离在根节点左子树上,即计算左子树最远距离
    NSInteger disLeft = [BinaryTreeNode maxDistanceOfTree:rootNode.leftNode];
    //最远距离在根节点有字数上,即计算右子树最远距离
    NSInteger disRight = [BinaryTreeNode maxDistanceOfTree:rootNode.rightNode];
    
    return MAX(MAX(disLeft, disRight), distance);
}

/**
 方案二

 @param rootNode 根节点
 @return 最大距离
 */
+ (NSInteger)maxDistanceOfTree1:(BinaryTreeNode *)rootNode
{
    if (!rootNode) {
        return 0;
    }
    
    TreeNodeProperty *p = [self propertyOfTreeNode:rootNode];
    return p.distance;
}

+ (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;
}


#pragma mark -- 翻转二叉树
/**
 翻转二叉树
 又叫求二叉树的镜像,即二叉树的左右子树对调

 @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;
}

#pragma mark -- 二叉树中某个节点到根节点的路径
/**
 二叉树中某个节点到根节点的路径
 
 既是寻路问题,又是查找节点问题。
 
 定义一个存放路径的栈(不是队列了,但是还是用可变数组来实现的)
 
 1)压入根节点,再从左子树中查找(递归进行的),如果未找到,再从右子树中查找,如果也未找到,则弹出根节点,再遍历栈中上一个节点。
 2)如果找到,则栈中存放的节点就是路径所经过的节点。
 */

#pragma mark -- 二叉树中两个节点最近的公共父节点
/**
 二叉树中两个节点最近的公共父节点
 
 首先需要明白,根节点肯定是二叉树中任意两个节点的公共父节点(不一定是最近的),因此二叉树中2个节点的最近公共父节点一定在从根节点到这个节点的路径上。因此我们可以先分别找到从根节点到这2个节点的路径,再从这两个路径中找到最近的公共父节点。
 */

#pragma mark --  二叉树中两个节点之间的路径
/**
 二叉树中两个节点之间的路径
 
 从查找最近公共父节点衍生出来的。
 */

#pragma mark -- 二叉树两个节点之间的距离
/**
 二叉树两个节点之间的距离
 
 可以从两个节点之间的路径衍生出来。
 */

#pragma mark -- 判断二叉树是否完全二叉树
/**
 判断二叉树是否完全二叉树
 
 完全二叉树定义为:若设二叉树的高度为h,除第h层外,其它各层的结点数都达到最大个数,第h层有叶子结点,并且叶子结点都是从左到右依次排布。
 
 完全二叉树必须满足2个条件:
 
 1)如果某个节点的右子树不为空,则它的左子树必须不为空
 2)如果某个节点的右子树为空,则排在它后面的节点必须没有孩子节点
 
 这里还需要理解“排在它后面的节点”,回头看看层次遍历算法,我们就能知道在层次遍历时,是从上到下从左到右遍历的,先将根节点弹出队列,再压入孩子节点,因此“排在它后面的节点”有2种情况:
 
 1)同层次的后面的节点
 2)同层次的前面的节点的孩子节点(因为遍历前面的节点时,会弹出节点,同时将孩子节点压入队列)
 
 通过上面的分析,我们可以设置一个标志位flag,当子树满足完全二叉树时,设置flag=YES。当flag=YES而节点又破坏了完全二叉树的条件,那么它就不是完全二叉树。
 */



#pragma mark -- 判断二叉树是否满二叉树
/**
 判断二叉树是否满二叉树
 
 满二叉树定义为:除了叶结点外每一个结点都有左右子叶且叶子结点都处在最底层的二叉树.
 满二叉树的一个特性是:叶子数=2^(深度-1),因此我们可以根据这个特性来判断二叉树是否是满二叉树。
 */


#pragma mark -- 判断二叉树是否平衡二叉树
/**
 判断二叉树是否平衡二叉树
 
 平衡二叉树定义为:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。平衡二叉树又叫AVL树。
 */