AVL树

  • AVL树定义:红黑树是一颗二叉搜索树,特别的是一棵保持高度平衡的二叉搜索树

  • AVL树特点:

    • 每个结点的左右子树的高度之差的绝对值(平衡因子)最多为1

  • AVL树插入:

    • 说明:新增节点的平衡因子是0,新增节点是右节点,父节点平衡因子+1,新增节点是左节点,父节点平衡因子-1

    • 插入新增节点后,父节点平衡因子变为0,说明节点插入在较矮的子树上,平衡没被破坏,高度也没变化,直接插入无需做任何处理

    • 插入新增节点后,父节点平衡因子变为+1或是-1,说明插入前节点的平衡因子是0,平衡没被破坏,但是高度+1.需要向上调整

    • 插入新增节点后,父节点平衡因子变为+2或是-1,说明节点插入在较高的子树上,平衡被破坏,根据下面4中情况调整

      • LL型:右旋

      • LR型:左旋变成LL型,右旋

      • RR:左旋

      • RL:右旋变成RR型,左旋

      • 调整后恢复平衡,且高度没有变化

  • AVL树删除:

    • 删除节点有两个孩子节点:中序遍历,找出删除节点的前驱或是后继节点,交换二者的数据,然后删除节点变成下面两种情况中的一种

    • 删除节点只有一个孩子节点:孩子节点替代删除的节点,向上调整

    • 删除节点无孩子节点:直接删除目标节点,向上调整

    • 向上调整:

      • 删除后平衡因子不变,不做处理

      • 删除左子树的节点,若失去平衡,令t=右子树,若t的左子树高度>t的右子树高度,相当于在右子树的左子树插入节点,执行RL操作,否则执行RR操作

      • 删除右子树的节点,若失去平衡,令t=左子树,若t的左子树高度>t的右子树高度,相当于在左子树的左子树插入节点,执行LL操作,否则执行LR操作

红黑树

  • 红黑树定义:红黑树是一颗二叉搜索树,特别的是一棵保持一定平衡的二叉搜索树

  • 红黑树的特点:

    1. 节点是红色或黑色

    2. 根节点是黑色

    3. 每个叶节点(NULL节点,无实际意义,只有颜色属性)是黑色

    4. 每个红色节点的两个子节点都是黑色(即:不能有两个连续的红色节点)

    5. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点

  • 红黑树的5点性质保证了整颗树中最长路径不大于最短路径的两倍,从而使得整棵树基本保持平衡

  • 红黑数的插入:

    • 插入节点标记为红色(如果是黑的,就会破坏性质5)

    • 插入节点的父节点为黑色,直接插入就行

    • 插入节点的父节点为红色:

      • 插入节点的叔父节点为红色,将插入节点和叔父节点变为黑色,父节点变为红色,向上调整只到根节点

      • 插入节点的叔父节点为黑色:

        • LL型:右旋,变色(不需要向上调整)

        • RL型:右旋变成LL型

        • RR型:左旋,变色(不需要向上调整)

        • LR型:左旋变成RR型

  • 红黑树删除:

    • 删除节点有两个孩子节点:中序遍历,找出删除节点的前驱或是后继节点,交换二者的数据,不交换颜色属性,然后删除节点变成下面两种情况中的一种

    • 删除节点只有一个孩子节点:

      • 删除节点是红色节点:直接删除

      • 删除节点是黑色节点:

        • 孩子节点是红色节点:删除节点后,将孩子节点变黑

        • 孩子节点是黑色节点:不存在这种情况,违反性质5

    • 删除节点无孩子节点

      • 删除节点是红色:直接删除

      • 删除节点是黑色:

        • 删除节点是左孩子

          • 兄弟节点是红色:父节点和兄弟节点的颜色互换,然后左旋,变成兄弟节点是黑色的情况

          • 兄弟节点是黑色:

            • 远侄节点是红色(近侄颜色没要求):父节点和兄弟节点的颜色互换,然后左旋,最后把操作前的远侄节点变成黑色,删除掉需要目标节点即可

            • 远侄节点是黑色:

              • 近侄节点为黑色:

                • 父节点为红色:父亲节点改成黑色,将兄弟节点改成红色,然后删目标节点

                • 父节点为黑色:将兄弟节点S的颜色改成红色,删除目标节点,以父节点为起点,向上调整

              • 近侄节点为红色:右旋,交换兄弟节点和近侄节点的颜色,变成远侄为红色节点的情况

        • 删除节点是右孩子

          • 兄弟节点是红色:父节点和兄弟节点的颜色互换,然后左旋,变成兄弟节点是黑色的情况

          • 兄弟节点是黑色:

            • 远侄节点是红色:父节点和兄弟节点的颜色互换,然后左旋,最后把操作前的远侄节点变成黑色,删除掉需要目标节点即可

            • 远侄节点是黑色:

              • 近侄节点为黑色:

                • 父节点为红色:父亲节点改成黑色,将兄弟节点改成红色,然后删目标节点

                • 父节点为黑色:将兄弟节点S的颜色改成红色,删除目标节点,以父节点为起点,向上调整

              • 近侄节点为红色:左旋,交换兄弟节点和近侄节点的颜色,变成远侄为红色节点的情况

  • 红黑树删除的口诀:先看待删除的节点的颜色,再看兄弟节点的颜色,再看侄子节点的颜色(侄子节点先看远侄子再看近侄子),最后看父亲节点的颜色

