• 数据结构之平衡二叉树


    1.重新平衡2子树,体现了归纳思想,以及简单的建模思想。

    2.旋转名字不太好理解。自己觉得 “沿袭” 更恰当。自己为了进行沿袭这个动作。归纳了2个定理:

         1.父节点可垂直变为其左孩子或者左孩子的左孩子。左右同理。

         2.子树内部任意一支子树可代替原子树。

    有这2个定理。就可以不用理解书上的旋转了,用自己的沿袭就可以完成单旋转和双旋转。毕竟大部分书,都只是告诉你如何旋转,还不如自己建模,抽象,定义定理。来实现。

    3.重新计算树高度时,从变化的叶子开始要一直往父节点重新计算,所以很适合使用递归的方法,并保障从上往下时,是逐层往下,那么就可以保证插入和删除以后,会逐层检查子树高度,以及对比是否需要旋转

      而旋转后需要跟新子树的父节点。所以参考书上的方法是有返回节点的,一边递归回去的时候更新给父节点,而自己为了方便里面,没哟使用返回值,而是多加参数的方法,不简便,但好理解。

    归纳和抽象建模思想体现在哪里。第一步到第二步的转变侧重体现了归纳,抽象,建模等基本思想。  第二步到第三步更体现了为了解决实际问题,修改模型的能力。val是一个进行抽象思维的练习的好例子,非常值得复习

     采用了2中不同的函数来完成 add .一个带返回值,一个不带返回值,而是使用参数。

    package com.linson.datastrcture;
    
    
    
    
    
    //自己的插入在递归中,并没有和书上返回节点。自己感觉自己无返回值的更好理解,代替方案就是放入一个头节点的父节点。好理解,但也繁琐点。
    //递归返回根节点从代码的简洁上绝对更优,只是带参数的方法更容易理解。
    //avl这个例子很值得复习。1.递归时的问题模型的确定  2.平衡树时的建模思维。 3.树高的递归计算,递归影响。这个很不错。4.compareble的系统库接口使用。5.左小右大思路的利用。
    //6,删除时,需要平衡的时候会存在高的那一边的左右子树又一样高。比父节点的兄弟都高2级。
    
    public class MyAVL<T extends Comparable<T>>
    {
        public static class MyAVLNode<E extends Comparable<E>>
        {
            public E mElement;
            public MyAVLNode<E> mLeftNode;
            public MyAVLNode<E> mRightNode;
            public int mHeight;
            
            public MyAVLNode(E _value,MyAVLNode<E> left,MyAVLNode<E> right)
            {
                mElement=_value;
                mLeftNode=left;
                mRightNode=right;
                mHeight=1;
            }
            
            public int compareTo(E other)
            {
                return mElement.compareTo(other);
            }
        }
        
        public MyAVL()
        {
            mRootNode=null;
        }
        
        //问题:插入节点到树。组合:插入到节点,插入到左树,插入到右树. 基本值节点为空。可以插入。不需要再判断是否继续插入左或者右
        //节点高度默认是1,添加节点,必须逐层检测父节点:左右子树中最大值+1谁否大于现值? 大于要往上再检查,一直到某上层没变化。
        //所以返回值可以改为返回是否需要检查高度。但是想一下,又要增加返回值,又要判断是否需要检查,还不如每层都检查,反正AVL的话,数据再大也不会很高。1024才11层。
        public void add(T element,MyAVLNode<T> subTreeNode,MyAVLNode<T> fatherNode,boolean isLeft)
        {
            if(subTreeNode==null)
            {
                MyAVLNode<T> TempNode=new MyAVLNode<T>(element, null, null);//节点和树的泛型都实现了对比接口。所以树的参数,可以直接放入到节点中
                if(fatherNode!=null)
                {
                    if(isLeft)
                    {
                        fatherNode.mLeftNode=TempNode;
                    }
                    else 
                    {
                        fatherNode.mRightNode=TempNode;
                    }
                }
                else 
                {
                    mRootNode=TempNode;
                }
            }
            else
            {
                boolean addIsBiger=subTreeNode.mElement.compareTo(element)<0;//泛型实现了对比接口
                if(addIsBiger)
                {
                    add(element,subTreeNode.mRightNode,subTreeNode,false);
                }
                else 
                {
                    add(element,subTreeNode.mLeftNode,subTreeNode,true);
                }
                RotationAndHeight(subTreeNode,fatherNode,isLeft);
            }
        }
        
        //问题:匹配一个树,组合:匹配根,匹配左树,匹配右树。基本问题:节点就是。
        //空:直接删,副节点设空。 有单子树。修改数据。左右树都有,找高度更高的换数据。 被换节点一定是叶子,删除叶子,父节点设空。
        //旋转和高度问题。要求
        //1.传过来的根节点参数的左右子树高度是正确的。那么RotationAndHeight函数就就可以往上递归正确的计算高度和进行修正。
        //2.remove函数往下递归时,保证是逐层进行的。那么才能保证RotationAndHeight会逐层往上。
        public void remove(T element,MyAVLNode<T> subTreeNode,MyAVLNode<T> fatherNode,boolean isLeft)
        {
            if(subTreeNode==null)
            {
                return;
            }
            int compareRet=subTreeNode.mElement.compareTo(element);
            if(compareRet==0)
            {
                if(subTreeNode.mLeftNode==null && subTreeNode.mRightNode==null)
                {
                    if(fatherNode!=null)
                        if(isLeft)
                        {
                            fatherNode.mLeftNode=null;
                        }
                        else 
                        {
                            fatherNode.mRightNode=null;
                        }
                    else 
                    {
                        mRootNode=null;
                    }
                }
                else if(subTreeNode.mLeftNode==null || subTreeNode.mRightNode==null)
                {
                    if(fatherNode!=null)
                    {
                        if(isLeft)
                        {
                            fatherNode.mLeftNode=subTreeNode.mLeftNode==null?subTreeNode.mRightNode:subTreeNode.mLeftNode;
                        }
                        else 
                        {
                            fatherNode.mRightNode=subTreeNode.mLeftNode==null?subTreeNode.mRightNode:subTreeNode.mLeftNode;
                        }
                    }
                    else 
                    {
                        mRootNode=subTreeNode.mLeftNode==null?subTreeNode.mRightNode:subTreeNode.mLeftNode;
                    }
                }
                else 
                {
                    MyAVLNode<T> minnode= findMin(subTreeNode.mRightNode);
                    subTreeNode.mElement=minnode.mElement;
                    //可以确定是叶子,要效率高点。就可以新写个findMin,返回父节点。直接写代码删除。不需要这里一直递归。但对于修正平衡就没有办法往上递归了。
                    remove(minnode.mElement, subTreeNode.mRightNode, subTreeNode, false);
                    RotationAndHeight(subTreeNode,fatherNode,isLeft);
                }
            }
            else if(compareRet>0)//根节点更大。
            {
                remove(element, subTreeNode.mLeftNode,subTreeNode, true);
                RotationAndHeight(subTreeNode,fatherNode,isLeft);
            }
            else 
            {
                remove(element, subTreeNode.mRightNode,subTreeNode, false);
                RotationAndHeight(subTreeNode,fatherNode,isLeft);
            }
            
            
        }
        
        public MyAVLNode<T> findMax(MyAVLNode<T> subTreeNode)
        {
    
            MyAVLNode<T> tempRet=subTreeNode;
            while(tempRet!=null && tempRet.mRightNode!=null)
            {
                tempRet=tempRet.mRightNode;
            }
            return tempRet;
        }
        
        public MyAVLNode<T> findMin(MyAVLNode<T> subTreeNode)
        {
    
            MyAVLNode<T> tempRet=subTreeNode;
            while(tempRet!=null && tempRet.mLeftNode!=null)
            {
                tempRet=tempRet.mLeftNode;
            }
            return tempRet;
        }
        
        private void RotationAndHeight(MyAVLNode<T> subTreeNode,MyAVLNode<T> fatherNode,boolean isLeft)
        {
            //高度差=2 旋转,重新计算高度。
            //高度差<2. 根节点是否需要更新高度。
            //高度差>2 .错误。
            //断言对于快速开发和测试非常重要,而且减少正式版的编译代码和速度。不过如果有必要后期还是要写入到日志中.
            int leftHeight=subTreeNode.mLeftNode==null?0:subTreeNode.mLeftNode.mHeight;
            int rightHeight=subTreeNode.mRightNode==null?0:subTreeNode.mRightNode.mHeight;
            int maxSubTreeHeight=Math.max(leftHeight, rightHeight);
            
            assert(Math.abs(leftHeight-rightHeight)<=2) : "why .left compare to right is error";
            if(Math.abs(leftHeight-rightHeight)==2)
            {
                int ll=0,lr=0,rl=0,rr=0;
                int treetype=0;
                if(leftHeight-rightHeight==2)
                {
                    assert(subTreeNode.mLeftNode!=null):"no way";
                    ll=subTreeNode.mLeftNode.mLeftNode==null?0:subTreeNode.mLeftNode.mLeftNode.mHeight;
                    lr=subTreeNode.mLeftNode.mRightNode==null?0:subTreeNode.mLeftNode.mRightNode.mHeight;
                    if(ll>=lr)//这里用等号的话,删除的时候,可以用更简单的单转。
                    {
                        MyAVLNode<T> newRoot= subTreeNode.mLeftNode;
                        subTreeNode.mLeftNode=newRoot.mRightNode;
                        newRoot.mRightNode=subTreeNode;
                        newRoot.mRightNode.mHeight=newRoot.mRightNode.mHeight-1;
                        if(isLeft && fatherNode!=null)
                        {
                            fatherNode.mLeftNode=newRoot;
                        }
                        else if (!isLeft && fatherNode!=null) {
                            fatherNode.mRightNode=newRoot;
                        }
                        else {
                            mRootNode=newRoot;
                        }
                    }
                    else 
                    {
                        MyAVLNode<T> newRoot= subTreeNode.mLeftNode.mRightNode;
                        subTreeNode.mLeftNode.mRightNode=newRoot.mLeftNode;
                        newRoot.mLeftNode= subTreeNode.mLeftNode;
                        subTreeNode.mLeftNode=newRoot.mRightNode;
                        newRoot.mRightNode=subTreeNode;
                        newRoot.mHeight++;
                        newRoot.mLeftNode.mHeight--;
                        newRoot.mRightNode.mHeight--;
                        if(isLeft && fatherNode!=null)
                        {
                            fatherNode.mLeftNode=newRoot;
                        }
                        else if (!isLeft && fatherNode!=null) {
                            fatherNode.mRightNode=newRoot;
                        }
                        else {
                            mRootNode=newRoot;
                        }
                    }
                }
                else 
                {
                    assert(subTreeNode.mRightNode!=null):"no way";
                    rl=subTreeNode.mRightNode.mLeftNode==null?0:subTreeNode.mRightNode.mLeftNode.mHeight;
                    rr=subTreeNode.mRightNode.mLeftNode==null?0:subTreeNode.mRightNode.mLeftNode.mHeight;
                    if(rr>=rl)//这里用等号的话,删除的时候,可以用更简单的单转。
                    {
                        MyAVLNode<T> newRoot= subTreeNode.mRightNode;
                        subTreeNode.mRightNode=newRoot.mLeftNode;
                        newRoot.mLeftNode=subTreeNode;
                        newRoot.mLeftNode.mHeight=newRoot.mLeftNode.mHeight-1;
                        if(isLeft && fatherNode!=null)
                        {
                            fatherNode.mLeftNode=newRoot;
                        }
                        else if (!isLeft && fatherNode!=null) {
                            fatherNode.mRightNode=newRoot;
                        }
                        else {
                            mRootNode=newRoot;
                        }
                    }
                    else 
                    {
                        MyAVLNode<T> newRoot= subTreeNode.mRightNode.mLeftNode;
                        subTreeNode.mRightNode.mLeftNode=newRoot.mRightNode;
                        newRoot.mRightNode= subTreeNode.mRightNode;
                        subTreeNode.mRightNode=newRoot.mLeftNode;
                        newRoot.mLeftNode=subTreeNode;
                        newRoot.mHeight++;
                        newRoot.mRightNode.mHeight--;
                        newRoot.mLeftNode.mHeight--;
                        if(isLeft && fatherNode!=null)
                        {
                            fatherNode.mLeftNode=newRoot;
                        }
                        else if (!isLeft && fatherNode!=null) {
                            fatherNode.mRightNode=newRoot;
                        }
                        else {
                            mRootNode=newRoot;
                        }
                    }
                }
            }
            else if(Math.abs(leftHeight-rightHeight)==1)
            {
                
                assert((subTreeNode.mHeight-maxSubTreeHeight==0 || subTreeNode.mHeight-maxSubTreeHeight==1)) : "top node's height was wrong compare with left and right substree"; 
                if(subTreeNode.mHeight-maxSubTreeHeight==0)
                {
                    subTreeNode.mHeight++;
                }
            }
        }
        
        
        
        
        //add:主要是4个问题。1,正确插入位置。2,更新新插入点的父节点数据。3.更新新插入点的父节点数据的高度,4.新插入点的父节点是否平衡
        //                   1,一般处理。2,采用递归+方法带返回值:新节点,那么当递归返回上层时,可给返回的新插入点赋予正确的父节点。
        //   3.4,保证递归方法是逐层进行,并保证新插入点的左右子树高度正确。那么递归回来,会保证新插入点及其所有父节点左右子树高度正确以及都检测旋转。
        public MyAVLNode<T> add(T element,MyAVLNode<T> addToThisNode)
        {
            MyAVLNode<T> ret=null;
            if(addToThisNode==null)//某条件下,成了基本问题
            {
                ret= new MyAVLNode<T>(element, null, null);
            }
            else//其他条件下,用更小规模问题来组合
            {
                int addIsBigger=element.compareTo(addToThisNode.mElement);
                if(addIsBigger<0)
                {
                    addToThisNode.mLeftNode= add(element, addToThisNode.mLeftNode);
                }
                else 
                {
                    addToThisNode.mRightNode=add(element, addToThisNode.mRightNode);
                }
                ret=addToThisNode;
    
            }
            //检查高度,检查是否需要旋转。
            ret=reHeightAndBalance(ret);
            if(addToThisNode==mRootNode)
            {
                mRootNode=ret;
            }
            return ret;
        }
        
        //remove 和add基本思路一样。稍微复杂一点.
        //有目标点有3种情况,1,无左右子树,那么删除叶子,2,左右一个为空。那么跳过要删除点,和左右子树想相连。
        //3.左右都不为空,本质和1是一样。删除右子树的最小值节点,也就是一个叶子。并把叶子的数据给目标点。
        //返回当前子树最高顶点。
        public MyAVLNode<T> remove(T element,MyAVLNode<T> removeThisNode)
        {
            MyAVLNode<T> ret=null;
            if(removeThisNode==null)
            {
                //空树或者没找到要删除的点,什么都不做,并返回null,因为本来就是null,返回给这个节点的父节点NULL,等于什么都没做。
                ret=null;
            }
            else 
            {
                int removeIsBigger=element.compareTo(removeThisNode.mElement);
                if(removeIsBigger==0)////某条件下,成了基本问题
                {
                    if(removeThisNode.mLeftNode==null && removeThisNode.mRightNode==null)
                    {
                        removeThisNode=null;//其实这句没用,栈内的一个变量赋值为空而已,也没达到手动释放的堆内存,而且是java了。没必要劳心内存问题。
                        ret=null;
                    }
                    else if(removeThisNode.mLeftNode==null || removeThisNode.mRightNode==null)//不可以写成 != || != .因为会包含 != && !=.而现在这样写,额外包含的,在上面已经被排除了.
                    {
                        ret =removeThisNode.mLeftNode==null?removeThisNode.mRightNode:removeThisNode.mLeftNode;
                    }
                    else
                    {
                        MyAVLNode<T> minNode= findMin(removeThisNode.mRightNode);
                        removeThisNode.mElement= minNode.mElement;
                        removeThisNode.mRightNode=remove(minNode.mElement, removeThisNode.mRightNode);
                        ret=removeThisNode;
                        //return remove(minNode.mElement, removeThisNode.mRightNode);//原写代码 严重错误。
                    }
                }
                else if(removeIsBigger<0)
                {
                    removeThisNode.mLeftNode= remove(element, removeThisNode.mLeftNode);
                    ret=removeThisNode;
                }
                else 
                {
                    removeThisNode.mRightNode= remove(element, removeThisNode.mRightNode);
                    ret=removeThisNode;
                }
            }
            ret=reHeightAndBalance(ret);
            if(removeThisNode==mRootNode)
            {
                mRootNode=ret;
            }
            return ret;
        }
        
        
        //rotation: add .可从新加入点算起,add方法已经是逐步升入,那么会原路返回
        //remove:1. 双枝或空枝,逐步过来,最低点有null情况。返回nlll就好。 2.单枝情况,还好, 检查返回值的高度和平衡。再重新返回.
        private MyAVLNode<T> reHeightAndBalance(MyAVLNode<T> subTreeNode)
        {
            MyAVLNode<T> ret=subTreeNode;
            //null:返回 。 要平衡:平衡。算高度。
            if(subTreeNode==null)
            {
                ret=null;
            }
            else 
            {
                int leftHeight=subTreeNode.mLeftNode==null?0:subTreeNode.mLeftNode.mHeight;
                int rightHeight=subTreeNode.mRightNode==null?0:subTreeNode.mRightNode.mHeight;
                int maxSubTreeHeight=Math.max(leftHeight, rightHeight);
                
                assert(Math.abs(leftHeight-rightHeight)<=2) : "why .left compare to right is error";
                if(Math.abs(leftHeight-rightHeight)==2)
                {
                    int ll=0,lr=0,rl=0,rr=0;
                    int treetype=0;
                    if(leftHeight-rightHeight==2)
                    {
                        assert(subTreeNode.mLeftNode!=null):"no way";
                        ll=subTreeNode.mLeftNode.mLeftNode==null?0:subTreeNode.mLeftNode.mLeftNode.mHeight;
                        lr=subTreeNode.mLeftNode.mRightNode==null?0:subTreeNode.mLeftNode.mRightNode.mHeight;
                        if(ll>=lr)//这里用等号的话,删除的时候,可以用更简单的单转。
                        {
                            MyAVLNode<T> newRoot= subTreeNode.mLeftNode;
                            subTreeNode.mLeftNode=newRoot.mRightNode;
                            newRoot.mRightNode=subTreeNode;
                            newRoot.mRightNode.mHeight=newRoot.mRightNode.mHeight-1;
                            ret=newRoot;
                        }
                        else 
                        {
                            MyAVLNode<T> newRoot= subTreeNode.mLeftNode.mRightNode;
                            subTreeNode.mLeftNode.mRightNode=newRoot.mLeftNode;
                            newRoot.mLeftNode= subTreeNode.mLeftNode;
                            subTreeNode.mLeftNode=newRoot.mRightNode;
                            newRoot.mRightNode=subTreeNode;
                            newRoot.mHeight++;
                            newRoot.mLeftNode.mHeight--;
                            newRoot.mRightNode.mHeight--;
                            ret=newRoot;
                        }
                    }
                    else 
                    {
                        assert(subTreeNode.mRightNode!=null):"no way";
                        rl=subTreeNode.mRightNode.mLeftNode==null?0:subTreeNode.mRightNode.mLeftNode.mHeight;
                        rr=subTreeNode.mRightNode.mLeftNode==null?0:subTreeNode.mRightNode.mLeftNode.mHeight;
                        if(rr>=rl)//这里用等号的话,删除的时候,可以用更简单的单转。
                        {
                            MyAVLNode<T> newRoot= subTreeNode.mRightNode;
                            subTreeNode.mRightNode=newRoot.mLeftNode;
                            newRoot.mLeftNode=subTreeNode;
                            newRoot.mLeftNode.mHeight=newRoot.mLeftNode.mHeight-1;
                            ret=newRoot;
                        }
                        else 
                        {
                            MyAVLNode<T> newRoot= subTreeNode.mRightNode.mLeftNode;
                            subTreeNode.mRightNode.mLeftNode=newRoot.mRightNode;
                            newRoot.mRightNode= subTreeNode.mRightNode;
                            subTreeNode.mRightNode=newRoot.mLeftNode;
                            newRoot.mLeftNode=subTreeNode;
                            newRoot.mHeight++;
                            newRoot.mRightNode.mHeight--;
                            newRoot.mLeftNode.mHeight--;
                            ret=newRoot;
                        }
                    }
                }
                else if(Math.abs(leftHeight-rightHeight)==1)
                {
                    assert((subTreeNode.mHeight-maxSubTreeHeight==0 || subTreeNode.mHeight-maxSubTreeHeight==1)) : "top node's height was wrong compare with left and right substree"; 
                    if(subTreeNode.mHeight-maxSubTreeHeight==0)
                    {
                        subTreeNode.mHeight++;
                    }
                    ret=subTreeNode;
                }
            }
            
            return ret;
        }
    
        //type:0:first print parent node .1 left children ,parent, right children. 2:left children ,right children parent.
        public void printTree(int type)
        {
            printTree(mRootNode, type,"");
        }
        
        //打印树:组合:打印左树,打印节点,打印右树。基本情况:叶子。
        private void printTree(MyAVLNode<T> node,int type,String space)
        {
            if(node==null)
            {
                System.out.println("");
                return;
            }
            
            String leafInfo=space+node.mElement.toString()+"["+node.mHeight+"]";
            String newSpace=space+"     ";
            
            if(node.mLeftNode==null && node.mRightNode==null)
            {
                System.out.println(leafInfo);
            }
            else
            {
                
                if(type==0)
                {
                    System.out.println(leafInfo);
                    if(node.mLeftNode!=null)
                    {
                        printTree(node.mLeftNode, type,newSpace);
                    }
                    if(node.mRightNode!=null)
                    {
                        printTree(node.mRightNode, type,newSpace);
                    }
                }
                
                if(type==1)
                {
                    
                    if(node.mLeftNode!=null)
                    {
                        printTree(node.mLeftNode, type,newSpace);
                    }
                    System.out.println(leafInfo);
                    if(node.mRightNode!=null)
                    {
                        printTree(node.mRightNode, type,newSpace);
                    }
                }
                
                if(type==2)
                {
                    if(node.mLeftNode!=null)
                    {
                        printTree(node.mLeftNode, type,newSpace);
                    }
                    if(node.mRightNode!=null)
                    {
                        printTree(node.mRightNode, type,newSpace);
                    }
                    System.out.println(leafInfo);
                }
                if(type==3)
                {
                    if(node.mRightNode!=null)
                    {
                        printTree(node.mRightNode, type,newSpace);
                    }
                    System.out.println(leafInfo);
                    if(node.mLeftNode!=null)
                    {
                        printTree(node.mLeftNode, type,newSpace);
                    }
                    
                    
                    
                }
            }
        }
        
        
        public MyAVLNode<T> mRootNode=null;
    }

    测试代码

    public static class AVL
        {
            public static void test()
            {
                MyAVL<Integer> mytreeAvl=new MyAVL<Integer>();
    //            mytreeAvl.add(20, mytreeAvl.mRootNode,null,true);
    //            mytreeAvl.add(7, mytreeAvl.mRootNode,null,true);
    //            //mytreeAvl.add(56, mytreeAvl.mRootNode,null,true);
    //            //mytreeAvl.add(5, mytreeAvl.mRootNode,null,true);
    //            //mytreeAvl.add(9, mytreeAvl.mRootNode,null,true);
    //            //mytreeAvl.add(3, mytreeAvl.mRootNode,null,true);
    ////            mytreeAvl.add(12, mytreeAvl.mRootNode,null,true);
    ////            mytreeAvl.add(64, mytreeAvl.mRootNode,null,true);
    ////            mytreeAvl.add(33, mytreeAvl.mRootNode,null,true);
    ////            mytreeAvl.add(15, mytreeAvl.mRootNode,null,true);
                
                
                mytreeAvl.add(20, mytreeAvl.mRootNode);
                mytreeAvl.add(7, mytreeAvl.mRootNode);
                mytreeAvl.add(56, mytreeAvl.mRootNode);
                mytreeAvl.add(5, mytreeAvl.mRootNode);
                mytreeAvl.add(9, mytreeAvl.mRootNode);
                mytreeAvl.add(3, mytreeAvl.mRootNode);
                mytreeAvl.add(12, mytreeAvl.mRootNode);
    //            mytreeAvl.add(64, mytreeAvl.mRootNode,null,true);
    //            mytreeAvl.add(33, mytreeAvl.mRootNode,null,true);
    //            mytreeAvl.add(15, mytreeAvl.mRootNode,null,true);
                
                
                //mytreeAvl.remove(20, mytreeAvl.mRootNode, null, true);
                //mytreeAvl.remove(5, mytreeAvl.mRootNode, null, true);
                
                mytreeAvl.printTree(3);
            }
        }
  • 相关阅读:
    FAT学习笔记(五)——FAQ
    FAT32学习笔记(五)——fat相关工具
    FAT学习笔记(四)——Dir Entry
    FAT学习笔记(三)--FSInfo
    zabbix介绍
    配置pxe 自动化安装centos6.7
    跳转方式用name方法的话,浏览器返回按钮点击返回时会有BUG
    FormData使用方法详解
    vue中的@click.native
    vue从后台获取数据,并导出EXCEL文件
  • 原文地址:https://www.cnblogs.com/lsfv/p/10565535.html
Copyright © 2020-2023  润新知