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

  1. set是按照一定次序存储元素的容器;
  2. 在set中,元素的value也标识它(value就是key,类型为T),并且每个value必须是唯一的。set中的元素不能在容器中修改(元素总是const),但是可以从容器中插入或删除它们。
  3. 在内部,set中的元素总是按照其内部比较对象(类型比较)所指示的特定严格弱排序准则进行排序。set中的元素默认按照小于来比较
  4. set容器通过key访问单个元素的速度通常比unordered_set容器慢,但它们允许根据顺序对子集进行直接迭代。
  5. set在底层是用平衡搜索树(红黑树)实现的。
  6. 与map/multimap不同,map/multimap中存储的是真正的键值对<key, value>,set中只放value,但在底层实际存放的是由<value, value>构成的键值对。
  7. set中插入元素时,只需要插入value即可,不需要构造键值对。
  8. 使用set的迭代器遍历set中的元素,可以得到有序序列
  9. set中查找某个元素,时间复杂度为:log2(n)

3.3 map

  1. map中的的元素是键值对
  2. map中的key是唯一的,并且不能修改
  3. 默认按照小于的方式对key进行比较
  4. map中的元素如果用迭代器去遍历,可以得到一个有序的序列
  5. map的底层为平衡搜索树(红黑树),查找的时间复杂度为:log2(n)
  6. 支持[]操作符,operator[]中实际进行插入查找。

3.4 multiset

  1. multiset中再底层中存储的是<value, value>的键值对
  2. mtltiset的插入接口中只需要插入即可
  3. 与set的区别是,multiset中的元素可以重复,set是中value是唯一的
  4. 使用迭代器对multiset中的元素进行遍历,可以得到有序的序列
  5. multiset中的元素不能修改
  6. 在multiset中找某个元素,时间复杂度为log2(n)
  7. multiset的作用:可以对元素进行排序

3.5 multimap

  1. multimap中的key是可以重复的。
  2. multimap中的元素默认将key按照小于来比较
  3. multimap中没有重载operator[]操作,因为重复数据的查找不准确
  4. 使用时与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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值