底层实现map和set的第一步,AVL树的学习

首先补充一些map的知识与两道与map相关的编程题

1.map中的[]重载

用途:用于快速查找,查找一个key,返回一个他的value。

代码:V和K分别是map中的value和key模板参数。

cpp 复制代码
V& operator[](const K& key)
{
//库中的insert本质会返回一个pair类型,用途是返回对应位置的迭代器和是否插入成功
	pair<iterator,bool> ret = insert({key,V()});
	iterator it = ret.first;
	return it->second;
}

值得注意的是insert的参数是一个pair(是该结点的数据),返回的也是以个pair(是该节点的迭代器和是否插入成功的bool值)

可以发现map中的[]重载有两个功能:

1.插入和修改数据:

当key是树中没有的数据时,就会在树中创建新的结点,然后引用就可以修改其的value数据。

2.修改数据:

当key是树中有的数据时,就会返回树中该节点的迭代器,然后引用就可以修改其的value数据。

从这点看能发现multimap的insert函数调用一定是成功,但是不支持[]重载因为不确定返回哪一个相同节点的迭代器。

题1.随机链表的复制

用map来分别存新旧来链表的结点,利用这种映射关系即可

cpp 复制代码
class Solution {
public:
    Node* copyRandomList(Node* head)
    {
        if(head == nullptr)return nullptr;
       map<Node*,Node*> m;
       Node* cur = head;
       Node* phead,*ptail = nullptr;
       while(cur)
       {
        if(cur == head)
        {
            phead = ptail = new Node(cur->val);
//记得记录二者的地址
            m[cur] = ptail;
            cur = cur->next;
        }
        else
        {
        Node* newnode = new Node(cur->val);
        ptail->next = newnode;
        ptail = newnode;
        m[cur] = ptail;
        cur = cur->next;
        }
       }
       cur = head;
       Node*pcur = phead;
       while(cur)
       {
        if(cur->random == nullptr)
{
pcur->random = nullptr;
}
else 
{
//映射找到对应地址,m中[]后存的是新链表的结点
pcur->random = m[cur->random];
}
cur = cur->next;
pcur = pcur->next;
       }
       return phead;
    }
};

2.前K个高频词

解法:pair的比较方式为先比较first的大小,相同时再比较second的大小。

因此单纯的pair比较无法稳定相同first元素的相对顺序。

于是我们先用map排出字典序,再用stable_sort保持相同元素的相对顺序的同时排secodn的大小即刻。(或通过自行设计仿函数给sort改变比较逻辑来保持相同元素的相对顺序)

cpp 复制代码
class Solution {
public:
struct com
{
    bool operator()(pair<string,int> a,pair<string,int> b)
    {
        return a.second > b.second;
        //用sort时就改为(a.second > b.second)||((a.second == b.second)&&(a.first > b.first));
    }
};
    vector<string> topKFrequent(vector<string>& words, int k)
    {
     map<string,int> m;
     for(auto& e:words)
     {
        m[e]++;
     }   
     vector<pair<string,int>> v(m.begin(),m.end());
     stable_sort(v.begin(),v.end(),com());
     vector<string>vv;
    for(int a =0;a < k;a++)
    {
        vv.push_back(v[a].first);
    }
     return vv;
    }
};

下面正式开始AVL树的说明

1.定义

该树的所有树的左右子树的高度差的绝对值不能超过1.

每个结点都有一个平衡因子,为该结点的右子树-左子树的高度差的绝对值,范围为[-1,1]。

大致框架:(以map为例子,原因为set的本质也与map一样,甚至可以说set是给map做牺牲的)

cpp 复制代码
#include<iostream>
using namespace std;
template <class K, class V>
struct AVLTreeNode
{
	pair<K, V> _val;
//这里用的是三叉链,用于找父亲的
	AVLTreeNode* _left;
	AVLTreeNode* _right;
	AVLTreeNode* _parent;
//_bf就是平衡因子
	int _bf;
	AVLTreeNode(const pair<K, V>& val)
		:_val(val)
		,_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_bf(0)
	{ }
};
template <class K,class V>
class AVLTree
{
public:
	typedef AVLTreeNode<K, V> Node;
private:
	Node* _root = nullptr;
};

2.insert