RB-tree和iterator之间的关系

RB-tree节点设计

typedef bool __rb_tree_color_type;
const __rb_tree_color_type _rb_tree_red = false;
const __rb_tree_color_type _rb_tree_black = true; 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; // 指向父节点
bsae_ptr left; // 指向左孩子
base_ptr right; // 指向右孩子 static base_ptr mimimum(base_ptr x)
{
while(x->left != )
{
x = x->left;
}
return x;
} static base_ptr maximum(base_ptr x)
{
while(x->right != )
{
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;
}

RB-tree迭代器

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 != )
{
node = node->right;
while(node->left != )
{
node = node->left;
}
}
else
{
// 当前节点的右节点不存在,就寻找第一个父节点且父节点的右孩子不是自己的节点,就是下一个节点
base_ptr y = node->parent;
while(node == y->right)
{
node = y;
y = node->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 != )
{
// 当前节点存在左节点,寻找左节点中的最大值(一直向右走,走到底就是最大值)
node = node->left;
while(node->right != )
{
node = node->right;
}
}
else
{
// 当前节点的左节点不存在,就寻找第一个父节点且父节点的左孩子不是自己的节点,就是上一个节点
base_ptr y = node->parent;
while(node == y->left)
{
node = y;
y = node->parent;
} node = y;
}
}
}
template <class Value, class Ref, class Ptr>
struct __rb_tree_iterator : public __rb_tree_base_iterator
{
typedef Value value_type;
typedef Ref reference;
typedef Ptr pointer;
typedef __rb_tree_iterator<Value, Value&, Value*> iterator;
typedef __rb_tree_iterator<Value, const Value&, const Value*> const_iterator;
typedef __rb_tree_iterator<Value, Ref, Ptr> self;
typedef __rb_tree_node<Value>* link_type; __rb_tree_iterator() {} __rb_tree_iterator(link_type x) { node = x} __rb_tree_iterator(const iterator& it) { node = it.node} reference operator*() const
{
return link_type(node)->value_field;
} pointer operator->() const
{
return &(operaotr*());
} self& operator++()
{
increment();
return *this;
} self operator++(int)
{
self temp = *this;
increment();
return temp;
} self& operator--()
{
decrement();
return *this;
} self operator--(int)
{
self temp = *this;
decrement();
return temp;
}
}

RB-tree数据结构

