通用算法 - [树结构] -二叉树的一些基本操作(三)

1、前言

数组、链表、二叉树、动态规划、栈与队列是面试中常考的知识点,而在这几个知识点中,二叉树属于难度比较大的部分,因此在这里总结一下二叉树常考的操作,包括:
(1)二叉树的递归遍历;
(2)二叉树的非递归遍历;
(3)求二叉树叶子节点的个数;
(4)求二叉树的深度;
(5)在二叉树中查找某颗子树;
(6)求二叉树中和为某个值的路径;
(7)求二叉树中的最大路径和;
(8)二叉树的镜像;
(9)判断某个序列是否是二叉搜索树的后序遍历序列;
(10)二叉搜索树和双向链表的相互转换;
(11)树中两个结点的最近祖先;
(12)二叉树搜索树的插入与删除;
(13)堆的插入与删除;
(14)判断一棵树是不是平衡二叉树;
(15)平衡二叉树的调整;
(16)判断一棵二叉是否对称;
(17)层次遍历一棵二叉树;

2、二叉树的基本操作

(9)判断某个序列是否是二叉搜索树的后序遍历序列
基本思路:可以采用倒推的方法,如果一个序列是二叉搜索树的后序遍历序列,那么该序列会有什么样的特征?
如果一个序列是二叉树的后序遍历序列,那么它一定具有以下性质:
(1)序列可以根据序列的最后一个数number划分为两部分,一部分大于number,记为A;另一部分小于number,记为B;且A一定在B的前面,A和B中的长度可以为0;
(2)序列A、B同样遵循性质(1);

def IsBackOrderSeq(seq,start,end):

	if len(seq) <= 0:
	
		return False
		
    length = end - start + 1

    if length == 1:

        return True

    greater_index = start

    for i in range(start,end):

        if seq[i] < seq[end]:

            greater_index += 1
        
    for j in range(greater_index,end):

        if seq[j] < seq[end]:

            return False

    front_isback = True

    back_isback = True

    if greater_index - 1 >= start:

        front_isback = IsBackOrderSeq(seq,start,greater_index - 1)

    if greater_index <= end - 1:

        back_isback = IsBackOrderSeq(seq,greater_index,end - 1)


    return front_isback and back_isback

        

(10)二叉搜索树和双向链表的相互转换
输入一棵二叉排序树,将该二叉排序树转换成排序的双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。
如下图所示:
在这里插入图片描述
基本思路:我们把二叉树搜索树中每个节点的左指针作为双向链表中的前向指针,右指针看成后向指针。同时将二叉树分为三个部分,左子树,根节点,和右子树。在转换时,只需将根节点与左子树中的最大节点链接,然后再将根节点与右子树的最小节点连接。
具体地,使用指针pLasNodeInList来保存已经转换好的排序双向链表的尾节点,再使用中序遍历二叉树中的每一个节点时,不断地将当前遍历节点插入到双向链表的末尾。

简单来说,就是不断地在排序的双向链表末尾pLastNodeInList插入中序遍历的当前节点。
代码实现:

pLastNodeInList = None

def ConvertNode(rootnode):

    if rootnode == None:

        return


    ConvertNode(rootnode.left)
    
    
    global pLastNodeInList
    
    rootnode.left = pLastNodeInList

    if pLastNodeInList != None:

        pLastNodeInList.right = rootnode

    pLastNodeInList = rootnode
    

    ConvertNode(rootnode.right)
    

(11)树中两个结点的最近祖先

基本思想

利用后序遍历实现;
对于当前遍历节点curnode,如果当前节点为null或等于A、或等于B,直接返回该节点;
先处理curnode的左右子树,左子树返回left,右子树返回right,判断left和right;
1)如果left、right都不为空,则说明在curnode的左子树中找到了A或B,curnode的右子树中也找到了A或B,curnode为A和B的首次相遇点,也是A和B的最近祖先,直接返回curnode;
2)如果left和right中一个为null,另一个不为null,说明不为空的节点是A和B中的一个,直接返回不为空的节点;
3)如果left、right都为空,说明以curnode为根的子树上没有发现A或B,返回null;
在这里插入图片描述
代码实现

def getLatestAncestor(rootnode,node1,node2):

    if rootnode == None or rootnode.val == node1.val or rootnode.val == node2.val:

        return rootnode

    left = getLatestAncestor(rootnode.left,node1,node2)

    right = getLatestAncestor(rootnode.right,node1,node2)

    if left != None and right != None:

        return rootnode

    if left != None:

        return left

    if right != None:

        return right

    return None

(12)二叉树搜索树的插入与删除;
(13)堆的插入与删除;
(14)判断一棵树是不是平衡二叉树;
(15)平衡二叉树的调整;
关于上述问题,具体参考:通用算法 - [树结构] - 特殊的树结构
(16) 判断二叉树是否对称
例如:二叉树[1,2,2,4,3,3,4]是对称的:

    1
   / \
  2   2
 / \ / \
