红黑树AVL先了解下最基础的二叉树吧

45次阅读

共计 5371 个字符,预计需要花费 14 分钟才能阅读完成。

什么是树

树这种数据结构在生活中非常常见,比如去图书馆找一本书,书是按照不同的分类来摆放的。比如电脑中的磁盘文件夹等等。使用树结构存储数据后,会出奇的高效。见名知意,树这种数据结构就像一个倒着的树一样,也会有树根,树的枝杈,还有树叶。

二叉树名词解释

  • 根节点:最上面节点叫根节点,根节点没有父节点,一个二叉树只存在一个根节点
  • 子节点:每个父节点下面的节点叫做子节点,分为左和右,可以有一个,也可以有两个,最多也只能有两个
  • 叶子节点:如果一个节点没有任何的子节点了,那么就称作叶子节点
  • 左右子树:二叉树具有天然的递归结构性值,根节点下每个子节点也可以组成自己的一颗二叉树。

二分搜索树

  • 二分搜索树是一个二叉树
  • 二分搜索树的每一个节点的值,都大于左子树所有节点的值,小于右子树所有节点的值
  • 存储的元素必须是有可比较性,不一定是数字,也可以实现 Compable 接口比较细节

以下都属于二叉树。首先二叉树不是平衡二叉树,多个节点少个节点都无所谓,深度不一样也无所谓。



元素的插入操作

先用 28 跟 41 比大小,比 41 小,根据二叉树特性,比当前节点小的节点都会在左面,所以 28 往左走,跟 22 比,28 比 22 大,大的就往右走和 33 比,28 比 33 小,小的往左边走,一看正好 33 左面为空,有个地方能放进去,最后将 28 存储到 33 节点左孩子节点的位置。

public class BinarySearchTree<E extends Comparable>{
    private class Node {
        public E e;
        public Node left,right;
        public Node(E e) {
            this.e = e;
            this.left = null;
            this.right = null;
        }
    }

    private Node root; // 根节点
    private int size;
    
    public boolean isEmpty() {return size == 0;}

    public int getSize() {return size;}

    public BinarySearchTree() {
        root = null;
        size = 0;
    }

    public void add(E e) {
        // 如果此时没有根节点
        if(root == null) {
            // 添加元素到根节点
            root = new Node(e);
            size++;
        }
        else {add(root, e);// 代表有根节点
        }
    }

    private void add(Node root,E e)
    {
        //1. 最根本的问题。和当前节点比较,看往那边放,前提是子节点为空的情况下
        if(e.equals(root.e)) {return;}

        else if(e.compareTo(root.e) < 0 && root.left == null) {root.left = new Node(e);
            size++;
            return;
        }
        else if(e.compareTo(root.e) > 0 && root.right == null) {root.right = new Node(e);
            size++;
            return;
        }
        //2. 把问题转换为更小的问题过程。这个 add 方法解决的就是根据当前节点比较,把元素插入合适的位置
        if(e.compareTo(root.e) < 0) {// 往左放
            add(root.left,e);
        }
        else{ // 相等一开始已经判断过了,这里只需要写 else 就可以了
            add(root.right,e);
        }
    }
}

是否包含某个元素

private boolean contains(Node node,E e)
{if(node == null)
        return false;
    if(e.compareTo(node.e) == 0) { // 找到了对应的节点
        return true;
    }
    else if (e.compareTo(node.e) > 0) {  // 大于当前节点就向右继续递归,否则向左递归继续找
        return contains(node.right,e);
    }
    else{return contains(node.left,e);
    }
}

二分搜索树的遍历

每个元素都有三次遍历机会,分别是在调用两侧递归函数之前,在两侧递归函数中间,和在两侧递归函数之后。

 前序遍历
public void preOrder()
{preOrder(root);
}

private void preOrder(Node node) {if(node == null)
        return;
    System.out.print(node.e + "---");
    preOrder(node.left);
    preOrder(node.right);
}

  
中序遍历
public void inOrder() {inOrder(root);
}

private void inOrder(Node node) {if(node == null)
        return;
    inOrder(node.left);
    System.out.print(node.e + "---");
    inOrder(node.right);
}

后序遍历
public void postOrder() {postOrder(root);
}