template <class Key, class Value, class KeyOfValue, class Compare, class Alloc = alloc>
class rb_tree
{
protect:
typedef void* void_pointer;
typedef __rb_tree_node_base* base_ptr;
typedef __rb_tree_node<Value> tr_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 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; protect:
link_type get_node()
{
return rb_tree_node_allocator::allocate();
} void put_node(link_type p)
{
rb_tree_node_allocator::dealocate(p);
} link_type create_node(const value_type& x)
{
link_type tmp = get_node();
construct(&tmp->value_field, x);
return tmp;
} void destroy_node(link_type p)
{
destroy(&p->value_field)
put_node(p)
} protected:
size_type node_count; // 记录节点总个数
link_type header; // 门卫节点
Compare key_compare; // 比较key值大小的函数 link_type& root() const { return (link_type&)header->parent; }
link_type& leftmost() const { return (link_type&)header->left; }
link_type& rightmost() const { return (link_type&)header->right; } static link_type minimun(link_type x)
{
return (link_type) __rb_tree_node_base::minimun(x);
} static link_type maximun(link_type x)
{
return (link_type) __rb_tree_node_base::maximun(x);
} public:
typedef __rb_tree_iterator<value_type, reference, pointer> iterator; private:
void init()
{
header = get_node();
color(header) = __rb_tree_red; // 初始化时都指向自己
root() = ;
leftmost() = header;
rightmost() = header;
} public:
rb_tree(const Compare& comp = Compare()) : node_count(), key_compare(comp)
{
init();
} // 不可插入相同的key,否则失败
pair<iterator, bool> insert_unique(const value_type& x); // 可以插入相同的key
pair<iterator, bool> insert_equal(const value_type& x); // 查找
iterator find(const Key& k)
{
link_type y = header;
link_type x = root(); while(x != )
{
if(!kwy_compare(key(x), k))
{
y = x;
x = left(x);
}
else
{
x = right(x);
}
} iterator j = iterator(y); return (j == end() || key_compare(k, key(j.node))) ? end() : j;
}
}

set

  • 底层是红黑树

  • set中的元素只有key,没有value

  • set中不允许存在两个相同的元素

multiset

  • mutliset和set基本类似,唯一的不同是set不允许重复,mutliset允许重复

  • set使用insert_unique,mutliset使用insert_equal

map

  • 底层是红黑树

  • map中的元素是键值对

  • map中不允许存在两个相同的元素

  • 键值对pair的定义:

template <class T1, class T2>
struct pair
{
typedef T1 first_type;
typedef T2 second_type; T1 first;
T2 second; pair() : first(T1()), seocond(T2()) {}
pair(const T1& a, const T2& b) : first(a), second(b) {}
}

multimap

  • mutlimap和map基本类似,唯一的不同是map不允许key重复,mutlimap允许重复

  • map使用insert_unique,mutlimap使用insert_equal

hashtable

  • 以空间换时间

  • 用一个足够大的vector保存所有的数据,为了使得所有数据都可以对应数组中唯一的一个下标,因此使用一个映射函数,将数据映射成下标,然后存储到vector中

    • 如果映射的下标处已经存在数据,就发生“碰撞”,需要使用其他方法进行规避,如:

      • 线性探测

      • 二次探测

      • 开链

    • 如果映射的下标处没有数据,直接存储

  • hashtable中的数据除以vector的大小叫负载系数,如果负载系数超过一定值,就需要扩大vector

  • 线性探测:如果映射出的下标已经存储了数据,那么将下标加1,进行存储,如果还是已经存储了数据,那么再加1,依次往后...

  • 二次探测:如果映射出的下标已经存储了数据,那么将下标加1^2,进行存储,如果还是已经存储了数据,那么再加2^2,依次往后...

  • 开链:如果映射出的下标已经存储了数据,那么直接将数据头插到该下标指向的链表中,即同一链表中的下标值都相同(类似deque的底层数据存储结构)

hashtable的各种结构定义

  • 节点定义

template <class Value>
struct __hashtable_node
{
__hashtable_node* next;
Value val;
};
  • 迭代器定义
