chapter5 关联式容器:树&红黑树


1 关联式容器

所谓关联式容器,观念上类似关联式数据库(实际上则简单许多):每笔数据(每个元素)都有一个键值(key)和一个实值(value)。当元素被插入到关联式容器中时,容器内部结构(可能是RB-tree,也可能是hash-table)便依照其键值大小,以某种特定规则将这个元素放置于适当位置。关联式容器没有所谓头尾(只有最大元素和最小元素),所以不会有所谓 push_back()、push_front()、pop_back()、pop_front()、begin()、end()这样的操作行为。

2 树

2.1 二叉树

在这里插入图片描述

2.2 二叉搜索树

可提供对数时间的元素插入和访问。

节点放置规则:任何节点的键值一定大于其左子树中的每一个节点的键值,并小于其右子树中的每一个节点的键值

在这里插入图片描述

节点插入:

在这里插入图片描述
节点删除:

在这里插入图片描述

2.3 平衡二叉搜索树

在这里插入图片描述

2.4 AVL tree

2.4.1 定义

“加上了额外平衡条件”的二叉搜索树,要求任何节点的左右子树高度相差最多1

在这里插入图片描述

平衡被破坏的四种情况:
在这里插入图片描述
在这里插入图片描述

2.4.2 单旋转

在这里插入图片描述

2.4.3 双旋转

在这里插入图片描述

3 RB-tree(红黑树)

3.1 特性

不仅是一个二叉搜索树(相对接近平衡),还需满足以下条件:

  1. 每个节点不是红色就是黑色
  2. 根节点为黑色
  3. 每个叶结点是黑色(此处的叶节点是指为空NULL的叶子节点)
  4. 父子两节点不得同时为红
  5. 任意节点至NULL(树尾端)的任何路径,所含之黑节点数目必须相同
    在这里插入图片描述

3.2 插入节点

插入新节点破坏RB-tree的规则,必须调整树形,即旋转树形并改变节点颜色

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.3 一个由上而下的程序

在这里插入图片描述

3.4 RB-tree的节点设计

//节点双层结构
typedef bool __rb_tree_color_type;
const _rb_tree_color_type __rb_tree_red = false;  //红色为0
const _rb_tree_color_type __rb_tree_black = true; //黑色为1

struct __rb_tree_node_base {
	typedef __rb_tree_color_type color_type;
    typedef __rb_tree_node_base* base_ptr;
    
    color_type color; //节点颜色,非红即黑
    base_ptr parent;  //各种操作常需要上溯其父节点
    base_ptr left;    //指向左节点
    base_ptr right;   //指向右节点
    
    static base_ptr minimum(base_ptr x) {
        while(x->left != 0) x = x->left; //一直向左走,就能找到最小值
    	return x;
    }
    
    static base_ptr maximum(base_ptr x) {
        while(x->right != 0) x = x->right; //一直向右走,就能找到最大值
        return x;
    }
};

template<class Value>
struct __rb_tree_node : public __rb_tree_node_base {
	typedef __rb_tree_node<Value>* link_type;
    Value value_field;	//节点值
};

3.5 RB-tree的迭代器

在这里插入图片描述
RB-tree的迭代器属于双向迭代器,但不具备随机定位能力。RB-tree迭代器的前进操作operator++()调用了基层迭代器的increment(),RB-tree迭代器的后退操作operator--()则调用了基层迭代器的decrement()

//基层迭代器
struct __rb_tree_base_iterator {
	typedef __rb_tree_node_base::base_ptr base_ptr;
    typedef bidirectional_iterator_tag iterator_category;
    typedef ptrdiff_t difference_type;
    
    base_ptr node;
    
    void increment() {
        if(node->right != 0) {  //如果有右子节点
            node = node->right;
            while(node->left != 0) node = node->left;
        }
        else {					//没有右子节点
            base_ptr y = node->parent;
            while(node == y->right) {	//判断当前节点是否为右子节点
                node = y;				//若是,则一直上溯,直到不为右子节点
                y = y->parent;
            }
            if(node->right != y) node = y;	//右子节点不等于此时的父节点
        }
    }
    
    void decrement() {
        if(node->color == __rb_tree_red && node->parent->parent == node)	//如果是红节点且父节点的父节点等于自己
            node = node->right;
        else if(node->left != 0) {	//如果有左子节点
            base_ptr y = node->left;
            while(y->right != 0) y = y->right;
            node = y;
        }
        else {	//既非根节点也无左子节点
            base_ptr y = node->parent;
            while(node == y->left) {	//当前节点为左子节点
                node = y;
                y = y->parent;			//上溯至不为左子节点
            }
            node = y;
        }
    }
};

//正规迭代器
template <class Value, class Ref, class Ptr>
struct __rb_tree_iterator : public _rb_tree_base_iterator {
    //...
    self& operator++() { increment(); return *this; }
    self operator++(int) {
        self tmp = *this;
        increment();
        return tmp;
    }
    