3  4 4  3

而二叉树[1,2,2,null,3,null,3]则不是镜像对称的:

    1
   / \
  2   2
   \   \
   3    3

基本思路:
如果一棵二叉树是对称的,那么它的左右子树成镜像对称;
在这里插入图片描述
接下来问题转化成如何判断两棵树成镜像对称,
经过分析(如下图所示),两棵树成镜像对称需要满足以下GAN个条件:
(1)两棵树的根节点相同;
(2)其中一棵树的右子树和另一棵树的左子树成镜像对称;
(3)其中一棵树的左子树和另一棵树的右子树成镜像对称;
在这里插入图片描述
由上述条件,很容易利用递归的方法来解决判断两棵二叉树是否成镜像对称的问题。
代码实现:

  bool twotreeisSymmetric(TreeNode* tree1, TreeNode* tree2){
        if(tree1 == NULL || tree2 == NULL){
            if(tree1 == NULL && tree2 == NULL){
                return true;
            }
            else{
                return false;
            }
        }
        return (tree1->val == tree2->val && 
        twotreeisSymmetric(tree1->right,tree2->left) && 
        twotreeisSymmetric(tree1->left, tree2->right));
        
    }

算法分析:
时间复杂度:因为我们遍历了整棵树一次,故算法的时间复杂度为 O ( n ) O(n) O(n),其中 n n n为树的节点数量。
空间复杂度:空间复杂度与递归调用的次数有关,而递归调用的次数受限于树的高度,在最糟糕的情况下,树是线性的,即高度为 n n n,所以该方法的空间复杂度为 O ( n ) O(n) O(n)
(17)层次遍历二叉树
基本思路:
层次遍历的思想很简单,使用一个辅助队列即可完成,每次从队首取出一个节点,保存该节点的值,然后将该节点的孩子节点放入队尾,直到列队为空。
如果要得到的不仅仅是节点的值,还包括该节点在树中的层次信息,那么需要使用一个二元组<节点,层数>来保存节点和节点的层数信息,队列中的每个元素都是这样的二元组。将一个节点入栈时,它的层数等于它父节点(即取出的队首元素的)的层数加1。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */


#include<cmath>
#include<tuple>
#include<algorithm>

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        
        vector<vector<int>> nullResult;
         if(root == NULL){
            return nullResult;
        }
        queue<tuple<TreeNode*,int> > q;  
        q.push(make_tuple(root,0));
        
        vector<tuple<int,int> > temp;
        int maxlayer = 0;
        
        while(!q.empty()){
            TreeNode* curnode = get<0>(q.front());
            int layer = get<1>(q.front());
            if(layer > maxlayer){
                maxlayer = layer;
            }
            if(curnode!= NULL){
                temp.push_back(make_tuple(curnode->val,layer));
            }
            if(curnode->left != NULL){
                q.push(make_tuple(curnode->left,layer + 1));
            }
            if(curnode->right != NULL){
                q.push(make_tuple(curnode->right, layer + 1));
            }
            q.pop();
        }
        vector<vector<int>> normalResult(maxlayer + 1);
        for(int i = 0; i < temp.size(); i++){
            int value = get<0>(temp[i]);
            int layer = get<1>(temp[i]);
            normalResult[layer].push_back(value);
        }
        return normalResult;
        
        
        
//         queue<TreeNode*> nodeq;
//         queue<int> layerq;
        
//         vector<int> valueseq;
//         vector<int> layerseq;
        
//         nodeq.push(root);
//         layerq.push(0);
        
//         while(!nodeq.empty()){
//             TreeNode* curnode = nodeq.front();
//             int layer = layerq.front();
            
//             if(curnode != NULL){
//                 valueseq.push_back(curnode->val);
//                 layerseq.push_back(layer);
//             }
//             if(curnode->left != NULL){
//                 nodeq.push(curnode->left);
//                 layerq.push(layer + 1);
//             }
//             if(curnode->right != NULL){
//                 nodeq.push(curnode->right);
//                 layerq.push(layer + 1);  
//             }
            
//             nodeq.pop();
//             layerq.pop();
            
//         }
        
//         vector<int>::iterator maxlayer = max_element(layerseq.begin(),layerseq.end());