template <class Value, class Key, class HashFcn, class ExtractKey, class EqualKey, class Alloc>
struct __hashtable_iterator
{
typedef hashtable<Value, Key, HashFcn, ExtractKey, EqualKey, Alloc> hashtable;
typedef __hashtable_iterator<Value, Key, HashFcn, ExtractKey, EqualKey, Alloc> iterator;
typedef __hashtable_node<Value> node; typedef forward_iterator_tag iterator_category;
typedef Value value_type;
typedef ptrdiff_t difference_type;
typedef size_t size_type;
typedef value& reference;
typedef value* pointer; node* cur;
hashtable* ht; __hashtable_iterator(node* n, hashtable* tab) : cur(n), ht(tab){}
__hashtable_iterator() {} reference operator*() const { return cur->val; }
pointer operator->() const { return &(operator*()); } iterator& operator++()
{
const node* old = cur;
cur = cur->next;
if(!cur)
{
size_type bucket = ht->bkt_num(old->val);
while(!cur && ++bucket < ht->buckets.size())
{
cur = ht->buckets[bucket];
}
}
return *this;
} iterator& operator++(int)
{
iterator tmp = *this;
++*this;
return tmp;
} bool operator==(const iterator& it) const { return cur == it.cur; }
bool operator!=(const iterator& it) const { return cur != it.cur; }
}
  • hashtable定义
template <class Value, class Key, class HashFcn, class ExtractKey, class EqualKey, class Alloc>
class hashtable
{
public:
typedef HashFcn hasher;
typedef EqualKey key_equal;
typedef size_t size_type; private:
hasher hash;
key_equal equals;
ExtractKey get_key; typedef __hashtable_node<Value> node;
typedef simple_alloc<node, Alloc> node_allocator; vector<node*, Alloc> buckets;
size_type num_elements; public:
size_type bucket_count() const { return buckets.size(); }
}
/*
Value:节点值类型
Key:节点的键值类型
HashFcn:仿函数,计算hash值
ExtractKey:仿函数,提取key值
EqualKey:仿函数,判别键值是否相同的方法
Alloc:空间配置器
*/
node* new_node(const value_type& obj)
{
node* n = node_allocator::allocate();
n->next = ;
construct(&n->val, obj);
return n;
} void delete_node(node* n)
{
destroy(&n->val);
node_allocator::deallocate(n);
} hashtable(size_type n, const HashFcn hf, const EqualKey eql) : hash(hf), equals(eql), get_key(ExtractKey()), num_elements()
{
initialize_buckets(n);
} void initialize_buckets(size_type n)
{
const size_type n_buckets = next_size(n);
buckets.reserve(n_buckets);
buckets.insert(buckets.end(), n_buckets, (node*));
num_elements = ;
} size_type next_size(size_type n) const
{
// 有一个大小为28的数组,里面全部是质数。下面函数是取出数组里面最接近n的质数。
return __stl_next_prime(n);
}
// 插入数据不允许重复
pair<iterator, bool>insert_unique(const value_type& obj)
{
resize(num_elements + ); // 判断表格vector是否需要重建,如果需要就重建
return insert_unique_noresize(obj); // 插入键值,不允许重复
} void resize(size_type num_elements_hint)
{
const size_type old_n = buckets.size();
if(num_elements_hint > old_n)
{
vector<node*, Alloc> tmp(n, (node*));
for(size_type bucket = ; bucket < old_n; ++bucket)
{
node* first = buckets[bucket];
/* 感觉没有必要遍历链表,直接移动链表头结点就行 */
while(first)
{
size_type new_bucket = bkt_num(first->val, n);
buckets[bucket] = first->next;
first->next = tmp[new_bucket];
tmp[new_bucket] = first;
first = buckets[bucket];
}
}
buckets.swap(tmp);
}
} pair<iterator, bool> insert_unique_noresize(const value_type& obj)
{
const size_type n = bke_num(obj);
node* first = buckets[n]; for(node* cur = first; cur; cur = cur->next)
{
if(equals(get_key(cur->val), get_key(obj)))
{
return pair<iterator, bool>(iterator(cur, this), false);
}
} node* tmp = new_node(obj);
tmp->next = first;
bucklet[n] = tmp;
++num_elements;
return pair<iterator, bool>(iterator(tmp, this), true);
}
// 插入数据允许重复
iterator insert_equal(const value_type& obj)
{
resize(num_elements + ); // 判断表格vector是否需要重建,如果需要就重建
return insert_equal_noresize(obj); // 插入键值,允许重复
} iterator insert_equal_noresize(const value_type& obj)
{
const size_type n = bke_num(obj);
node* first = buckets[n]; for(node* cur = first; cur; cur = cur->next)
{
if(equals(get_key(cur->val), get_key(obj)))
{
// 如果有key相同的节点,将新节点插入在相同节点的后面
node* tmp = new_node(obj);
tmp->next = cur->next;
cur->next = tmp;
++num_elements;
return iterator(tmp, this);
}
} node* tmp = new_node(obj);
tmp->next = first;
bucklet[n] = tmp;
++num_elements;
return iterator(tmp, this);
}
// 映射函数
size_type bkt_num(const value_type& obj, size_t n) const
{
return bkt_num_key(get_key(obj), n);
} size_type bkt_num(const value_type& obj) const
{
return bkt_num_key(get_key(obj));
} size_type bkt_num_key(const key_type& key) const
{
return bkt_num_key(get_key(obj), buckets.size());
} // 最底层干活的函数
size_type bkt_num_key(const key_type& key, size_t n) const
{
// STL为所有的基础类型都定义了hash函数
return hash(key) % n;
}

