数据结构与算法笔记(八)

有些话、适合烂在心里 提交于 2021-01-05 07:50:03
一、二叉树的查找
1.二叉查找树
    对于树结构的查找,因为实际运用中大多都是使用二叉树的结构,因此我们只讨论二叉树的查找。
    若有一棵非空二叉树,他满足如下条件:
  • 若他的左子树不为空树,则有左子树上所有结点的值小于根结点的值。
  • 若他的右子树不为空树,则有右子树上所有节点的值大于根结点的值。
  • 他的左右子树也满足上述两个条件。
  • 不能有值相等的结点。
    则称这棵树为二叉查找/排序/搜素树。对二叉查找树进行中序遍历可以获取一个有序(升序)排列的集合。
    若对一棵普通的二叉查找树查找操作,则过程与二分查找类似,且查找的次数不会超过树的高度。此时查找到额时间复杂度为O(log2n);但若是出现极端情况,一棵二叉查找树完全没有左孩子或完全没有右孩子,此时变成链表结构,查找的时间复杂度也随之变成O(n)。因此,为了提升查询效率,在构建查找树时要尽可能的保证左右子树的结点数量相近,以保证查找效率。
    二叉查找树的简单实现:

/**
* 二叉查找功能接口
* Created by bzhang on 2019/3/2.
*/
public interface TreeList {
      //获取根结点
      Node getRoot();
      // 返回树中的结点数,即数据元素的个数。
      int size();
      //树的高度
      int getHeight();
      // 返回树中排在 key 的数据元素
      Object get(int key);

      //向树中增加结点
      void put(int key,Object value);
      // 如果树为空返回 true,否则返回 false。
      boolean isEmpty();
      // 判断树中是否包含数据元素 e
      boolean contains(int key);

      //删除树中排在key位置的结点
      boolean remove(int key);

      //中序遍历
      void inOrder();
}
 
 
    接口实现类:

/**
* 底层为链表实现的二叉查找树的实现。
* Created by bzhang on 2019/3/2.
*/
public class TreeListImpl implements TreeList {

      private int size;       //树中的结点数
      private Node root;      //跟结点

      @Override
      public Node getRoot() {
            return root;
      }

      @Override
      public int size() {
            return size;
      }

      @Override
      public int getHeight() {
            return getHeight(root);
      }

      //实际计算二叉树高度的方法
      private int getHeight(Node node) {
            if (node==null){
                  return 0;
            }else {
                  int r = getHeight(node.getRight());
                  int l = getHeight(node.getLeft());
                  return r>l?(r+1):(l+1);
            }
      }

      @Override
      public Object get(int key) {
            Node temp = root;
            while (temp!=null){     //当前结点不为空,进入循环
                  if (temp.getKey()==key){      //key与结点的key相等,则返回结点的value值。
                        return temp.getValue();
                  }else if(temp.getKey()>key){        //key小于当前结点的key值,去查找当前节点的左子树
                        temp = temp.getLeft();


                  }else if (temp.getKey()<key){       //key大于当前结点的key值,去查找当前节点的右子树
                        temp = temp.getRight();
                  }
            }
            return null;      //树中不存在key,返回null
      }

      @Override
      public void put(int key, Object value) {

            if (root==null){        //第一传入的结点设为根结点
                  Node node = new Node(key,value,null,null,null);
                  root = node;
                  size++;
            }else {
                  addNode(key,value);     //调用向树中增加结点的方法
            }
      }

      //实际向二叉查找树中增加结点的方法
      private void addNode(int key, Object value) {
            Node temp = root;
            Node res = null;
            while (temp!=null){
                  if (temp.getKey()==key){      //树中已存在key的结点,更新其value值
                        temp.setValue(value);
                        return;
                  }else if(temp.getKey()>key){        //key小于当前结点的key值,往其左子树中增加结点
                        res = temp;
                        temp = temp.getLeft();

                  }else if (temp.getKey()<key){       //key大于当前结点的key值,往其右子树中增加结点
                        res = temp;
                        temp = temp.getRight();
                  }
            }
            Node newNode = new Node(key,value,null,null,res);     //新建以查找到的结点为父结点的结点
            if (res.getKey()>key){        //判断新建结点是res左孩子还是右孩子
                  res.setLeft(newNode);
            }else {
                  res.setRight(newNode);
            }
            size++;
      }

      @Override
      public boolean isEmpty() {
            return size == 0;
      }

      @Override
      public boolean contains(int key) {
            Node temp = root;
            while (temp!=null){
                  if (temp.getKey()==key){
                        return true;
                  }else if(temp.getKey()>key){
                        temp = temp.getLeft();


                  }else if (temp.getKey()<key){
                        temp = temp.getRight();
                  }
            }
            return false;
      }

      @Override
      public boolean remove(int key) {
            Node temp = root;
            while (temp!=null){     //查询要删除掉的结点
                  if (temp.getKey()==key){
                        remove(temp);     //实际进行删除操作的方法
                        return true;
                  }else if(temp.getKey()>key){
                        temp = temp.getLeft();


                  }else if (temp.getKey()<key){
                        temp = temp.getRight();
                  }
            }
            return false;
      }