private void postOrder(Node node) {if(node == null)
        return;
    postOrder(node.left);
    postOrder(node.right);
    System.out.print(node.e + "---");
}

如下添加了这些元素之后,树的结构应该是这样的

int[] arr = {12,23,45,6,8,3,21};
for(int x = 0; x < arr.length; x++)
{tree.add(arr\[x\]);
}
tree.preOrder();

//--------------------------------

     12
   /    \
  6      23
 /  \    /  \
3    8  21   45

前序遍历:12 - 6 -3 - 8 - 23 - 21 - 45
中序遍历:3 - 6 - 8 - 12 - 21 - 23 - 45
后序遍历:3 - 8 - 6 - 21 - 45 - 23 -12

查找最小值和最大值

因为二分查找树的特性,最小值一定在最左侧,如果一个节点的左子节点为 null 了,那么这个节点就是最小值。相反最大值在右侧。

// 找到最大值和最小值
public E getMin()
{Node min = getMin(root);
    return min.e;
}

private Node getMin(Node node)
{if(node.left == null)
        return node;
    return getMin(node.left);
}

// 找到最大值和最小值
public E getMax()
{Node min = getMax(root);
    return min.e;
}

private Node getMax(Node node)
{if(node.right == null)
        return node;
    return getMax(node.right);
}

删除最大和最小值

和查找最大值最小值思想差不多,只不过要注意下面这种情况。16 的左子节点为空,所以这个最小值是 16,删除 16 之后,要把 16 的右子节点和 28 关联上。也就是递归的时候,要把当前节点的左孩子节点和删除最值后返回的节点关联上。

// 删除最小元素
public E removeMin()
{E min = getMin();
    root = removeMin(root);
    return min;
}

private Node removeMin(Node node)
{if(node.left == null) { // 这里如果 node 的右子节点为空,逻辑也是成立的
        Node rightNode = node.right;
        node.right = null;
        size--;
        return rightNode;
    }
    node.left = removeMin(node.left);
    return node;
}

// 删除最大元素
public E removeMax()
{E max = getMax();
    root = removeMax(root);
    return max;
}

private Node removeMax(Node node)
{if(node.right == null) { // 这里如果 node 的右子节点为空,逻辑也是成立的
        Node leftNode = node.left;
        node.left = null;
        size--;
        return leftNode;
    }
    node.right = removeMax(node.right);
    return node;

}

删除任意节点

比如要删除 58 这个节点,如果 58 没有左孩子或者右孩子其中一个节点,那么逻辑是和删除最大最小值是一样的,假设没有 50 这个左孩子节点,那么删除了 58 之后,将他的右侧节点 60 返回就好了。但如果 58 两侧都有节点,删除了 58 之后就面临着谁来当这个 ” 老大 ” 的问题,这个老大有两个人可以当,一个是 59,一个是 53。也就是右子树的最小值或者左子树的最大值。

知道这个节点以后,就可以维护这个节点的关系,最后给上层返回这个节点即可。那么维护关系其实就是让 59 的左孩子节点变成当前 58 的左孩子节点,让 59 的右孩子节点变成除了 59 以外的新节点。最后把 58 的关联关系干掉即可。

public void deleteNode(E e)
{root = deleteNode(root,e);
}

private Node deleteNode(Node node, E e) {if(node == null)
        return null;
    if(e.compareTo(node.e) > 0) {node.right = deleteNode(node.right,e);
        return node;
    }
    else if(e.compareTo(node.e) < 0) {node.left = deleteNode(node.left,e);
        return node;
    }
    else{ // 此时节点相等
        // 左子树为空
        if(node.left == null) {
            Node rightNode = node.right;
            node.right = null;
            size--;
            return rightNode;
        }
        // 左子树为空
        if(node.right == null) {
            Node leftNode = node.left;
            node.left = null;
            size--;
            return leftNode;
        }

        //1. 找到待删除节点的接班人,接班人就是当前右子节点中最小的那个
        //2. 用接班人顶替待删除节点
        Node successor = getMin(node.right);
        successor.right = removeMin(node.right);
        successor.left = node.left;
        node.left = node.right = null;
        return successor;
    }
}

正文完
 0