insert与平衡二叉树的差别就在于对平衡因子进行了要求,此外可以说没有其他区别了(除了要多更新一个父节点)

一个结点的平衡因子的更新要求

1.结点的平衡因子=右子树高度-左子树高度。

2.对于一个结点而言,平衡因子的更新由左右子树的高度是否发生变化所决定。

3.一个结点的右子树高变高1格时,该结点平衡因子++,左子树高变高1格时,该结点平衡因子--。

4.该结点的父亲变化后的平衡因子值决定了是否向上更新其父亲的父亲(也就是爷爷)结点的平衡因子的值。

更新的中止条件

1.更新后该节点的父亲结点的平衡因子为0就不必再向上更新。

2.更新后该节点的父亲结点的平衡因子为1或-1,此时就向上更新。

3.更新后该节点的父亲结点的平衡因子为2或-2,此时就要对父亲进行旋转,旋转完后就不用再更新了。

insert的大框:

复制代码
bool insert(const pair<K,V>& val)
{
	if (_root == nullptr)
	{
		
		_root = new Node(val);
		
		return true;
	}
	Node* cur = _root;
	Node* parent  = nullptr;
	while (cur)
	{
		if (cur->_val.first < val.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (cur->_val.first > val.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			return false;
		}
	}
	//此处走到空结点
	cur = new Node(val);
	//连接parent和cur
	if (parent->_val.first < val.first)
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}
	cur->_parent = parent;
	//下面开始更新平衡因子
	while (parent)
	{
		if (cur == parent->_left)
			parent->_bf--;
		else 
			parent->_bf++;
		if (parent->_bf == 0)
			break;
		else if (parent->_bf == 1 || parent->_bf == -1)
		{
			cur = parent;
			parent = parent->_parent;
		}
		else
		{
			if (parent->_bf == 2 && cur->_bf == 1)
			{
				rotateL(parent);
				
			}
			else if (parent->_bf == -2 && cur->_bf == -1)
			{
				rotateR(parent);
				
			}
			else if (parent->_bf == -2 && cur->_bf == 1)
			{
				rotateLR(parent);
				
			}
			else if (parent->_bf == 2 && cur->_bf == -1)
			{
				rotateRL(parent);
				
			}
			else assert(false);
//在旋转完之后一定要break,因为此时的树已经平衡了,但不代表parent走到结尾了
			break;
		}
	}
	return true;
}

3.旋转

目的:

1.保持搜索树的规则

2.使树变平衡

3.恢复整棵树的高度

种类:右单旋,左单旋,右左双旋,左右双旋。

1.右单旋(左子树太高了)

上图中parent的左子树太高是parent的_bf == -2,同时subl的_bf == -1,二者同号,此时就要有右单旋。

解释上图:

将subl的sublR给parent的左子树然后parent做subl的右子树。更新后parent和subl的平衡因子就都为0了。

代码实现:

cpp 复制代码
void rotateR(Node* parent)
{
	Node* subl = parent->_left;
	Node* sublR = subl->_right;
	Node* grandparent = parent->_parent;
	//下面两调为核心代码
	subl->_right = parent;
	parent->_left = sublR;
	//sublR可能为空
	if (sublR)
		sublR->_parent = parent;
	parent->_parent = subl;

	//下面的代码是为了将grandparent与subl连接起来
	// 
	//parent为root时,grandparent为nullptr
	//此时grandparent就无法解引用找到subl了
	if (parent == _root)
	{
		_root = subl;
		subl->_parent = nullptr;
	}
	else
	{
		if (grandparent->_left == parent)
			grandparent->_left = subl;
		else 
			grandparent->_right = subl;
	}
	//直接在这里更新就不用分类讨论了
	subl->_bf = parent->_bf = 0;
}

2.左单旋(右子树太高了)

与右单旋差不多

subR->left = parent,parent->right = subRL.

代码:

cpp 复制代码
	void rotateL(Node* parent)
	{
//就是换了一个方向
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		Node* grandparent = parent->_parent;
		subR->_left = parent;
		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;
		parent->_parent = subR;
		if (parent == _root)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (grandparent->_right == parent)
				grandparent->_right = subR;
			else
				grandparent->_left = subR;
		}
		subR->_bf = parent->_bf = 0;
	}

3.左右双旋

如上图:当parent为-2且cur为1时,符合左右双旋的情况。

