【知识讲解】 C++容器map+AVL树的介绍与使用


目录

前言

[Part1. map / multimap 核心接口](#Part1. map / multimap 核心接口)

[Part1.1. 基础接口](#Part1.1. 基础接口)

[Part1.2. map独有接口](#Part1.2. map独有接口)

[Part1.3. 接口insert() 的特殊返回值:pair<迭代器, bool>](#Part1.3. 接口insert() 的特殊返回值:pair<迭代器, bool>)

[Part1.4. 容器multimap 与map的不同之处](#Part1.4. 容器multimap 与map的不同之处)

[Part1.5. 容器map在算法题中的使用](#Part1.5. 容器map在算法题中的使用)

[Part2. AVL树介绍与使用](#Part2. AVL树介绍与使用)

[Part2.1. 平衡判定规则](#Part2.1. 平衡判定规则)

[Part2.2. AVL插入完整流程](#Part2.2. AVL插入完整流程)

[Part2.3. 旋转调整](#Part2.3. 旋转调整)

[Part3. 结语](#Part3. 结语)


前言

map作为C++很重要的容器,同时AVL树也是经典的数据结构,了解他们的构成是必要的,接下来,跟随小编的视角来看看这两个的介绍和基本的使用吧。


let's go!!!!!!!!!


Part1. map / multimap 核心接口

Part1.1. 基础接口

<1> erase(it/key):支持迭代器删除、按key批量删除

<2> swap()

<3> clear()

<4> find(key):返回目标键的迭代器,不存在返回 end() (multimap返回中序遍历第一个出现的)

<5> count(key):返回容器中该 key 存在的元素个数


Part1.2. map独有接口

<1> mapped_type& operator\[\] (const key_type& k);

<2> 功能:传入key,返回对应value引用。

<3> 关键特性:++若传入的key不存在,会自动插入该键,默认构造空value后返回。++

<4> 底层逻辑:

cpp 复制代码
pair<iterator, bool> ret = insert({k, mapped_type()});//通过insert插入 再通过它的返回值返回value
iterator it = ret.first;
return it->second;

Part1.3. 接口insert() 的特殊返回值:pair<迭代器, bool>

返回值含义:

<1> 插入成功(容器无重复key):返回新插入节点迭代器 + true

<2> 插入失败(key已存在):返回原有同key节点迭代器 + false
拓展用法:可通过返回的布尔值,实现类似find的查找判断逻辑。


Part1.4. 容器multimap 与map的不同之处

<1> 不支持 operator\[\]
同一key可存储多条value,下标无法确定返回哪一个value,直接禁用。

<2> 批量同key查找:equal_range(key)
返回 pair<iterator, iterator>:

1.++第一个迭代器:第一个大于等于key的节点(lower_bound)++

2. ++第二个迭代器:第一个大于key的节点(upper_bound)++
两个迭代器围成区间 [it1, it2),包含当前key全部元素,是multimap遍历同key数据的高效方案。


Part1.5. 容器map在算法题中的使用


cpp 复制代码
Node* copyRandomList(Node* head) {
//关键在于通过map建立新节点和原节点之间的映射关系
        if(head==nullptr)
        {
            return nullptr;
        }
        map<Node*,Node*> aaa;
        Node* _head=new Node(head->val);;
        Node* start=head;
        Node* _start=_head;
        while(start->next!=nullptr)
        {
           _start->next=new Node(start->next->val);
            start=start->next;
            _start=_start->next;
        }
        _start=_head;
        start=head;
        while(start!=nullptr)
        {
            aaa.insert({start,_start});
            start=start->next;
            _start=_start->next;
        }
        _start=_head;
        start=head;
        while(_start!=nullptr)
        {
            auto it=aaa.find(start->random);
            if(it!=aaa.end())
            _start->random=it->second;
            else
            _start->random=nullptr;
            _start=_start->next;
            start=start->next;
        }
        return _head;
    }

题目链接:138. 随机链表的复制 - 力扣(LeetCode)


cpp 复制代码
bool cmp(pair<string,int> a,pair<string,int> b)
    {
        if(a.second!=b.second)
        {
            return a.second>b.second;
        }
        else
        {
        return a.first<b.first;
        }

    }
class Solution {
public:
    //关键在于我们要用一个比较器来比较在出现次数相同的情况下 根据字典序来排序
    vector<string> topKFrequent(vector<string>& words, int k) {
        map<string,int> aaa;
        for(auto s:words)
        {
            aaa[s]++;
        }
        vector<pair<string,int>> bbb(aaa.begin(),aaa.end());
        sort(bbb.begin(),bbb.end(),cmp);/stable_sort(bbb.begin(),bbb.end(),kv());//因为上面这个map已经对字典序排序过一次了 这里用稳定排序就不用在用函数来单独处理出现次数相同的情况了
        vector<string> ans;
        for(int i=0;i<k;i++)
        {
            ans.push_back(bbb[i].first);
        }
        return ans;
    }
};

题目链接:692. 前K个高频单词 - 力扣(LeetCode)


Part2. AVL树介绍与使用

Part2.1. 平衡判定规则

<1> AVL树要求:任意节点左右子树高度差绝对值 ≤ 1

<2> 平衡因子公式:平衡因子 = 右子树高度 - 左子树高度

<3> 合法取值:-1、0、1

Part2.2. AVL插入完整流程

<1> 新增节点,完成二叉搜索树插入;

<2> 向上回溯更新路径上其父亲节点的平衡因子;

<3> 若节点平衡因子超出 -1,1,判定失衡,旋转调整(调整后不用向上调整了);

<4> 若节点平衡因子为1/-1,说明调整前是平衡的,现在不平衡了,高度改变,继续向上调整。

<5> 若节点平衡因子为0,说明平衡,高度不变,停止向上调整。

<6> 若遇到根节点,停止向上调整。


代码实现:

cpp 复制代码
bool insert(const std::pair<k, v>& kv)
{
	if (_root == nullptr)
	{
		_root(kv);
		return;
	}
	Node* cur = _root;
	Node* parent = nullptr;
	while (cur)
	{
		if (cur->_kv.first > kv.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else if(cur->_kv.first < kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else
		{
			return false;
		}
	}
	cur = new Node(kv);
	if (parent->_kv.first > kv.first)
	{
		parent->_left = cur;
	}
	else
	{
		parent->_right = cur;
	}
	cur->_parent = parent;
	while (parent)
	{
		if (cur == cur->_parent->_left)
		{
			cur->_parent->_bf--;
		}
		else
		{
			cur->_parent->_bf++;
		}
		if (cur->_parent->_bf == 0)
		{
			break;
		}
		else if (cur->_parent->_bf == 1 || cur->_parent->_bf == -1)
		{
			cur = cur->_parent;
		}
		else if(cur->_parent->_bf == 2 || cur->_parent->_bf == -2)
		{

			break;
		}
		else
		{
			assert(false);
		}
	}
	return true;
}

Part2.3. 旋转调整

失衡分4类,通过单次/双旋转降低子树高度,重新满足AVL平衡规则:

<1> 右单旋 <2> 左单旋 <3> 左右双旋 <4> 右左双旋


右单旋抽象图解:

(左单旋与之相似)


具体的,当h=2 时共有 3×3×4=36 种子树组合,h=3 时组合数达6120种,但我们只要掌握抽象图就好。


Part3. 结语

上面我们介绍了map和AVL的基本情况,接下来小编还会有更多文章关于这个的,也请敬请期待~
最后,祝大家可以:春风得意马蹄疾,一日看尽长安花!

最后的最后,要是觉得本文还可以的话,可以点点赞,关注小编一波,谢谢大家!~