《算法》3.3平衡查找树

平衡查找树

1、2-3查找树

定义:

一棵2-3查找树或为一棵空树。或又以下结点组成:

  • 2-结点:含有一个键和两条连接
  • 3-结点:含有两个键和三条连接

1.1查找

和标准的二叉查找树类似

1.2插入

有以下几种情况:

1、向2-结点中插入新键
  • 把这个2-结点替换为一个3-结点
2、向一棵只含有一个3-结点的树中插入新键
  • 先将新键存入该结点,成为一个4-结点**(新建4-结点)**
  • 转换为3个2-结点**(分解)**
3、向一个父结点为2-结点的3-结点插入新键
  • 新建4-结点
  • 分解:将中键插入到父结点中
4、向一个父结点为3-结点的3-结点中插入新键
  • 新建4-结点
  • 分解:将中键插入到父结点中
  • 此时父结点变为一个4-结点
  • 递归对父结点进行同样的操作。

1.3局部变换

插入算法的变换是局部的:

  • 除了相关的结点和链接之外不会修改或者检查树的其他部分

1.4全局性质

局部变换不会影响树的全局的有序性平衡性

  • 任意空链接到根结点的路径长度都是相等的。

标准的二叉查找树是由上向下生长的,2-3树是由下向上生长的。

在一棵大小为N的2-3树中,查找和插入的操作访问的结点必然不超过lgN个。

2、红黑二叉查找树

2-3查找树性能很好,但是转换调整操作需要移动对象,太过复杂麻烦。

**红黑二叉查找树(红黑树)**就是用标准的二叉查找树和一些额外的信息来表示2-3树。

  • 红链接:将两个2-结点连接起来构成一个3-结点。
  • 黑链接:就是普通的链接。

2.1定义

红黑树就是满足以下条件的二叉查找树:

  • 红链接均为左链接
  • 没有任何一个结点同时和两条红链接相连
  • 是完美黑色平的,即任意空链接到根结点的路径上的黑链接数量都是相等的。

我们将指向结点自己的链接的颜色存储在自己上。

2.2旋转

旋转操作可以改变红链接的指向。

左旋

目的:将一条红色右链接转化为一条红色左链接。

操作:

  • 红色右链接连接着一个子结点和一个父结点,
  • 只需要将子结点变为父结点,父结点变为左子结点。

右旋

和左旋相反。

2.3插入

有以下几种情况:

1、向单个2-结点插入新键
  • 如果新键小于老键:直接在左新增一个红色结点
  • 如果新键大于老键:在右新增一个红色结点,再左旋。
2、向树底的2-结点插入新键
  • 同上。
3、向一棵双键树(即一个3-结点)插入新键

分3种情况:

  • 如果新键大于原树的两个键:在父结点的右新建一个红色结点,将两条红链接变成黑色。
  • 如果新键小于原树的两个键:在子结点的左新建一个红色结点,将上层的红链接右旋,得到第一种情况。
  • 如果新键介于原树的两个键之间:在子结点的右新建一个红色结点,将下层的红链接左旋,得到第二种情况。
4、向树底的3-结点插入新键
  • 同上

2.4颜色转换

  • 将子结点的颜色由红变黑
  • 将父结点的颜色由黑变红

但是,每次插入之后要将树的根结点置为黑色。

2.5将红链接在树中向上传递

在沿着插入点到根结点的路径上向上移动时,在经过的每个结点中顺序完成以下操作,就能完成插入操作:

  • 如果右子结点是红色的,而左子结点是黑色的,进行左旋。
  • 如果左子结点是红色的,且它的左子结点也是红色的,进行右旋。
  • 如果左右子结点都是红色的,就进行颜色转换。

2.6实现

/**
 * 红黑树
 * @Author: AZhu
 * @Date: 2021/3/5 23:30
 */
public class RedBlackBST<Key extends Comparable<Key>, Value> {

    private static final boolean RED = true;
    private static final boolean BLACK = false;
    
    private Node root;//根结点