hash_set

  • 使用方式和set完全相同

  • 底层使用hashtable

  • hashset中的元素只有key,没有value

  • hashset中不允许存在key相同的数据

hash_mutliset

  • 和hash_set完全相同,唯一的不同是允许存在相同key的数据

  • 底层使用hashtable

hash_map

  • 使用方式和map完全相同

  • 底层使用hashtable

  • map中不允许存在两个相同的元素

  • 存储的是键值对

hash_mutlimap

  • 和hash_map完全相同,唯一的不同是允许存在相同key 的数据

  • 底层使用hashtable

自定义的类作为map和hash_map的key需要注意的几点

  • 自定义类做map的key

    • 必须从重载operator<

  • 自定义类做hash_map的key

    • 提供equals()

    • 提供hashcode()

STL源码剖析:关联式容器的更多相关文章

  1. STL源码剖析——序列式容器#1 Vector

    在学完了Allocator.Iterator和Traits编程之后,我们终于可以进入STL的容器内部一探究竟了.STL的容器分为序列式容器和关联式容器,何为序列式容器呢?就是容器内的元素是可序的,但未 ...

  2. STL源码剖析——序列式容器#4 Stack & Queue

    Stack stack是一种先进后出(First In Last Out,FILO)的数据结构,它只有一个出口,元素的新增.删除.最顶端访问都在该出口进行,没有其他位置和方法可以存取stack的元素. ...

  3. STL源码剖析——序列式容器#2 List

    list就是链表的实现,链表是什么,我就不再解释了.list的好处就是每次插入或删除一个元素,都是常数的时空复杂度.但遍历或访问就需要O(n)的时间. List本身其实不难理解,难点在于某些功能函数的 ...

  4. STL源码剖析——序列式容器#5 heap

    准确来讲,heap并不属于STL容器,但它是其中一个容器priority queue必不可少的一部分.顾名思义,priority queue就是优先级队列,允许用户以任何次序将任何元素加入容器内,但取 ...

  5. STL源码剖析——序列式容器#3 Deque

    Deque是一种双向开口的连续线性空间.所谓的双向开口,就是能在头尾两端分别做元素的插入和删除,而且是在常数的时间内完成.虽然Vector也可以在首端进行元素的插入和删除(利用insert和erase ...

  6. STL源码剖析之序列式容器

    最近由于找工作需要,准备深入学习一下STL源码,我看的是侯捷所著的<STL源码剖析>.之所以看这本书主要是由于我过去曾经接触过一些台湾人,我一直觉得台湾人非常不错(这里不涉及任何政治,仅限 ...

  7. STL源码剖析 - RB-tree

    在我看来,看源码是一件既痛苦又兴奋的事.当我们在推敲其中的难点时,是及其痛苦的,但当发现实现代码是那么丝滑简洁时,“wc, nb!”. 1. 导语 如果我们去看关联式容器map.set.multima ...

  8. STL"源码"剖析-重点知识总结

    STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略多 :) 1.STL概述 STL提供六大组件,彼此可以组合 ...

  9. 【转载】STL"源码"剖析-重点知识总结

    原文:STL"源码"剖析-重点知识总结 STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点 ...

  10. STL源码剖析读书笔记之vector

    STL源码剖析读书笔记之vector 1.vector概述 vector是一种序列式容器,我的理解是vector就像数组.但是数组有一个很大的问题就是当我们分配 一个一定大小的数组的时候,起初也许我们 ...

随机推荐

  1. 搜索引擎ElasticSearch入门

    前言 最近项目上需要用到搜索引擎,由于之前自己没有了解过,所以整理了一下搜索引擎的相关概念知识. 正文 想查数据就免不了搜索,搜索就离不开搜索引擎,百度.谷歌都是一个非常庞大复杂的搜索引擎,他们几乎索 ...

  2. SpringMVC 学习笔记(六)拦截器

    5.1.处理器拦截器简介 Spring Web MVC的处理器拦截器(如无特殊说明,下文所说的拦截器即处理器拦截器) 类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理. ...

  3. 几个超级实用但很少人知道的 VS 技巧

    大家好,今天分享几个我知道的实用 VS 技巧,而这些技巧我发现很多人都不知道.因为我经常在工作中遇到:我在同事电脑上解决问题,或在会议上演示代码示例时,使用了一些 VS "骚"操作 ...

  4. Mybatis学习笔记(1)

    CRUD操作 1.从实体类参数中取值 #{属性名} select * from user where username = #{username} 2.当sql语句只有一个参数且参数类型是基本类型或基 ...

  5. 入门大数据---Scala学习

    Scala是什么? Scala是一种基于函数式编程和面向对象的高级语言.它开发了Spark等大型应用.它和Java有效集成,底层也是支持JVM的. 它有六大特性: 无缝JAVA互操作 Scala在JV ...

  6. vue全家桶(2.7)

    3.11.1.vue-router中的全局钩子函数 在vue-router中,路由发生变化,我们可以做一些事情,例如:可以决定是否进入导航,可以决定跳转到哪里,官方文档中又叫做导航守卫 首先来看一个全 ...

  7. 一个ioc例子jdk和spring版本导致问题

    今天橘子松在做一个简单例子的时候,出现bug让我久久找了半小时... 天啊 不会吧 错误如下:   java.lang.NoSuchMethodError: org.springframework.a ...

  8. css3动画添加间隔

    因项目需要,需要在元素上实现动画效果,并且需要有动画间隔.坑爹的是animation-delay只有在第一次动画开始的时候才起效. 在网上找了很多方法,最终的方法基本都是改动画规则,比如 @keyfr ...

  9. vs遇到的字符串问题

    原以为自己的字符串已经理解不错了, 今天又被vs搞了. 情景就不说了, 直接说结果: 有两种情况 1 当文件是存储为gbk或者utf-8的时候, 中文字符存储永远是gbk的值. ‘按’字的gbk编码 ...

  10. Jenkins Pipeline 部署 SpringBoot 应用

    一. 安装依赖包 yum install -y wget yum install -y gcc-c++ yum install -y zlib-devel perl-ExtUtils-MakeMake ...