//         vector<vector<int>> normalResult(*maxlayer + 1);
//         for(int i=0; i < valueseq.size(); i++){
//              normalResult[layerseq[i]].push_back(valueseq[i]);
//         }
//         return normalResult;
    
    }
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 算法9-9~9-12是关于平衡二叉树基本操作,包括插入、删除、旋转等操作。平衡二叉树是一种特殊的二叉搜索,它的左右子高度差不超过1,可以保证的高度始终在log(n)级别,从而保证了的查找、插入、删除等操作的时间复杂度都是O(log(n))。 具体来说,算法9-9是平衡二叉树的插入操作,它首先按照二叉搜索的规则找到要插入的位置,然后通过旋转操作来保持平衡。算法9-10是平衡二叉树的删除操作,它也是通过旋转操作来保持平衡。算法9-11和9-12是平衡二叉树的旋转操作,包括左旋、右旋、左右旋和右左旋,这些操作可以使重新达到平衡状态。 总之,平衡二叉树基本操作是非常重要的,它们可以保证的高度始终在log(n)级别,从而保证了的查找、插入、删除等操作的时间复杂度都是O(log(n)),是一种非常高效的数据结构。 ### 回答2: 平衡二叉树是一种基于二叉查找的数据结构,其在插入和删除节点时会自动调整,以保持的平衡性。平衡二叉树的常见有AVL、红黑等。本文主要介绍平衡二叉树基本操作,包括插入、删除和查找。 9-9 插入操作 在平衡二叉树中插入一个节点的过程和在二叉查找中插入节点的过程类似。不同的是,在插入结束后,需要检查当前节点是否失去平衡并做出相应的调整。 在插入节点时,需要记录节点的高度(从叶节点到根节点的距离)。如果当前节点为空,则将新节点插入到该节点处并将高度设置为1;否则,比较新节点的值和当前节点的值,如果新节点的值小于当前节点的值,则将新节点插入到当前节点的左子中并更新该节点的高度;如果新节点的值大于当前节点的值,则将新节点插入到当前节点的右子中并更新该节点的高度。 插入结束后,需要检查当前节点是否失去平衡。我们可以用该节点的左子高度和右子高度之差来衡量它是否平衡。如果该节点的平衡因子大于1,则需要进行旋转操作,以恢复平衡。 9-10 删除操作 在平衡二叉树中删除一个节点需要分为以下种情况: 1. 被删除的节点为叶子节点,直接删除即可。 2. 被删除的节点有一个子节点,将该子节点代替被删除的节点即可。 3. 被删除的节点有两个子节点,需要找到它的中序遍历下一个节点(即比它大的最小节点)代替被删除的节点。如果该节点有右子,则中序遍历下一个节点为右子中最小的节点;如果该节点没有右子,则中序遍历下一个节点为它的某个祖先节点。 删除结束后,需要检查当前节点是否失去平衡。如果失去平衡,则需要进行旋转操作,以恢复平衡。 9-11 查找操作 在平衡二叉树中查找一个节点的过程和在二叉查找中查找节点的过程类似。需要从根节点开始,比较查找的值和当前节点的值。如果查找的值小于当前节点的值,则在左子中递归查找;如果查找的值大于当前节点的值,则在右子中递归查找;如果查找的值等于当前节点的值,则返回该节点。 9-12 平衡因子 平衡二叉树的平衡因子定义为当前节点的左子高度和右子高度之差。如果平衡因子的绝对值大于1,则说明该节点失去了平衡。在平衡二叉树中,每个节点的平衡因子只能为-1、0或1。如果不是这个值,则需要进行旋转操作以恢复平衡。 ### 回答3: 平衡二叉树是一种特殊的二叉搜索,它的左右子高度差不超过1。由于平衡二叉树对于插入、删除、查找等操作的时间复杂度都是O(logn),因此在许多应用场景中得到了广泛的应用。平衡二叉树基本操作包括插入、删除、查找等,以下分别介绍: 9-9 插入操作:平衡二叉树的插入操作与普通二叉搜索相同,只是插入后需要进行平衡处理,避免出现左右子高度差不平衡的情况。例如插入节点x,需要先查找其应当插入的位置,然后通过旋转操作将其父节点与祖父节点一起旋转,使得重新平衡。插入操作的时间复杂度为O(logn)。 9-10 删除操作:删除操作也类似于普通二叉搜索,需要删除节点x后通过旋转操作迭代处理其祖先节点的平衡性,保证整个的平衡性。删除操作的时间复杂度为O(logn)。 9-11 查找操作:查找操作与普通二叉搜索相同,只是由于平衡二叉树的高度比较平衡,因此可以保证其查找效率较高。查找操作的时间复杂度为O(logn)。 9-12 平衡操作:平衡二叉树的平衡操作主要包括旋转操作和重构操作。旋转操作通过将子旋转到左右子高度相等来实现平衡,分为左旋和右旋两种。重构操作通过重新构建平衡二叉树的结构来保证整个的平衡性。平衡操作的时间复杂度为O(1)。 综上所述,平衡二叉树是一种高效的数据结构,其插入、删除、查找等基本操作时间复杂度都为O(logn),通过平衡操作可以保证整个的平衡性。在实际应用中,平衡二叉树被广泛应用于数据库、搜索引擎、红黑等场景中,具有重要的实用价值。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Albert_YuHan

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值