    private class Node {
        Key key;//键
        Value value;//值
        Node left, right;//左右子树
        int N;//以该节点为根的树中的结点总数
        boolean color;//指向自己的链接的颜色

        public Node(Key key, Value value, int n, boolean color) {
            this.key = key;
            this.value = value;
            this.N = n;
            this.color = color;
        }
    }

    private boolean isRed(Node x) {
        if (x==null) return false;
        return x.color == RED;
    }

    /**
     * 左旋
     * @param h
     * @return
     */
    private Node rotateLeft(Node h) {
        Node x = h.right;
        h.right = x.left;
        x.left = h;
        x.color = h.color;
        h.color = RED;
        x.N = h.N;
        h.N = size(x.left) + size(x.right) + 1;
        return x;
    }

    /**
     * 右旋
     * @param h
     * @return
     */
    private Node rotateRight(Node h) {
        Node x = h.left;
        h.left = x.right;
        x.right = h;
        x.color = h.color;
        h.color = RED;
        x.N = h.N;
        h.N = size(x.left) + size(x.right) + 1;
        return x;
    }

    /**
     * 返回树的结点数目
     * @return
     */
    public int size() {
        return size(root);
    }
    private int size(Node x) {
        if (x == null) {
            return 0;
        } else {
            return x.N;
        }
    }

    /**
     * 颜色转换
     * @param h
     */
    private void flipColor(Node h) {
        h.color = RED;
        h.left.color = BLACK;
        h.right.color = BLACK;
    }

    /**
     * 查找key对应的value
     * @param key
     * @return
     */
    public Value get(Key key) {
        return get(root, key);
    }
    private Value get(Node x, Key key) {
        if (x==null) return null;
        int cmp = key.compareTo(x.key);//比较
        if (cmp < 0) {
            return get(x.left, key);//到左子树查找
        } else if (cmp > 0) {
            return get(x.right, key);//到右子树查找
        } else {
            return x.value;//找到了
        }
    }

    /**
     * 插入或更新一对键值对
     * @param key
     * @param value
     */
    public void put(Key key, Value value) {
        root = put(root, key, value);
        root.color = BLACK;
    }
    private Node put(Node h, Key key, Value value) {
        if (h==null) return new Node(key, value, 1, RED);//新建结点
        int cmp = key.compareTo(h.key);//比较
        if (cmp < 0) {
            h.left = put(h.left, key, value);//到左子树
        } else if (cmp > 0) {
            h.right = put(h.right, key, value);//到右子树
        } else {
            h.value = value;//更新
        }
        //沿路径返回调整
        if (isRed(h.right) && !isRed(h.left)) {
            h = rotateLeft(h);
        }
        if (isRed(h.left) && isRed(h.left.left)) {
            h = rotateRight(h);
        }
        if (isRed(h.left) && isRed(h.right)) {
            flipColor(h);
        }
        //增加路径上的每一个结点的N值
        h.N = size(h.left) + size(h.right) + 1;
        return h;
    }
    
}

2.7分析

所有基于红黑树的符号表实现都能保证操作的运行时间为对数级别。

一棵大小为N的红黑树的高度不会超过2lgN

红黑树是平衡的,因此查找比二叉查找树更快。

红黑树的有序性相关操作与二叉查找树一致,并且在最坏情况下的运行时间也是对数级别的。

  • 因此,对于大小可能上亿的表,仍能保证在几十次比较之内完成这些操作。

简单的符号表实现的成本总结:

算法最坏情况平均情况是否高效的支持有序性相关的操作
查找插入查找插入
顺序查找NNN/2N
二分查找lgN2NlgNN
二叉查找树NN1.39lgN1.39lgN
红黑树2lgN2lgN1.00lgN1.00lgN

3答疑

Q1:为什么不存在红色右链接和4-结点?

A1:因为这样可以大大减少代码量。

Q2:为什么不在Node类型中用一个数组来表示2-结点和3-结点?

A2:这样的话就是B树了,并且数组带来的额外开销太大了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值