      /**
       * 实际删除结点方法
       * @param temp
       */
      private void remove(Node temp) {
            if (temp==root){        //判断要删除的结点是否为根结点
                  if (temp.getLeft()==null&&temp.getRight()==null){     //根结点没有左右子树
                        root = null;
                  }else if(temp.getLeft()!=null&&temp.getRight()==null){      //仅有左子树
                        root = temp.getLeft();
                        root.setParent(null);
                  }else if (temp.getLeft()==null&&temp.getRight()!=null){     //仅有右子树
                        root = temp.getRight();
                        root.setParent(null);
                  }else {           //左右子树都有,则将右孩子设为根结点,并向右子树中最小的结点上添加整个左子树
                        root = temp.getRight();
                        root.setParent(null);
                        Node minRight = getMinNode(temp.getRight());
                        minRight.setLeft(temp.getLeft());
                        temp.getLeft().setParent(minRight);

                  }

            }else {           //要删除的结点不是根结点
                  Node parent = temp.getParent();


                  if (temp.getLeft()==null&&temp.getRight()==null){           //要删除的结点没有左右子树
                        if (parent.getLeft()==temp){
                              parent.setLeft(null);
                        }else {
                              parent.setRight(null);
                        }
                  }else if(temp.getLeft()!=null&&temp.getRight()==null){      //仅有左子树
                        if (parent.getLeft()==temp){
                              parent.setLeft(temp.getLeft());
                        }else {
                              parent.setRight(temp.getLeft());
                        }
                  }else if (temp.getLeft()==null&&temp.getRight()!=null){     //仅有右子树
                        if (parent.getLeft()==temp){
                              parent.setLeft(temp.getRight());
                        }else {
                              parent.setRight(temp.getRight());
                        }
                  }else {           //左右子树都有
                        /**
                         * 判断要删除的结点temp是其父结点parent的左孩子还是右孩子。
                         * 若是左孩子,则设置要temp的右孩子为parent结点的左孩子,
                         * 并将temp的整个左子树设为其右子树中最小结点的左孩子。
                         * 若是右孩子,则将temp的左孩子设为parent结点的右孩子,
                         * 并将temp的整个右子树设为temp左子树中最大结点的右孩子。
                         *
                         */
                        if (parent.getLeft()==temp){
                              Node minRight = getMinNode(temp.getRight());
                              parent.setLeft(temp.getRight());
                              temp.getRight().setParent(parent);
                              minRight.setLeft(temp.getLeft());
                              temp.getLeft().setParent(minRight);
                        }else {

                              Node maxLeft = getMaxNode(temp.getLeft());
                              parent.setRight(temp.getLeft());
                              temp.getLeft().setParent(parent);
                              maxLeft.setRight(temp.getRight());
                              temp.getRight().setRight(maxLeft);
                        }
                  }
            }
            size--;

      }

      //获取以root为根结点的二叉查找树的最大结点
      private Node getMaxNode(Node left) {
            Node temp = root;
            while (temp.getRight()!=null){
                  temp = temp.getRight();
            }
            return temp;
      }

      //获取以root为根结点的二叉查找树的最小结点
      private Node getMinNode(Node root) {
            Node temp = root;
            while (temp.getLeft()!=null){
                  temp = temp.getLeft();
            }
            return temp;


      }

      @Override
      public void inOrder() {
            checkedNotNull(root);
            System.out.print("中序遍历:");
            Deque<Node> stack = new LinkedList();
            Node temp = root;
            while (temp!=null||!stack.isEmpty()){
                  while (temp!=null){
                        stack.push(temp);
                        temp = temp.getLeft();
                  }
                  if (!stack.isEmpty()){
                        temp = stack.pop();
                        System.out.print("key:"+temp.getKey()+"*value:"+temp.getValue()+"||");
                        temp = temp.getRight();
                  }

            }
            System.out.println("");
      }


      //判断是否为空树
      private void checkedNotNull(Node root) {
            if (root==null){
                  throw new RuntimeException("空树不能进行遍历");
            }
      }
}
 
2.平衡二叉树
    在二叉查找树中有个很打的缺陷:可能出现二叉查找树变为链表,即时间复杂度向O(n)倾斜。为了解决该问题,我们需要在向树中添加元素时做到自动平衡功能,即要让树保持:树中任意结点的左右子树的高度差(平衡因子)不能大于1。满足这个条件的二叉查找树,称之为平衡二叉树。
  • 平衡因子:又称平衡度,表示的是结点的左右子树的差值。
    建立平衡二叉树的目的为了减少二叉查找树的查找次数。我们知道,在二叉查找树中查找某个元素的次数与二叉查找树的高度有着:n<=high的关系(其中,n为查找次数,high为高度)。因此,减少二叉树的高度(层次),便能有效的提高查找的速度。
    常见的实现二叉查找树自平衡的算法有:AVL树,红黑树,替罪羊树等。另外还有不是二叉树的平衡树,B树,B+树等(不做过多讨论)。因为Java中HashMap、TreeSet和TreeMap底层采用红黑树,这里只讨论红黑树。
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!