    self& operator--() { decrement(); return *this; }
    self operator--(int) {
        self tmp = *this;
        decrement();
        return tmp;
    }
};

3.6 RB-tree的数据结构

型别定义:

template<class Key, class Value, class KeyOfValue, class Compare, class Alloc = alloc>
class rb_tree {
protected:
    typedef void* void_pointer;
    typedef __rb_tree_node_base* base_ptr;
    typedef __rb_tree_node<Value> rb_tree_node;
    typedef simple_alloc<__rb_tree_node, Alloc> rb_tree_node_allocator;
    typedef __rb_tree_color_type color_type;
public:
    typedef Key key_type;
    typedef Value value_type;
    typedef value_type* pointer;
    typedef const value_type* const_pointer;
    typedef value_type& reference;
    typedef const value_type& const_reference;
    typedef rb_tree_node* link_type;
    typedef size_t size_type;
    typedef ptrdiff_t difference_type;
    //...
};

节点配置和销毁:

protected:
	link_type get_node() { return rb_tree_node_allocator::allocate(); }
	void put_node(link_type p) { rb_tree_node_allocator::deallocate(p); }
	
    link_type create_node(const value_type& x) {
        link_type tmp = get_node(); //配置空间
        __STL_TRY {
            construct(&tmp->value_field, x); //构造内容
        }
        __STL_UNWIND(put_noed(tmp));
        return tmp;
    }

	void destory_node(link_type p) {
        destory(&p->value_field);	//析构内容
        put_node(p);				//释放内存
    }

维护RB-tree的三笔数据:

protected:
	size_type node_count;	//记录树的大小(节点数量)
	link_type header;       //实现上的一个技巧
	Compare key_compare;	//节点间的键值大小比较准则

构造与析构:

private:
	void init() {
        header = get_node();	//产生一个节点空间,令header指向它
        color(header) = _rb_tree_red;
        
        root() = 0;
        leftmost() = header; //令header的左子节点为自己
        rightmost() = header; //令header的右子节点为自己
    }

public:
    rb_tree(const Compare& comp = Compare()) : node_conut(0), key_compare(comp) {
    	init();
    }
	
	~rb_tree() {
        clear();
        put_node(header);
    }

3.7 RB-tree的构造与内存管理

由上述RB-tree的数据结构可知,它定义了专属的空间配置器**rb_tree_node_allocator**。

RB-tree的构造方式有两种,一种是以现有的RB-tree复制一个新的RB-tree,另一种是产生一颗空树:

rb_tree<int, int, identity<int>, less<int>> itree;

上述代码指定了节点的键值、实值、大小比较标准然后调用了RB-tree的默认构造函数,由上一节默认构造的程序可知它调用了init()。

在init()中为RB-tree的根节点再设计了一个父节点,名为header:

在这里插入图片描述
因此,再插入新节点时,不仅需要依照RB-tree的规则来调整,还需要维护header的正确性,使其父节点指向根节点,左子节点指向最小节点,右子节点指向最大节点。

3.8 RB-tree的元素操作

3.8.1 元素插入

  1. insert_equal(const Value& v):(插入新值,节点键值允许重复)该函数负责从根节点开始,往下寻找适当的插入点,返回__insert(x, y, v)真正负责元素插入
  2. insert_unique(const Value& v):(插入新值,节点键值不允许重复,若重复则插入无效),返回值是一个pair,第一元素是RB-tree迭代器,指向新增节点,第二元素表示插入成功与否

真正的插入执行程序:

__insert(base_ptr x_, base_ptr y_, const Value& v)

x_为新值插入点,y_为插入点之父节点,v为新值

在该程序中维护header的正确性,并调用__rb_tree_rebalance(z, header->parent)调整RB-tree。

调整RB-tree(旋转及改变颜色):

__rb_tree_rebalance(__rb_tree_node_base* x, __rb_tree_node_base*& root)

程序实现逻辑可参考上述3.2、3.3节,根据不同情况有时只需调整节点颜色,有时要单旋转(右旋或左旋),有时要双旋转

左旋__rb_tree_rotate_left(__rb_tree_node_base* x, __rb_tree_node_base*& root)

右旋__rb_tree_rotate_right(__rb_tree_node_base* x, __rb_tree_node_base*& root)

在这里插入图片描述

3.8.2 元素的搜寻

RB-tree提供的find函数:

//寻找RB数中是否有键值为k的节点
template<class Key, class Value, class KeyOfValue, class Compare, class Alloc>
typename rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::iterator
    rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::find(const Key& k) {
        link_type y = header;
        link_type x = root();
        
        while(x != 0) {
            if(!key_compare(key(x), k))
                //x键值大于k,遇到大值往左走
                y = x, x = left(x);
            else
                //x键值小于k,遇到小值往右走
                x = right(x);
            iterator j = iterator(y);
            return (j == end() || key_compare(k, key(j.node))) ? end() : j;
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值