java树类算法题-操作格子,逆序对,安慰奶牛

线段树

参考博客(https://www.cnblogs.com/reminis/p/12652184.html)()
在这里插入图片描述
在这里插入图片描述
由上面两张图大概能理解什么是线段树(是平衡二叉树,但是不是完全二叉树)

首先一个问题是我们如果用数组来存储构建的线段树,那么我们需要多大空间

对于满二叉树来说,0层有一个节点,1层有两个节点,2层有四个节点,3层有8个节点,则在h层一共有2h-1个节点(大约是2h个),最后一层(h-1层),有2(h-1)个节点,即满二叉树最后一层的节点数大致等于前面所有层节点之和。满二叉树是一种特殊的完全二叉树。
像第二张图,n=2k,刚好可以构成一课满二叉树,这时只需要2n的区间就好了
而第一张图,n=2k+1,左右会多出来两个节点,我们存的话还是要按满二叉树存,因为下标表示位置,所以需要数组2n+2n=4n的空间,但实际上这2n的空间中只有一个节点是有效数据,其他2n-1的空间都是浪费掉的。
所以创建线段树,一般是固定4n存储空间

每一个中间节点都表示什么意义

我们并不是存储一个区间,而是根据需要存储对应于这一个区间的信息,例如求和查询,最大最小值查询等

初始化线段树

先定义树的节点,left和right表明了这个树的区间

class Node{
    int left;
    int right;
    int sum;
    int max;
}

回溯建线段树树

 //为区间[left,right]建立一个以index为祖先的线段树,index为数组下标 
    private static void buildTree(int left,int right,int index){
        //
        tree[index]=new Node();
        tree[index].left=left;
        tree[index].right=right;
        if(left==right){// 当区间长度为 0 时,就是到达叶子节点,结束递归 
            father[left]=index;// 能知道某个点对应的序号,为了单点更新update()的时候从下往上一直到顶 ,不过注意因为这里给我们的数据是1-n,如果数组里是随机数,那就不能用left当下标了,会超出数组界线
            return;
        }
         //该结点往 左孩子的方向 继续建立线段树,线段的划分是二分思想,如果写过二分查找的话这里很容易接受 ,这里将 区间[left,right] 一分为二了
        buildTree(left,(int)Math.floor(((left+right)/2.0)),index*2);
        //该结点往 右孩子的方向 继续建立线段树  
        buildTree((int)Math.floor(((left+right)/2.0))+1,right,index*2+1);
    }

ok,看看题目里面让我们做什么

有n个格子,从左到右放成一排,编号为1-n。

共有m次操作,有3种操作类型:

1.修改一个格子的权值,
2.求连续一段格子权值和,
3.求连续一段格子的最大值。

对于每个2、3操作输出你所求出的结果。

第一个是线段树的单点更新
思路:father[]数组里面已经有每一个点在线段树中的位置,所以可以直接找到对应的叶子结点开始,沿二叉树的路径向上更新至根结点即可

//index是被更新点的坐标,
private static void update(int index){
   if(index==1)return;// 向上已经找到了祖先(整个线段树的祖先结点 对应的下标为1)  
   if(tree[index].left==tree[index].right){//该节点为叶子节点
       tree[index].max=arr[tree[index].left];
       tree[index].sum=arr[tree[index].left];
   }
   int fatherIndex=index/2;//代表父节点在tree中的位置(根节点为1的情况)
   tree[fatherIndex].sum=tree[fatherIndex*2].sum+tree[fatherIndex*2+1].sum;
   update(fatherIndex);
}

除此之外构建线段树之后max和sum的初始化赋值也是通过这个函数

//更新区间最大值、区间和
        for(int i=1;i<=gridNum;i++){
            update(father[i]);
        }

第二个第三个都是属于区间查询类型

区间查询大体上可以分为3种情况讨论:

1当前结点所代表的区间完全位于给定需要被查询的区间之外,则不应考虑当前结点
2当前结点所代表的区间完全位于给定需要被查询的区间之内,则可以直接查看当前结点的母结点
3当前结点所代表的区间部分位于需要被查询的区间之内,部分位于其外,则我们先考虑位于区间外的部分,后考虑区间内的(注意总有可能找到完全位于区间内的结点,因为叶子结点的区间长度为1,因此我们总能组合出合适的区间)

    // index为区间的序号(对应的区间是最大范围的那个区间,也是第一个图最顶端的区间,一般初始是 1) 
    private static void getMax(int x,int y,int index){
        //如果要求的区间正好是某一个节点
        if(x==tree[index].left&&y==tree[index].right){
            tempMax=tree[index].max>tempMax?tree[index].max:tempMax;
            return;
        }
 
        int leftIndex=index*2;//左子树在tree中的位置
        if(x<=tree[leftIndex].right){//所求的区间在左子树中有涉及
            if(y<=tree[leftIndex].right){//左子树完全包含所求的区间,则查询区间形态不变  
                getMax(x,y,leftIndex);
            }else{//半包含于左区间,则查询区间拆分,左端点不变,右端点变为左子树的右区间端点  
                getMax(x,tree[leftIndex].right,leftIndex);
            }
        }
 
        int rightIndex=leftIndex+1;//右子树在tree中的位置
        if(y>=tree[rightIndex].left){//所求的区间在右子树有涉及
            if(x>=tree[rightIndex].left){//右子树完全包含所求的区间,则查询区间形态不变  
                getMax(x,y,rightIndex);
            }else{// 半包含于左区间,则查询区间拆分,与上同理  
                getMax(tree[rightIndex].left,y,rightIndex);
            }
        }
    }
 // index为区间的序号(对应的区间是最大范围的那个区间,也是第一个图最顶端的区间,一般初始是 1) 
    private static void getMax(int x,int y,int index){
        if(x==tree[index].left&&y==tree[index].right){
            tempMax=tree[index].max>tempMax?tree[index].max:tempMax;
            return;
        }
 
        int leftIndex=index*2;//左子树在tree中的位置
        if(x<=tree[leftIndex].right){//所求的区间在左子树中有涉及
            if(y<=tree[leftIndex].right){//左子树完全包含所求的区间,则查询区间形态不变  
                getMax(x,y,leftIndex);
            }else{//半包含于左区间,则查询区间拆分,左端点不变,右端点变为左子树的右区间端点  
                getMax(x,tree[leftIndex].right,leftIndex);
            }
        }
 
        int rightIndex=leftIndex+1;//右子树在tree中的位置
        if(y>=tree[rightIndex].left){//所求的区间在右子树有涉及
            if(x>=tree[rightIndex].left){//右子树完全包含所求的区间,则查询区间形态不变  
                getMax(x,y,rightIndex);
            }else{// 半包含于左区间,则查询区间拆分,与上同理  
                getMax(tree[rightIndex].left,y,rightIndex);
            }
        }
    }

平衡二叉树

看来应该先做这个再做线段树,因为线段树是平衡二叉树
现在再来深入一下平衡二叉树吧,平衡二叉树属于二叉排序树的一种(它有这么一个特点,某个节点,若其有两个子节点,则一定满足,左子节点值一定小于该节点值,右子节点值一定大于该节点值
先说一下二叉排序树,初始化就不说了,和其他树大同小异
排序树增加
当要进行添加元素的时候,得考虑根节点的初始化,一般情况有两种、当该类的构造函数一初始化就对根节点root进行初始化,第二种、在进行第一次添加元素的时候,对根节点进行添加。理论上两个都可以行得通,但通常采用的是第二种懒加载形式。
在进行添加元素的时候,有这样几种情况需要考虑
一、添加时判断root是否初始化,若没初始化,则初始化,将该值赋给根节点,size加一。
二、因为二叉树搜索树满足根节点值大于左节点,小于右节点,需要将插入的值,先同根节点比较,若大,则往右子树中进行查找,若小,则往左子树中进行查找。直到某个子节点。
这里的插入实现,可以采用两种,一、递归、二、迭代(即通过while循环模式)。
(获取要插入的节点的父节点递归实现:递归停止标志是1、父节点为子节点 2、插入节点值比父节点小,但父节点没有左子节点3、插入节点值比父节点大,但父节点没有右子节点 4、插入节点值和父节点相等。5、父节点为空)

排序树删除
在常规思路中,删除二叉搜索树的某一个节点,肯定会想到以下四种情况,
在这里插入图片描述
1、要删除的节点没有左右子节点,如上图的D、E、G节点
2、要删除的节点只有左子节点,如B节点
3、要删除的节点只有右子节点,如F节点
4、要删除的节点既有左子节点,又有右子节点,如 A、C节点

前三种好说,第四种就是要找大于该节点值得所有节点集合中值最小的那个节点,比如C的后继节点是F,A的后继节点是E。

平衡二叉树(AVL)

平衡二叉树构建的基本思想就是在构建二叉排序树的过程中,每当插入一个节点时,先检查是否因插入而破坏了树的平衡性,若是,找出最小不平衡树。在保持二叉排序树特性的前提下,调整最小不平衡子树中各节点之间的链接关系,进行相应的旋转,使之成为新的平衡子树。
关键点旋转:
在这里插入图片描述这里需要进行右旋操作

/**
     * 在这种情况,因为A和B节点均没有右孩子节点,
     * 所以不用考虑太多
     * @param aNode 代表A节点
     * @return
     */
    public Node leftRotation(Node aNode){
        if(aNode != null){
            Node bNode = aNode.leftChild;// 先用一个变量来存储B节点
            bNode.parent = aNode.parent;// 重新分配A节点的父节点指向
            //判断A节点的父节点是否存在
            if(aNode.parent != null){// A节点不是根节点
                /**
                 * 分两种情况
                 *   1、A节点位于其父节点左边,则B节点也要位于左边
                 *   2、A节点位于其父节点右边,则B节点也要位于右边
                 */
                if(aNode.parent.leftChild == aNode){
                    aNode.parent.leftChild = bNode;
                }else{
                    aNode.parent.rightChild = bNode;
                }
            }else{// 说明A节点是根节点,直接将B节点置为根节点
                this.root = bNode;
            }
            bNode.rightChild = aNode;// 将B节点的右孩子置为A节点
            aNode.parent = bNode;// 将A节点的父节点置为B节点
            return bNode;// 返回旋转的节点
        }
        return null;
    }

在这里插入图片描述这种先对B左旋,就又变成了第一种情况,再右旋

/**
     * 
     * @param bNode 代表B节点
     * @return  右旋操作
     */
    public Node rightRotation(Node bNode){
        if(bNode != null){
            Node cNode = bNode.rightChild;// 用临时变量存储C节点
            cNode.parent = bNode.parent;
            // 这里因为bNode节点父节点存在,所以不需要判断。加判断也行,
            if(bNode.parent.rightChild == bNode){
                bNode.parent.rightChild = cNode;
            }else{
                bNode.parent.leftChild = cNode;
            }
            cNode.leftChild = bNode;
            bNode.parent = cNode;
            return cNode;
        }
        return null;
    }

总结
在这里插入图片描述
好了,开始看看题目(java蓝桥-逆序对)吧

有一颗2n-1个节点的二叉树,它有恰好n个叶子节点,每个节点上写了一个整数。如果将这棵树的所有叶子节点上的数从左到右写下来,便得到一个序列a[1]…a[n]。现在想让这个序列中的逆序对数量最少,但唯一的操作就是选树上一个非叶子节点,将它的左右两颗子树交换。他可以做任意多次这个操作。求在最优方案下,该序列的逆序对数最少有多少。

首先逆序对的含义
如果存在正整数 i, j 使得 1 ≤ i < j ≤ n 而且 A[i] > A[j],则 <A[i], A[j]> 这个有序对称为 A 的一个逆序对,也称作逆序数。

没做出来,也没找到合适的题解,以后有机会补

最小生成树

将一个有权图中的 所有顶点 都连接起来,并保证连接的边的 总权重最小,即最小生成树,最小生成树不唯一

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值