美文网首页
数据结构与算法:二叉树

数据结构与算法:二叉树

作者: 妖精的菩萨 | 来源:发表于2019-05-06 20:22 被阅读0次

    在计算机科学中,二叉树(Binary Tree)是包含n个节点的有限集合,该集合或者为空集(此时,二叉树称为空树),或者由一个根节点和两棵互不相交的、分别称为根节点的左子树和右子树的二叉树组成。

    前文

    本文主要讲述了对二叉排序树的创建及其它操作,二叉排序树具有以下几个特点:

    1、若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
    2、若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
    3、任意节点的左、右子树也分别为二叉查找树;
    4、没有键值相等的节点。
    

    接下来我们通过用OC代码实现的方式完成对二叉排序树的一些基础操作,其中主要就是考察我们的递归思想。
    首先我们以二叉树的节点为一个类创建一个类,这个类拥有两个属性,分别是左节点和右节点。如下所示:

    @interface DyBinaryTreeNode : NSObject
    
    @property (nonatomic, strong) DyBinaryTreeNode *leftNode;
    @property (nonatomic, strong) DyBinaryTreeNode *rightNode;
    

    接着,我们便可以依次定义并实现其操作方法了。

    0x01 创建二叉排序树

    1、创建二叉树
    /
     *  @param values 数组
     *  @return 二叉树根节点
     */
    + (DyBinaryTreeNode *)createTreeWithValues:(NSArray *)values {
        DyBinaryTreeNode *root = nil;
        for (NSInteger i=0; i<values.count; i++) {
            NSInteger value = [(NSNumber *)[values objectAtIndex:i] integerValue];
            root = [DyBinaryTreeNode addTreeNode:root value:value];
        }
        return root;
    }
    

    我们通过以下方法调用:

    NSArray *arr = [NSArray arrayWithObjects:@(7),@(6),@(3),@(2),@(1),@(9),@(10),@(12),@(14),@(4),@(15),nil];
    DyBinaryTreeNode *tree = [DyBinaryTreeNode new];
    tree = [DyBinaryTreeNode createTreeWithValues:arr];
    

    会生成如下图所示的二叉排序树:


    2、获取某个位置的节点(层序)

    大致思路
    1、将根节点加到队列中,当队列有元素时循环。
    2、循环一次将队列的第一个元素出队列,index-1
    3、当index=0时返回此时的根节点。
    4、遍历队列第一个元素(根节点)的左节点和右节点,加到队列中。

     /*  @param index    按层次遍历树时的位置(从0开始算)
     *  @param rootNode 树根节点
     *  @return 节点
     */
    + (DyBinaryTreeNode *)treeNodeAtIndex:(NSInteger)index inTree:(DyBinaryTreeNode *)rootNode {
        //按层次遍历
        if (!rootNode || index < 0) {
            return nil;
        }
        //数组当成队列
        NSMutableArray *queueArray = [NSMutableArray array];
        //压入根节点
        [queueArray addObject:rootNode];
        while (queueArray.count > 0) {
            DyBinaryTreeNode *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;
    }
    

    调用当index==8时,打印的值为1。

    DyBinaryTreeNode *tree1 = [DyBinaryTreeNode treeNodeAtIndex:8 inTree:tree];
    NSLog(@"节点值为==%ld\n",tree1.value);
    

    0x02 二叉树遍历

    分为先序遍历、中序遍历、后序遍历、层序遍历。
    先、中、后主要是指遍历根的顺序,如果先遍历根便是先序,其它类似。左节点和右节点的遍历顺序固定(先左后右)。
    层序遍历:从上至下,从左至右依次遍历。
    可参考wiki的树的遍历

    1、先序遍历

    先访问根,再遍历左子树,再遍历右子树。典型的递归思想。
    通过block,依次将遍历到的node值回调回去。

    / *  
     根-左-右
    @param rootNode 根节点
     *  @param handler  访问节点处理函数
     */
    + (void)preOrderTraverseTree:(DyBinaryTreeNode *)rootNode handler:(void(^)(DyBinaryTreeNode *treeNode))handler {
        if (rootNode) {
            if (handler) {
                handler(rootNode);//根
            }
            [DyBinaryTreeNode preOrderTraverseTree:rootNode.leftNode handler:handler];//左
            [DyBinaryTreeNode preOrderTraverseTree:rootNode.rightNode handler:handler];//右
        }
    }
    
    2、中序遍历

    先遍历左子树,再访问根,再遍历右子树

    /**
     *  左 根 右
     *  @param rootNode 根节点
     *  @param handler  访问节点处理函数
     */
    + (void)inOrderTraverseTree:(DyBinaryTreeNode *)rootNode handler:(void(^)  (DyBinaryTreeNode *treeNode))handler {
        if (rootNode) {
            [DyBinaryTreeNode inOrderTraverseTree:rootNode.leftNode handler:handler];//左
            if (handler) {
                handler(rootNode);//中
            }
            [DyBinaryTreeNode inOrderTraverseTree:rootNode.rightNode handler:handler];//右
        }
    }
    
    3、后序遍历

    先遍历左子树,再遍历右子树,再访问根

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

    以上三种遍历方式依次打印为:

    先序遍历结果:7,6,3,2,1,4,9,10,12,14,15
    中序遍历结果:1,2,3,4,6,7,9,10,12,14,15
    后序遍历结果:1,2,4,3,6,15,14,12,10,9,7
    
    3、层序遍历

    同上根据index获取节点的值的思想一样。
    通过数组模拟队列,进行遍历操作。

    + (void)levelTraverseTree:(DyBinaryTreeNode *)rootNode handler:(void(^)(DyBinaryTreeNode *treeNode))handler {
        if (!rootNode) {
            return;
        }
        NSMutableArray *queueArray = [NSMutableArray array]; //数组当成队列
        [queueArray addObject:rootNode]; //压入根节点
        while (queueArray.count > 0) {
            DyBinaryTreeNode *node = [queueArray firstObject];
            if (handler) {
                handler(node);
            }
            [queueArray removeObjectAtIndex:0]; //弹出最前面的节点,仿照队列先进先 出原则
            if (node.leftNode) {
                [queueArray addObject:node.leftNode]; //压入左节点
            }
            if (node.rightNode) {
                [queueArray addObject:node.rightNode]; //压入右节点
            }
        }
    }
    

    0x03 获取二叉树的属性

    1、获取二叉树的深度

    二叉树的深度
    从根节点到叶子节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度包含的节点数为树的深度。
    可以理解为二叉树有多少层。

    递归思想:二叉树的深度 = MAX(左子树深度,右子树深度)+1
    1:根节点。

    /*
     *  @param rootNode 二叉树根节点
     *
     *  @return 二叉树的深度
     */
    + (NSInteger)depthOfTree:(DyBinaryTreeNode *)rootNode {
        if (!rootNode) {
            return 0;
        }
        if (!rootNode.leftNode && !rootNode.rightNode) {
            return 1;
        }
        
        //左子树深度
        NSInteger leftDepth = [DyBinaryTreeNode depthOfTree:rootNode.leftNode];
        //右子树深度
        NSInteger rightDepth = [DyBinaryTreeNode depthOfTree:rootNode.rightNode];
        
        return MAX(leftDepth, rightDepth) + 1;
    }
    
    
    2、获取二叉树的宽度

    队列的最大长度,结点数最多的层的结点数。
    思想:同层序遍历,保存队列中最多元素时的元素个数。

    /*
     *  @param rootNode 二叉树根节点
     *
     *  @return 二叉树宽度
     */
    + (NSInteger)widthOfTree:(DyBinaryTreeNode *)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++) {
                DyBinaryTreeNode *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;
    }
    
    3、获取二叉树的节点数

    递归思想:节点数=左子树节点数+右子树节点数+1(根节点)

    + (NSInteger)numberOfNodesInTree:(DyBinaryTreeNode *)rootNode {
        if (!rootNode) {
            return 0;
        }
        //节点数=左子树节点数+右子树节点数+1(根节点)
        return [DyBinaryTreeNode numberOfNodesInTree:rootNode.leftNode] + [DyBinaryTreeNode numberOfNodesInTree:rootNode.rightNode] + 1;
    }
    
    4、获取二叉树某一层的节点数

    递归思想:level层节点数 = 左子树level-1层节点数+右子树level-1层节点数

    + (NSInteger)numberOfNodesOnLevel:(NSInteger)level inTree:(DyBinaryTreeNode *)rootNode {
        if (!rootNode || level < 1) { //根节点不存在或者level<0
            return 0;
        }
        if (level == 1) { //level=1,返回1(根节点)
            return 1;
        }
        //递归:level层节点数 = 左子树level-1层节点数+右子树level-1层节点数
        return [DyBinaryTreeNode numberOfNodesOnLevel:level-1 inTree:rootNode.leftNode] + [DyBinaryTreeNode numberOfNodesOnLevel:level-1 inTree:rootNode.rightNode];
    }
    
    5、获取叶子节点数

    叶子节点:左子树和右子树都是空的节点。
    递归思想:叶子数 = 左子树叶子数 + 右子树叶子数

    + (NSInteger)numberOfLeafsInTree:(DyBinaryTreeNode *)rootNode {
        if (!rootNode) {
            return 0;
        }
        //左子树和右子树都是空,说明是叶子节点
        if (!rootNode.leftNode && !rootNode.rightNode) {
            return 1;
        }
        //递归:叶子数 = 左子树叶子数 + 右子树叶子数
        return [DyBinaryTreeNode numberOfLeafsInTree:rootNode.leftNode] + [DyBinaryTreeNode numberOfLeafsInTree:rootNode.rightNode];
    }
    
    6、获取二叉树的最大直径

    二叉树看成一个图,父子节点之间的连线看成是双向的,定义“距离”为两个节点之间的边数。

    递归思想:
    分为三种情况
    1、最远距离经过根节点:距离 = 左子树深度 + 右子树深度
    2、最远距离在根节点左子树上,即计算左子树最远距离
    3、最远距离在根节点右子树上,即计算右子树最远距离

    + (NSInteger)maxDistanceOfTree:(DyBinaryTreeNode *)rootNode {
        if (!rootNode) {
            return 0;
        }
        //方案一:(递归次数较多,效率较低)
        NSInteger distance = [DyBinaryTreeNode depthOfTree:rootNode.leftNode] + [DyBinaryTreeNode depthOfTree:rootNode.rightNode];
        NSInteger disLeft = [DyBinaryTreeNode maxDistanceOfTree:rootNode.leftNode];
        NSInteger disRight = [DyBinaryTreeNode maxDistanceOfTree:rootNode.rightNode];
        
        return MAX(MAX(disLeft, disRight), distance);
    }
    

    0x04 翻转二叉树

    又叫:二叉树的镜像。交换节点的左节点和右节点。

    1、递归翻转

    递归思想:先交换当前节点的左右节点,再递归调用去翻转以左右节点为根节点的二叉树。

    + (DyBinaryTreeNode *)invertBinaryTree:(DyBinaryTreeNode *)rootNode {
        if (!rootNode) {
            return nil;
        }
        if (!rootNode.leftNode && !rootNode.rightNode) {
            return rootNode;
        }
        //交换当前节点的左右节点。
        DyBinaryTreeNode *tempNode = rootNode.leftNode;
        rootNode.leftNode = rootNode.rightNode;
        rootNode.rightNode = tempNode;
        
        //递归交换左右子节点
        [DyBinaryTreeNode invertBinaryTree:rootNode.leftNode];
        [DyBinaryTreeNode invertBinaryTree:rootNode.rightNode];
        
        return rootNode;
    }
    
    2、非递归翻转

    思想:类似于层序遍历,将即将入队列的节点交换其左右节点。

    /**
     * 非递归方式翻转
     */
    + (DyBinaryTreeNode *)invertBinaryTreeNot:(DyBinaryTreeNode *)rootNode {
        if (!rootNode) {  return nil; }
        if (!rootNode.leftNode && !rootNode.rightNode) {  return rootNode; }
        
        NSMutableArray *queueArray = [NSMutableArray array]; //数组当成队列
        [queueArray addObject:rootNode]; //压入根节点
        while (queueArray.count > 0) {
            DyBinaryTreeNode *node = [queueArray firstObject];
            [queueArray removeObjectAtIndex:0]; //弹出最前面的节点,仿照队列先进先出原则
            
            DyBinaryTreeNode *pLeft = node.leftNode;
            node.leftNode = node.rightNode;
            node.rightNode = pLeft;
            
            if (node.leftNode) {
                [queueArray addObject:node.leftNode];
            }
            if (node.rightNode) {
                [queueArray addObject:node.rightNode];
            }
            
        }
        return rootNode;
    }
    
    

    备注

    关于二叉树的基本算法先介绍到此处,后续会补充更多关于二叉树操作的一些算法。

    相关文章

      网友评论

          本文标题:数据结构与算法:二叉树

          本文链接:https://www.haomeiwen.com/subject/maodoqtx.html