二叉树

什么是二叉树?

在计算机科学中,二叉树是每个节点最多有两个子树的树结构。通常子树被称作“左子树”和“右子树”,左子树和右子树同时也是二叉树。二叉树的子树有左右之分,并且次序不能任意颠倒。二叉树是递归定义的,所以一般二叉树的相关题目也都可以使用递归的思想来解决,当然也有一些可以使用非递归的思想解决,我下面列出的一些算法有些采用了递归,有些是非递归的。

什么是二叉排序树?

二叉排序树又叫二叉查找树或者二叉搜索树,它首先是一个二叉树,而且必须满足下面的条件:

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值