set和map
1、容器
1.1 序列式容器
vector、list、deque、forward_list和string 与vector等相关的容器都是序列式容器,其底层都是线性的数据结构,能够快速访问,存储元素本身。
1.2 关联式容器
与序列式容器不同,元素存储的位置由元素相关联的字值决定,因此在数据检索方面比序列式容器高。
2、键值对
用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表键值,value表示与key对应的信息
3、树形结构的关联式容器
3.1 树型结构的关联式
容器主要有四种:map、set、multimap、multiset。这四种容器的共同点是:使用平衡搜索树(即红黑树)作为其底层结果,容器中的元素是一个有序的序列。
3.2 set
- set是按照一定次序存储元素的容器;
- 在set中,元素的value也标识它(value就是key,类型为T),并且每个value必须是唯一的。set中的元素不能在容器中修改(元素总是const),但是可以从容器中插入或删除它们。
- 在内部,set中的元素总是按照其内部比较对象(类型比较)所指示的特定严格弱排序准则进行排序。set中的元素默认按照小于来比较
- set容器通过key访问单个元素的速度通常比unordered_set容器慢,但它们允许根据顺序对子集进行直接迭代。
- set在底层是用平衡搜索树(红黑树)实现的。
- 与map/multimap不同,map/multimap中存储的是真正的键值对<key, value>,set中只放value,但在底层实际存放的是由<value, value>构成的键值对。
- set中插入元素时,只需要插入value即可,不需要构造键值对。
- 使用set的迭代器遍历set中的元素,可以得到有序序列
- set中查找某个元素,时间复杂度为:log2(n)
3.3 map
- map中的的元素是键值对
- map中的key是唯一的,并且不能修改
- 默认按照小于的方式对key进行比较
- map中的元素如果用迭代器去遍历,可以得到一个有序的序列
- map的底层为平衡搜索树(红黑树),查找的时间复杂度为:log2(n)
- 支持[]操作符,operator[]中实际进行插入查找。
3.4 multiset
- multiset中再底层中存储的是<value, value>的键值对
- mtltiset的插入接口中只需要插入即可
- 与set的区别是,multiset中的元素可以重复,set是中value是唯一的
- 使用迭代器对multiset中的元素进行遍历,可以得到有序的序列
- multiset中的元素不能修改
- 在multiset中找某个元素,时间复杂度为log2(n)
- multiset的作用:可以对元素进行排序
3.5 multimap
- multimap中的key是可以重复的。
- multimap中的元素默认将key按照小于来比较
- multimap中没有重载operator[]操作,因为重复数据的查找不准确
- 使用时与map包含的头文件相同
4、AVL树
4.1概念
AVL是二叉搜索树,它的左右子树都是AVL树,且左右子树高度之差(简称平衡因子)的绝对值不超过1。
相比于普通的二叉搜索树,AVL树可以解决二叉搜索树有序或者接近有序数据的插入问题,提高效率,二叉搜索树的效率为N,而AVL树的效率为lon2(N)。
5、AVL树实现
5.1 结点
struct AVLTreeNode
{
AVLTreeNode(const T& data = T())
: _pLeft(nullptr)
, _pRight(nullptr)
, _pParent(nullptr)
, _data(data)
, _bf(0)
{}
AVLTreeNode<T>* _pLeft;//左孩子
AVLTreeNode<T>* _pRight;//右孩子
AVLTreeNode<T>* _pParent;//父亲
T _data;
int _bf; // 结点的平衡因子,用来控制平衡
};
5.2 AVL树的插入
共两步:1.按照二叉搜索树的插入方式;2.调整平衡因子,如果在较高子树中插入数据,会导致树的不平衡,此时需要进行旋转,总共有4种情况的旋转。
5.2.1右单旋
新结点插入较高左子树的左侧,如下图:
图2
图2
如图2中在5的左子树插入一个数据,或者图2的中的h为0时,即图1中插入1时,需要进行右单旋,如下图:
使parent的左指针指向leftChildright,leftChild作此树的根结点,且它的右指针指向parent,然后调整父亲结点和平衡因子即可完成右单旋。需要注意的是,当parent不为该树的根时,要调整parent的父亲结点的指向。旋转完成后,调整的结点平衡因子均为0。
5.2.2左单旋
与右单旋类似,新结点插入较高右子树的右侧,如下图:
使parent的右指针指向rightChildleft,rightChild作此树的根结点,且它的左指针指向parent,然后调整父亲结点和平衡因子即可完成左单旋。同样的,当parent不为该树的根时,要调整parent的父亲结点的指向。旋转完成后,调整的结点平衡因子均为0。
5.2.3左右单旋
新节点插入较高左子树的右侧,如下图:
10的左子树较高,然后在左子树的右子树上插入数据。情况1下的旋转如图:
旋转完成后,要根据3种情况调整平衡因子。
5.2.4右左单旋
新节点插入较高右子树的左侧,如下图:
10的右子树较高,然后在右子树的左子树上插入数据。情况1下的旋转如图:
旋转完成后,要根据3种情况调整平衡因子。
6、AVL树的验证
先验证是否为二叉搜索树,再验证每个结点的字数的高度差是否小于1。
1.二叉搜索树的检验可以用中序遍历来检验;
2.高度差得检验可以用以下代码
bool IsAVLTree(Node* pRoot)
{
if (nullptr == pRoot) return true;
// 计算pRoot左右子树的高度差
int leftHeight = _Height(pRoot->_pLeft);
int rightHeight = _Height(pRoot->_pRight);
int diff = rightHeight - leftHeight;
//如果差值不等于平衡因子或绝对值大于1,则平衡因子错误
if (diff != pRoot->_bf || abs(diff)>1)
return false;
// 如果pRoot子树都是AVL树,则该树一定是AVL树
return _IsAVLTree(pRoot->_pLeft) && _IsAVLTree(pRoot->_pRight);
}
//高度计算
int _Height(Node* pRoot)
{
if(pRoot==nullptr)return 0;
return max(_Height(pRoot->_pLeft)+1, _Height(pRoot->_pRight)+1);
}
7、代码
#pragma once
#include<iostream>
#include<assert.h>
#include<algorithm>
using namespace std;
template<class T>
struct AVLTreeNode
{
AVLTreeNode(const T& data = T())
: _pLeft(nullptr)
, _pRight(nullptr)
, _pParent(nullptr)
, _data(data)
, _bf(0)
{}
AVLTreeNode<T>* _pLeft;
AVLTreeNode<T>* _pRight;
AVLTreeNode<T>* _pParent;
T _data;
int _bf; // 结点的平衡因子
};
// AVL: 二叉搜索树 + 平衡因子的限制
template<class T>
class AVLTree
{
typedef AVLTreeNode<T> Node;
public:
AVLTree(): _pRoot(nullptr){}
// 在AVL树中插入值为data的结点
bool Insert(const T& data)
{
if (_pRoot == nullptr) {
_pRoot = new Node(data);
return true;
}
Node* parent = _pRoot, *cur = _pRoot;
//找到data的位置
while (cur) {
if (cur->_data < data) {
parent = cur;
cur = cur->_pRight;
}
else if (cur->_data > data) {
parent = cur;
cur = cur->_pLeft;
}
else
return false;
}
cur = new Node(data);
if (parent->_data>data)
parent->_pLeft = cur;
else
parent->_pRight =cur;
cur->_pParent = parent;
//更改平衡因子
while (cur != _pRoot){
if (parent->_pLeft == cur)
parent->_bf--;
else
parent->_bf++;
//相当于补全子树,两子树平衡,停止向上的更改
if (parent->_bf == 0)
break;
//继续向上更改
else if (parent->_bf == 1 || parent->_bf == -1) {
cur = parent;
parent = parent->_pParent;
}
//左或右子树不平衡,旋转子树
else if (parent->_bf == 2 || parent->_bf == -2) {
if (parent->_bf == -2) {
//右单旋,即在parent的较高左子树左侧插入
if(cur->_bf==-1)
RotateR(parent);
//左右单旋,即在parent的较高左子树右侧插入
if (cur->_bf == 1)
RotateLR(parent);
}
else {
//左单旋,即在parent的较高右子树右侧插入
if (cur->_bf == 1)
RotateL(parent);
//右左单旋,即在parent的较高右子树左侧插入
if (cur->_bf == -1)
RotateRL(parent);
}
break;
}
else
assert(false);
}
return true;
}
// AVL树的验证
bool IsAVLTree()
{
return _IsAVLTree(_pRoot);
}
void InOrder()
{
_InOrder(_pRoot);
}
private:
// 根据AVL树的概念验证pRoot是否为有效的AVL树
bool _IsAVLTree(Node* pRoot)
{
if (nullptr == pRoot) return true;
// 计算pRoot左右子树的高度差
int leftHeight = _Height(pRoot->_pLeft);
int rightHeight = _Height(pRoot->_pRight);
int diff = rightHeight - leftHeight;
//如果差值不等于平衡因子或绝对值大于1,则平衡因子错误
if (diff != pRoot->_bf || abs(diff)>1)
return false;
// 如果pRoot子树都是AVL树,则该树一定是AVL树
return _IsAVLTree(pRoot->_pLeft) && _IsAVLTree(pRoot->_pRight);
}
int _Height(Node* pRoot)
{
if(pRoot==nullptr)return 0;
return max(_Height(pRoot->_pLeft)+1, _Height(pRoot->_pRight)+1);
}
// 右单旋
void RotateR(Node* pParent)
{
Node* parentParent = pParent->_pParent;
Node* leftChild = pParent->_pLeft;
Node* leftChildRight = leftChild->_pRight;
//旋转,将父亲的左指向左孩子的右,左孩子的右指向父亲
pParent->_pLeft = leftChildRight;
leftChild->_pRight = pParent;
//更改结点的父亲
if (leftChildRight)
leftChildRight->_pParent = pParent;
pParent->_pParent = leftChild;
leftChild->_pParent = parentParent;
//若更改后子树的根结点的父亲为空,即为AVL树的根时,更新根,否则链接到父亲
if (pParent == _pRoot) {
_pRoot = leftChild;
leftChild->_pParent = nullptr;
}
else{
if (parentParent->_pLeft == pParent)
parentParent->_pLeft = leftChild;
else
parentParent->_pRight = leftChild;
}
//更改平衡因子
leftChild->_bf = pParent->_bf = 0;
}
// 左单旋
void RotateL(Node* pParent)
{
Node* parentParent = pParent->_pParent;
Node* rightChild = pParent->_pRight;
Node* rightChildLeft = rightChild->_pLeft;
//旋转,将父亲的右指向右孩子的左,右孩子的左指向父亲
pParent->_pRight = rightChildLeft;
rightChild->_pLeft = pParent;
//更改结点的父亲
if (rightChildLeft)
rightChildLeft->_pParent = pParent;
pParent->_pParent = rightChild;
rightChild->_pParent = parentParent;
//若更改后子树的根结点的父亲为空,即为AVL树的根时,更新根,否则链接到父亲
if (pParent == _pRoot) {
_pRoot = rightChild;
rightChild->_pParent = nullptr;
}
else {
if (parentParent->_pLeft == pParent)
parentParent->_pLeft = rightChild;
else
parentParent->_pRight = rightChild;
}
//更改平衡因子
rightChild->_bf = pParent->_bf = 0;
}
// 右左双旋
void RotateRL(Node* pParent)
{
Node* rightChild = pParent->_pRight;
Node* rightChildLeft = rightChild->_pLeft;
int bf = rightChildLeft->_bf;
RotateR(pParent->_pRight);
RotateL(pParent);
//更改平衡因子
if (bf == 1) {
rightChildLeft->_bf = rightChild->_bf = 0;
pParent->_bf = -1;
}
else if (bf == -1) {
pParent->_bf = rightChildLeft->_bf = 0;
rightChild->_bf = 1;
}
else if (bf == 0) {
pParent->_bf = rightChildLeft->_bf = rightChild->_bf = 0;
}
else
assert(false);
}
// 左右双旋
void RotateLR(Node* pParent)
{
Node* leftChild = pParent->_pLeft;
Node* leftChildRight = leftChild->_pRight;
int bf = leftChildRight->_bf;
RotateL(pParent->_pLeft);
RotateR(pParent);
//更改平衡因子
if (bf == 1) {
leftChildRight->_bf = pParent->_bf = 0;
leftChild->_bf = -1;
}
else if (bf == -1) {
leftChild->_bf = leftChildRight->_bf = 0;
pParent->_bf = 1;
}
else if (bf == 0) {
pParent->_bf = leftChildRight->_bf = leftChild->_bf = 0;
}
else
assert(false);
}
void _InOrder(Node*pRoot)
{
if (pRoot == nullptr)return;
_InOrder(pRoot->_pLeft);
cout << pRoot->_data << " ";
_InOrder(pRoot->_pRight);
}
private:
Node* _pRoot;
};
#include"AVLTree.h"
int main()
{
AVLTree<int> t;
//int arr[] = { 5,9,4,2,3,10,0,7,8,1,6 };
//int arr[] = { 5,4,3,2,1};//右单旋测试
//int arr[] = {1,2,3,4,5};//左单旋测试
//int arr[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
int arr[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
for (auto e : arr) {
t.Insert(e);
if (!t.IsAVLTree())
cout << "error" << endl;
}
t.InOrder();
return 0;
}