数据结构与算法——树的基本知识

一、概述

     向量(循秩访问,根据元素的秩快速确定其存储的物理地址从而找到指定元素)和列表(循位置访问,通过各个节点间保存相互引用找到指定节点)的操作复杂度如下:                                                                                       

·  向量:静态的查找复杂度为O(1),动态的插入和删除复杂度为O(n)

·  列表:静态的查找复杂度为O(n),动态的插入和删除复杂度为O(1)  

     为了结合向量和列表的优势,引入了树。树是一种半线性数据结构,可以看做是一种特殊的无环图T=(V,E)。其中,节点总数|V|=n,边总数|E|=e,父亲节点和孩子节点的用边连接。                         

 二、树的基本概念以及属性

        1、节点:每一个元素称作一个节点

        2、树根:无父亲节点的节点称作根节点,空树无根节点。

        3、父亲节点:一个节点包含子节点A、B(有对A、B的指针)则该节点称作A、B父节

        4、兄弟节点:A 和 B 互 称为兄弟节点

        5、堂兄弟节点:父亲节点在同一层的节点

        6、叶子节点:度为0的节点

        7、森林:互不相交的树的集合

        8、度数:该节点包含边的个数,和其子树的个数一样多;树的度为所有节点的最大度数

        9、树的深度/高度:树的层次数目

三、树的构造

        1、长子兄弟法:节点A保存了A的长子节点(纵向引用)和A的兄弟节点的引用(横向引用)

              如果需要查找某个节点的所有孩子节点,只需要找到其长子节点再由长子节点进行遍历即可。

