1. 二叉树搜索树
众所周知,map和set特性是我们掌握C++必不可少的一个知识点,而二叉搜索树则是它最基础的铺垫,因此在之前我们数据结构的基础上,我们也是对其进行更深入的学习。
- 二叉搜索树基本概念
- 二叉搜索树可以是一棵空树;
- 它的左子树上所有节点的值,都是小(大)于根节点的值;
- 它的右子树上所有节点的值,都是大(小)于根节点的值。
- 二叉搜索树的遍历和查找 :接近二分的查找log(n)
二叉搜索树遍历的话是按中序遍历来走的,如果我们查找一个节点的话,从根节点开始查找。
- 左子树的最右节点是所有左子树中最大的节点 如4
- 右子树的最左节点是所有右子树中最小的节点 如6
- 二叉搜索树的插入:依然遵循左边全部小于根节点,右边全部大于根节点
因此,从根节点进行比较,使用递归的方式,以此进行下去
如果树中已经存在需要插入的数据的话,则不进行重复插入,一般情况下插入的位置都为叶子,子树不完全的非叶子节点,即度为0或者1的节点位置。
具体的插入代码和方式我放在二叉搜索树的实现代码之中一起展示出来。 - 二叉搜索树的删除(重点)
- 若查找元素不存在,则直接返回
- 若存在的话,进行删除也需要考虑多种情况,是否为叶子节点,是否存在左右孩子节点,所以我们需要在删除后依然保持它原本的结构。
- 二叉搜索树的实现
template <class T>
struct BSTNode
{
T _val;
BSTNode<T>* _left;
BSTNode<T>* _right;
BSTNode(const T& val = T())
:_val(val)
, _left(nullptr)
, _right(nullptr)
{}
};
template <class T>
class BSTree
{
public:
typedef BSTNode<T> Node;
Node* find(const T& val)//查找
{
Node* cur = _root;
while (cur)
{
if (cur->_val == val)
return cur;
else if (cur->_val < val)
cur = cur->_right;
else
cur = cur->_left;
}
return nullptr;
}
bool insert(const T& val)//插入
{
if (_root == nullptr)
{
_root = new Node(val);
return true;
}
Node* cur = _root;
Node* parent = nullptr;
//查找插入位置
while (cur)
{
parent = cur;
if (cur->_val == val)
return false;
else if (cur->_val < val)
cur = cur->_right;
else
cur = cur->_left;
}
cur = new Node(val);
//判断放在parent的哪一边
if (parent->_val < val)
parent->_right = cur;
else
parent->_left = cur;
return true;
}
bool erase(const T& val)//删除
{
//查找
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (cur->_val == val)
break;
else if (cur->_val < val)
{
parent = cur;
cur = cur->_right;
}
else
{
parent = cur;
cur = cur->_left;
}
}
//判断是否找到了需要删除的节点
if (cur == nullptr)
return false;
//删除
//1. 叶子
if (cur->_left == nullptr && cur->_right == nullptr)
{
if (cur == _root)
{
//如果是根节点,更新根节点
_root = nullptr;
}
else
{
if (parent->_left == cur)
parent->_left = nullptr;
else
parent->_right = nullptr;
}
delete cur;
}
else if (cur->_left == nullptr)
{
//2. 左孩子为空
if (cur == _root)
{
_root = cur->_right;
}
else
{
if (parent->_left == cur)
parent->_left = cur->_right;
else
parent->_right = cur->_right;
}
delete cur;
}
else if (cur->_right == nullptr)
{
//3. 右孩子为空
if (cur == _root)
{
_root = cur->_left;
}
else
{
if (parent->_left == cur)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
}
delete cur;
}
else
{
// 4.左右孩子都存在
// a. 找最左或者最右节点
// 找右子树的最左节点
Node* leftMostChild = cur->_right;
Node* parent = cur;
while (leftMostChild->_left)
{
parent = leftMostChild;
leftMostChild = leftMostChild->_left;
}
// b. 值替换
cur->_val = leftMostChild->_val;
// c. 删除最左或者最右节点
if (parent->_left == leftMostChild)
parent->_left = leftMostChild->_right;
else
parent->_right = leftMostChild->_right;
delete leftMostChild;
}
return true;
}
void inorder()
{
_inorder(_root);
cout << endl;
}
void _inorder(Node* root)
{
if (root)
{
_inorder(root->_left);
cout << root->_val << " ";
_inorder(root->_right);
}
}
void destory(Node* root)
{
if (root)
{
destory(root->_left);
destory(root->_right);
delete root;
}
}
~BSTree()
{
destory(_root);
}
void copyTree(Node* root)
{
if (root)
{
insert(root->_val);
copyTree(root->_left);
copyTree(root->_right);
}
}
Node* copyTree2(Node* root)
{
if (root)
{
Node* cur = new Node(root->_val);
cur->_left = copyTree2(root->_left);
cur->_right = copyTree2(root->_right);
return cur;
}
return nullptr;
}
BSTree()
:_root(nullptr)
{}
BSTree(const BSTree<T>& bst)
:_root(nullptr)
{
_root = copyTree2(bst._root);
}
BSTree<T>& operator=(BSTree<T> bst)
{
swap(_root, bst._root);
return *this;
}
private:
Node* _root = nullptr;
};
- KV键值对
每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方式在现实生活中非常常见:比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对。
比如:实现一个简单的英汉词典dict,可以通过英文找到与其对应的中文,具体实现方式如下:
<单词,中文含义>为键值对构造二叉搜索树,注意:二叉搜索树需要比较,键值对比较时只比较Key
查询英文单词时,只需给出英文单词,就可快速找到与其对应的key
template <class T1, class T2>
struct pair
{
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
pair(): first(T1()), second(T2())
{}
pair(const T1& a, const T2& b): first(a), second(b)
{}
};
2. 树形结构的关联式容器
序列式容器,vector,list,deque其底层为线性序列的数据结构,里面存储的元素本身,而关联式容器也是用来存储数据的,与序列式容器不同的是,其里面存储的是<key, value>结构的键值对,在数据检索时比序列式容器效率更高。其中主要的四种,map,set,multimap,multiset,他们使用红黑树作为其底层结构,容器中的元素是一个有序的序列。
1. map
- 构造
map<string, int> m;
vector<pair<string, int>> vec;
for (int i = 0; i < 10; i++)
{
pair<string, int> p(string("hello") + (char)(i + '0'), 5);
vec.push_back(p);
vec.push_back(pair<string, int>(string("77") + (char)(i + '0'), 10));
vec.push_back(make_pair(string("44") + (char)(i + '0'), 3));
}
map<string, int> m2(vec.begin(), vec.end());
map<string, int> copy(m2);
- 遍历
map<string, int>::iterator it = m2.begin();
while (it != m2.end())
{
cout << it->first << "-->" << it->second << endl;
//it->first = string("0000");
it->second = 100;
++it;
}
cout << "value changed" << endl;
pair<map<int, int>::iterator, bool> ret = m.insert(make_pair(3,3));
cout << ret.first->first << "--->" <<ret.first->second << endl;
- 接口函数
cout << m2.size() << endl;
ret = m.insert(make_pair(3, 10));
it = m.find(3);
cout << m.count(100) << endl;
2. set
- 构造
set<int> s;
int array[] = { 10, 3, 2, 8, 11 };
set<int, greater<int>> s2(array, array + 5);
set<int, greater<int>> copy(s2);
- 遍历
set<int, greater<int>>::iterator it = s2.begin();
//set元素不能修改
while (it != s2.end())
{
cout << *it << " ";
//*it = 100;
++it;
}
cout << endl;
- 接口函数
s2.insert(6);
s2.insert(s2.begin(), 15);
s2.insert(array2, array2 + 4);
s2.erase(10);
s2.erase(s2.begin());
set<int>::iterator it = s2.find(3);
cout << s2.count(3) << endl;
cout << s2.count(300) << endl;
3. multimap
- multimap的存储由<key,value>键值对组成,和map相比较,multimap中的key是可以重复的
- multimap中没有重载的operator[]操作
- 元素默认按照小于来比较
4. multiset
- multiset中在底层中存储的是<value,value>的键值对
- multiset中的元素可以重复,set中value是唯一的
- multiset中的元素可以删除插入,但不能进行修改
- 查找某个元素的时间复杂度为logN
- 其主要作用对元素进行排序
3. AVL树
- 它的左右子树都是AVL树
- 左右子树高度只差的绝对值不超过1(-1/0-1) 简称平衡因子
- 它有n个结点,其高度可保持在O(logn),搜索时间复杂度为O(logn)
- avl树结点的定义
template <class T>
struct AVLNode
{
T _value;
int _bf;
AVLNode<T>* _left;
AVLNode<T>* _right;
AVLNode<T>* _parent;
AVLNode(const T& val = T())
:_value(val)
, _bf(0)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
{}
};
- avl树的插入
其插入可以按照二叉搜索树的方式来进行插入,但是要在插入后调整节点的平衡因子
- 左边插入,父亲的平衡因子-1 ;
- 右边插入,父亲的平衡因子+1;
对于改变了的平衡因子,依然需要保证它的左右子树的高度不超过绝对值1,因此我们需要进行AVL树的旋转调整平衡因子(新插入节点中所有祖先节点需要更新或结点的子树高度发生变化则需要更新)
bool insert(const T& val)
{
//搜索树的插入
if (_root == nullptr)
{
_root = new Node(val);
return true;
}
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
parent = cur;
if (cur->_value == val)
return false;
else if (cur->_value < val)
cur = cur->_right;
else
cur = cur->_left;
}
cur = new Node(val);
if (parent->_value < val)
parent->_right = cur;
else
parent->_left = cur;
cur->_parent = parent;
//更新 + 调整
while (parent)
{
// 1. 更新parent平衡因子
if (parent->_left == cur)
--parent->_bf;
else
++parent->_bf;
//2. 判断是否需要继续更新
if (parent->_bf == 0)
break;
else if (parent->_bf == -1 || parent->_bf == 1)
{
//继续向上更新
cur = parent;
parent = parent->_parent;
}
else if (parent->_bf == -2 || parent->_bf == 2)
{
//调整
if (parent->_bf == -2 && cur->_bf == -1)
{
//左边的左边高,右旋
RotateR(parent);
cout << "insert: " << val << "右旋:" << parent->_value << endl;
}
else if (parent->_bf == 2 && cur->_bf == 1)
{
//右边的右边高, 左旋
RotateL(parent);
cout << "insert: " << val << "左旋:" << parent->_value << endl;
}
else if (parent->_bf == 2 && cur->_bf == -1)
{
cout << "insert: " << val << "右左双旋:" << parent->_value << " " << cur->_value << endl;
//右边的左边高: 右左双旋
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
RotateR(cur);
RotateL(parent);
//调整平衡因子
if (bf == 1)
{
//subRL右子树高
subR->_bf = 0;
parent->_bf = -1;
}
else if (bf == -1)
{
//subRL左子树高
parent->_bf = 0;
subR->_bf = 1;
}
}
else if (parent->_bf == -2 && cur->_bf == 1)
{
cout << "insert: " << val << "左右双旋:" << parent->_value << " " << cur->_value << endl;
//左边的右边高:左右双旋
RotateL(cur);
RotateR(parent);
}
break;
}
}
return true;
}
- 如果出现左边的左边高的情况,进行右单旋
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
subL->_right = parent;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
if (parent == _root)
{
_root = subL;
subL->_parent = nullptr;
}
else
{
Node* g = parent->_parent;
subL->_parent = g;
if (g->_left == parent)
g->_left = subL;
else
g->_right = subL;
}
parent->_parent = subL;
parent->_bf = subL->_bf = 0;
}
- 如果出制右边的右边高的情况,则进行左单旋
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
subR->_left = parent;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
if (parent == _root)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
Node* g = parent->_parent;
subR->_parent = g;
if (g->_left == parent)
g->_left = subR;
else
g->_right = subR;
}
parent->_parent = subR;
parent->_bf = subR->_bf = 0;
}
-
如果左子树的右边高,则先左单旋再右单旋
-
如果右子树的左边高,则先右单旋再左单旋
-
平衡因子的调整和修改
-
AVL树的插入总结
- 平衡因子树的检查
- 每个节点子树高度差的绝对值不超过1
- 节点的平衡因子是否计算正确
bool isBalance()
{
return _isBalance(_root);
}
bool _isBalance(Node* root)
{
if (root == nullptr)
return true;
//左右子树高度差是否和平衡因子相等
int subL = Height(root->_left);
int subR = Height(root->_right);
if (root->_bf != subR - subL)
{
cout << "节点:" << root->_value << "异常: bf: " << root->_bf << " 高度差:" << subR - subL << endl;
return false;
}
//平衡因子的绝对值知否小于2
return abs(root->_bf) < 2
&& _isBalance(root->_left)
&& _isBalance(root->_right);
}
int Height(Node* root)
{
if (root == nullptr)
return 0;
int left = Height(root->_left);
int right = Height(root->_right);
return left > right ? left + 1 : right + 1;
}
4. 红黑树
红黑树确保没有一条路径会比其他路径长出两倍!
- 红黑树的性质
- 根是黑色的
- 红色不能连续,黑色可以连续
- 每条路径上,黑色节点个数相同
- 最长路径是最短路径的两倍:最短,全黑;最长:红黑相间
- 红黑树节点的定义
enum Color
{
BLACK,
RED
};
template <class K, class V>
struct RBNode
{
pair<K, V> _value;//节点的值域
Color _color;//节点的颜色
RBNode<K, V>* _parent;
RBNode<K, V>* _left;
RBNode<K, V>* _right;
RBNode(const pair<K, V>& value = pair<K,V>())
:_value(value)
, _color(RED)//默认颜色为红色
, _parent(nullptr)
, _left(nullptr)
, _right(nullptr)
{}
};
- 红黑树结构
为了实现关联式容器的简单,红黑树实现中增加一个头结点,头结点的父亲指向红黑树的根节点,_left指向红黑树中最小的节点,_right指向红黑树最大的节点。
Node* leftMost()
{
Node* cur = _header->_parent;
while (cur && cur->_left)
cur = cur->_left;
return cur;
}
Node* rightMost()
{
Node* cur = _header->_parent;
while (cur && cur->_right)
cur = cur->_right;
return cur;
}
- 红黑树的插入
bool insert(const pair<K, V>& val)
{
//空树
if (_header->_parent == nullptr)
{
//创建第一个根节点
Node* root = new Node(val);
//根节点为黑色
root->_color = BLACK;
_header->_parent = root;
root->_parent = _header;
_header->_left = root;
_header->_right = root;
return true;
}
//非空树
Node* cur = _header->_parent;
Node* parent = nullptr;
while (cur)
{
parent = cur;
//按照key值进行比较: pair.first
if (cur->_value.first == val.first)
return false;
if (cur->_value.first < val.first)
cur = cur->_right;
else
cur = cur->_left;
}
cur = new Node(val);
if (parent->_value.first < val.first)
parent->_right = cur;
else
parent->_left = cur;
cur->_parent = parent;
//调整: 修改颜色, 旋转
while (cur != _header->_parent && cur->_parent->_color == RED)
{
Node* p = cur->_parent;
Node* g = p->_parent;
if (g->_left == p)
{
Node* u = g->_right;
if (u && u->_color == RED)
{
//修改颜色
u->_color = p->_color = BLACK;
g->_color = RED;
//继续向上查看
cur = g;
}
else
{
//u不存在/u存在且为黑
if (cur == p->_right)
{
RotateL(p);
swap(cur, p);
}
//cur在p的左边,右旋
RotateR(g);
//修改颜色
p->_color = BLACK;
g->_color = RED;
break;
}
}
else
{
Node* u = g->_left;
if (u && u->_color == RED)
{
//修改颜色
u->_color = p->_color = BLACK;
g->_color = RED;
//继续向上查看
cur = g;
}
else
{
//u不存在/u存在且为黑
if (cur == p->_left)
{
RotateR(p);
swap(cur, p);
}
//cur在p的左边,右旋
RotateL(g);
//修改颜色
p->_color = BLACK;
g->_color = RED;
break;
}
}
}
//根颜色置为黑色
_header->_parent->_color = BLACK;
//更新_header的左,有
_header->_left = leftMost();
_header->_right = rightMost();
return true;
}
- 插入后颜色调整
新节点的颜色默认红色,因此若双亲结点是黑色,则不违反红黑树任何性质,否则需要进行调整(约定cur 为当前节点,p为父节点,g为祖父节点,u为叔叔节点)
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
subL->_right = parent;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
if (parent == _header->_parent)
{
_header->_parent = subL;
subL->_parent = _header;
}
else
{
Node* g = parent->_parent;
subL->_parent = g;
if (g->_left == parent)
g->_left = subL;
else
g->_right = subL;
}
parent->_parent = subL;
}
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
subR->_left = parent;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
if (parent == _header->_parent)
{
_header->_parent = subR;
subR->_parent = _header;
}
else
{
Node* g = parent->_parent;
subR->_parent = g;
if (g->_left == parent)
g->_left = subR;
else
g->_right = subR;
}
parent->_parent = subR;
}
- 红黑树的验证
- 检测其是否满足二叉搜索树
- 检测其是否满足红黑树的性质
bool isRBtree()
{
Node* root = _header->_parent;
if (root == nullptr)
return true;
//1. 根是否为黑色
if (root->_color != BLACK)
return false;
//2. 每条路径黑色是否相同, 3. 红色是否连续
//获取某一条路径上黑色个数,作为基准值
int blackCount = 0;
Node* cur = root;
while (cur)
{
if (cur->_color == BLACK)
++blackCount;
cur = cur->_left;
}
int curCount = 0;
//遍历
return _isRBTree(root, blackCount, curCount);
}
bool _isRBTree(Node* root, int blackCount, int curCount)
{
//当前路径上黑色个数是否相同
if (root == nullptr)
{
if (blackCount != curCount)
return false;
return true;
}
//如果当前节点为黑色,累加
if (root->_color == BLACK)
++curCount;
Node* parent = root->_parent;
if (parent && parent->_color == RED && root->_color == RED)
{
//cout << "有红色连续的节点" << endl;
return false;
}
return _isRBTree(root->_left, blackCount, curCount)
&& _isRBTree(root->_right, blackCount, curCount);
}
与AVL树相比较的话,红黑树不过分的追求绝对的平衡,只需要保证最长路径不超过最短路径的2倍,降低了插入和旋转的次数,所以再经常进行增删的结构中性能比AVL树更优。
5. 红黑树来实现STL的map和set
- 红黑树的迭代器
想要用红黑树实现STL的map和set的话,就需要涉及到关于红黑树的迭代器的问题,它的好处是可以方便遍历,使数据结构的底层实现与用户的透明。(begin()可以放在红黑树中最小节点,end()放在最大节点的下一个位置,则头结点位置)
//通过封装节点,实现红黑树的迭代器操作
template <class V>
struct RBTIterator
{
typedef RBNode<V> Node;
typedef RBTIterator<V> Self;
Node* _node;
RBTIterator(Node* node)
:_node(node)
{}
//*,->,!=, ==, ++
V& operator*()
{
return _node->_value;
}
V* operator->()
{
return &_node->_value;
}
bool operator!=(const Self& it)
{
return _node != it._node;
}
bool operator==(const Self& it)
{
return _node == it._node;
}
//重点: 中序遍历
Self& operator++()
{
if (_node->_right)
{
//如果右子树存在,下一个位置:右子树的最左节点
_node = _node->_right;
while (_node->_left)
_node = _node->_left;
}
else
{
//如果没有右子树,需要向上回溯
Node* parent = _node->_parent;
while (_node == parent->_right)
{
_node = parent;
parent = parent->_parent;
}
//下一步需要访问的节点位置
if (_node->_right != parent)
_node = parent;
}
return *this;
}
};
- 改造红黑树
因为关联式容器中存储的是<可以,value>的键值对,因此ValueType如果是map,则为pair<K,V>,如果是set,则为k;KeyofValue,通过value来获取key的一个仿函数类
//KeyOfValue: 获取V所对应的K值
template <class K, class V, class KeyOfValue>
class RBTree
{
public:
typedef RBNode<V> Node;
typedef RBTIterator<V> iterator;
iterator begin()
{
//最左节点的位置
return iterator(_header->_left);
}
iterator end()
{
return iterator(_header);
}
RBTree()
:_header(new Node)
{
_header->_left = _header->_right = _header;
}
pair<iterator, bool> insert(const V& val);
- set的模拟实现
template <class K>
class Set
{
struct SetKeyOfValue
{
const K& operator()(const K& key)
{
return key;
}
};
public:
typedef typename RBTree<K, K, SetKeyOfValue>::iterator iterator;
pair<iterator, bool> insert(const K& key)
{
return _rbt.insert(key);
}
private:
RBTree<K, K, SetKeyOfValue> _rbt;
};
- map的模拟实现
#include "RBTree.hpp"
template <class K, class V>
class Map
{
struct MapKeyOfValue
{
const K& operator()(const pair<K, V>& value)
{
return value.first;
}
};
public:
typedef typename RBTree<K, pair<K, V>, MapKeyOfValue>::iterator iterator;
iterator begin()
{
return _rbt.begin();
}
iterator end()
{
return _rbt.end();
}
pair<iterator, bool> insert(const pair<K, V>& value)
{
return _rbt.insert(value);
}
V& operator[](const K& key)
{
pair<iterator, bool> ret = _rbt.insert(make_pair(key, V()));
return ret.first->second;
}
private:
RBTree<K, pair<K, V>, MapKeyOfValue> _rbt;
};