做法:

如上图:先以subl为parent进行单左旋,再以parent为parent(旋转函数中的参数名)进行单右旋即可。

然而在平衡因子的更新中会出现几种情况:

1.新节点在sublR的左子树中

2.新节点在sublR的右子树中

3.新节点就是sublR

可以发现:sublR的平衡因子会导致subl和parent的平衡因子不同,根据sublR的平衡因子情况进行更新即可。

代码:

cpp 复制代码
void rotateLR(Node* parent)
{
	Node* subl = parent->_left;
	Node* sublR = parent->_right;
	//提前备份一个,因为在单旋中sublf的bf会被更新.
	int bf = sublR->_bf;
	rotateL(subl);
	rotateR(parent);
	if (bf == -1)
	{
		subl = sublR = 0;
		parent = 1;
	}
	else if (bf == 1)
	{
		subl = -1;
			sublR = 0;
		parent = 0;
	}
	else if (bf == 0)
	{
		subl = sublR = 0;
		parent = 0;
	}
	else 
	{
		//可能树出问题了
		assert(false);
	}
}

4.右左双旋也类似

cpp 复制代码
void rotateRL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	//提前备份一个,因为在单旋中sublf的bf会被更新.
	int _bf = subRL->_bf;
	rotateR(subR);
	rotateL(parent);
	if (_bf == -1)
	{
		subRL = 0;
		subR = 1;
		parent = 0;
	}
	else if (_bf == 1)
	{
		parent = -1;
		subR = subRL = 0;
	}
	else if (_bf == 0)
	{
		subR = subRL = 0;
		parent = 0;
	}
	else
	{
		//可能树出问题了
		assert(false);
	}
}

最后补充一下insert的逻辑

cpp 复制代码
if (parent->_bf == 2 && cur->_bf == 1)
				rotateL(parent);
else if (parent->_bf == -2 && cur->_bf == -1)
				rotateR(parent);
else if (parent->_bf == -2 && cur->_bf == 1)
				rotateLR(parent);
else if (parent->_bf == 2 && cur->_bf == -1)
				rotateRL(parent);
else assert(false);

最后来一首判断是否为AVL树的代码

cpp 复制代码
//依旧是封装流打法	
int height()
	{
		return _height(_root);
	}
	int size()
	{
		return _size(_root);
	}
	bool isbalacetree()
	{
		return _isbalacetree(_root);
	}
    Node* find(const V& value)
   {
	Node* cur = _root;
	while (cur)
	{
		if (cur->_val.second > value) cur = cur->_right;
		else if (cur->_val.second < value) cur = cur->_left;
		else return cur;
	}
	return nullptr;
   }
private:
	bool _isbalacetree(Node* _root)
	{
		if (_root == nullptr)return true;
		int left = _height(_root->_left);
		int right = _height(_root->_right);
//高度查>=2就是错
		if (abs(left - right) >= 2)return false;
//_bf>=2就是错
		if (_root->_bf >= 2)return false;
		return _isbalacetree(_root->_left) && _isbalacetree(_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;
	}
	int _size(Node* _root)
	{
		if (_root == nullptr)return 0;
		return _size(_root->_left) + _size(_root->_right) + 1;
	}
相关推荐
垫脚摸太阳2 小时前
第 36 场 蓝桥·算法挑战赛·百校联赛---赛后复盘
数据结构·c++·算法
Aaswk3 小时前
刷题笔记(回溯算法)
数据结构·c++·笔记·算法·leetcode·深度优先·剪枝
zhooyu3 小时前
GLM中lerp实现线性插值
c++·opengl
我不是懒洋洋3 小时前
预处理详解
c语言·开发语言·c++·windows·microsoft·青少年编程·visual studio
计算机安禾3 小时前
【数据结构与算法】第14篇:队列(一):循环队列(顺序存储
c语言·开发语言·数据结构·c++·算法·visual studio
IT从业者张某某3 小时前
基于EGE19.01完成恐龙跳跃游戏-V00-C++使用EGE19.01这个轮子
c++·游戏
-许平安-4 小时前
MCP项目笔记六(PluginsLoader)
c++·笔记·raii·plugin system
呜喵王阿尔萨斯4 小时前
argc & argv
c语言·c++
Vect__4 小时前
std::bind和lambda的使用
c++