四、树的种类

        1、二叉树:每个节点最多两个子树的结构

        2、二叉查找树:即BST,左子树值all>=根节点值>=右子树值all;或者左子树值all<=根节点值<=右子树值all

        3、满二叉树:除最后一层无任何子节点外,每一层上的所有结点都有两个子结点的二叉树。

        4、完全二叉树:去掉最后一层可以看做满树;最后一层的叶子节点都连续集中在最左侧。

        5、平衡二叉树:追求全局平滑,任何一个节点的左右子树高度差最多为1,需要在插入和删除后需要旋转类动态调

              整以满 足其平滑性以解决二叉查找树查找和插入最坏可能情况下时间复杂度为线性时间的问题。                                          6、红黑树:平衡二叉树的一种,解决平衡二叉树的平衡代价问题,只要求树的局部具有平衡特性,效率优化具体体

             现在插 入和删除所需要的调整操作次数上,可以替代平衡二叉树。                                                                                       7、伸展树:一种二叉排序树,在局部性较强的情况下通过将访问节点提升至根节点来提高效率,能在O(logn)时

              间复杂度内完成插入、查找以及删除操作。                                                                                                                        8、B树:一种平衡多路查找树,为了解决大规模数据情况下树的查找退化为线性复杂度的问题。

 五、树的基本操作(以二叉树为例)

        1、遍历:前序遍历、中序遍历、后序遍历、层次遍历

            1.1 前序遍历:根节点>左子树>右子树

                   1.1.1 递归实现

                            public void preOrder(MyTreeNode node){
                                if(node!=null){
                                System.out.println(node.data);
                                 preOrder(node.left);
                                 preOrder(node.right);
                                 }
                            }

                   1.1.2 栈实现

                          由于前序遍历的遍历顺序是先跟再左再右,而栈是先进后出的,所以节点入栈顺序是先右节点,再左

                    节点。这样的入栈顺序能够保证左子树遍历完才遍历右子树。                                                                               

                    public void preOrder(MyTreeNode node){
                        Stack<MyTreeNode> stack=new Stack<MyTreeNode>();
                         stack.push(node);    // 树根节点入栈
                         while(stack.size()>0){
                          MyTreeNode node1=stack.pop();    //栈顶元素出栈,即根节点(左子树的根节点或者树根)出栈
                          System.out.println(node1.data);
                          if(node1.right!=null)
                                  stack.push(node1.right);
                                  if(node1.left!=null)
                                  stack.push(node1.left);
                           }
                     }

             1.2 中序遍历:左子树>根节点>右子树

                   1.2.1 递归实现

                    public void middleOrder(MyTreeNode node){
                if(node!=null){              

                                    middleOrder(node.left);
            System.out.println(node.data);
            middleOrder(node.right);

                }
                     }

                   1.2.2 栈实现

                         中序遍历是先从最左下角的叶子节点往上呈阶梯式遍历到根节点,在遍历的过程中如果某一节点有右子

                    树则重复前 一遍历操作。所以,入栈顺序为根节点>左孩子1号>左孩子2号>......>最左孩子,出栈时输出该

                    节点元素。如果 该节点有右子树,则按照上述入栈顺序入栈右子树。                                                                   

                    public void middleOrder(MyTreeNode node){
                           MyTreeNode node1=node;
                           Stack<MyTreeNode> stack=new Stack<MyTreeNode>();
                           while(node1!=null){
                                 while(node1!=null){        
                                       stack.push(node1);    //子树入栈,第一次为整棵树,第二次则为栈内某个节点的右子树,......
                                        node1=node1.left;
                                  }
                                 while(stack.size()>0){
                                        node1=stack.pop();
                                        System.out.println(node1.data);
                                         if(node1.right!=null){    //如果该节点有右孩子(子树),则停止出栈,并将该右子树当做新的树入栈
                                          node1=node1.right;
                                          break;
                                  }
                                      }
                                if(stack.size()==0&&node1.right==null){
                                        System.out.println(node1.data);
                                         break;
                                }
                              }
                    }                                                                                              

            1.3 后序遍历:左子树>右子树>跟节点

                    1.3.1 递归法:

                     public void  afterOrder(MyTreeNode node){
                             if(node!=null){
                             afterOrder(node.left);
                              afterOrder(node.right);
                             System.out.println(node.data);
                                  }
                             }

                    1.3.2 栈实现

                    后续遍历需要两个栈,一个栈记录左右孩子节点,另一个栈记录出栈的结果(根节点)。

                    public void afterOrder(MyTreeNode node){
                           Stack<MyTreeNode> stack=new Stack<MyTreeNode>();
                           stack.push(node);    
                           Stack<MyTreeNode> stack2=new Stack<MyTreeNode>();
                           MyTreeNode node1=node;
                           while(node1!=null&&stack.size()>0){    
                                if(node1.left!=null)
                                     stack.push(node1.left);//左子树入栈
                                if(node1.right!=null)
                                    stack.push(node1.right);    //右子树入栈,先出
                                node1=stack.pop();
                                  stack2.push(node1);         //node1是栈1出栈的节点,为树根节点或者右子树的根节点
                          }
                          while(stack2.size()>0)
                                System.out.println(stack2.pop().data);
                       }

            1.4 层次遍历:根节点>第一层>第二层>......

                  public void floor(MyTreeNode node){
                        List<MyTreeNode> list=new LinkedList<MyTreeNode>();
                        list.add(node);
                        while(list.size()>0){    //此处省略输出结果
                                List<MyTreeNode> list2=new LinkedList<MyTreeNode>();
                                for(MyTreeNode node1:list){
                                if(node1.left!=null)
                                        list2.add(node1.left);
                                if(node1.right!=null)
                                        list2.add(node1.right); 
                            }
                        list=list2;
                             }
                      }

        2、增删改查:略,由树的种类决定

        3、树的高度:可用层次遍历的层数表示树高,或者使用递归的方式实现

             public int getHeight(MyTreeNode node){
                  if(node!=null)
                    return getHeight(node.left)>getHeight(node.right)?(getHeight(node.left)+1):(getHeight(node.right)+1);

                    return 0;

              }  

        4、树的宽度:使用层次遍历的方式实现,所有list中最大的size即树的宽度

        5、其他:两个最远节点距离?判断该树是否是完全二叉树、平衡二叉树?如何利用中序+后序/前序重构树?两个节

             点的公共祖先?判断一颗树是否为某颗树的子树?如何翻转二叉树?   

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值