c++进阶----map和set

目录

1、关联式容器

2、键值对

3、树形结构的关联式容器

3.1、set

3.2、map

​编辑

4、底层结构

4.1、AVL树


1、关联式容器

在初阶我们接触过的STL部分容器,比如:vector,list,deque,这些都是序列式容器。

关联式容器里面存的是<key,value>结构式的键值对,在数据检索时比序列式容器效率更高。

map是key/value      set是key

2、键值对

具有一一对应关系的一种结构。key代表键值,value代表与key对应的信息。

3、树形结构的关联式容器

3.1、set

set<int> s;
s.insert(3);
s.insert(2);
s.insert(7);
s.insert(9);
s.insert(3);
s.insert(7);
s.insert(6);

set<int>::iterator it = s.begin();
while(it != s.end())
{
    cout << *it << " ";
    ++it;
}
//运行结果是2 3 6 7 9  所以它的迭代器是排序加去重

//删除
s.erase(3);//直接删

auto pos = s.find(3);  //找到位置删  
//这个find是比他大往右走 比他小往左走 O(logN)
if(pos != s.end())
   s.erase(pos);  
//还有一种find
auto pos = find(s.begin(),s.end(),3); //暴力查找 要把整棵树全都走一遍O(N)

set的迭代器是排序加去重。去重的原理是:一个值已经有了就不插入

//排序 不去重
multiset<int> s;
s.insert(3);
s.insert(5);
s.insert(8);
s.insert(7);
s.insert(7);
s.insert(9);
for(auto e : s)
{
    cout << e << " ";
}
//3 5 7 7 8 9
auto ret = s.equal_rang(7);//删掉所有的7
auto itlow = ret.first;
auto itup = ret.secnd;

//[itlow,itup)
cout << *itlow <<endl; //7
cout << *itup << endl; //8

s.erase(itlow, itup);
for(auto e : s)
{
    cout << e << " ";
}

auto pos = s.find(7);
//返回的是中序的第一个7

3.2、map

map<string, string> dict;
pair<string,string> kv1("insert", "插入");   //方式1
dict.insert(kv1); 
dict.insert(pair<string,string>("sort","排序")); //方式2
dict.insert(make_pair("string","字符串")); //方式3
//c++11支持多参数隐式类型转换
dict.insert({"string", "字符串"});

//建议使用
map<string, string> dict;
dict.insert(make_pair("string","字符串"));
dict.insert(make_pair("sort","排序"));
dict.insert(make_pair("insert","插入"));
//插入过程中,只比较key,value是否相同无所谓
std::map<std::string,std::string>::iterator it = dict.begin();
//auto it = dict.begin();
while(it != dict.end())
{
    cout << (*it).first << ":" << (*it).second << endl;
    cout << it->first << ":" << it->second << endl; //建议使用箭头
    ++it;
}
cout << endl;
//范围for
for(const auto& kv : dict)
{
    cout << kv.first << ":" << kv.second << endl;
}
//统计次数
string arr[] = {"西瓜","苹果","西瓜","香蕉", "苹果", "西瓜", "香蕉", "西瓜", "苹果"};
map<string, int> countMap;
for(auto e : arr)
{
    auto it = countMap.find(e);
    if(it == countMap.end())
    {
        countMap.insert(make_pair(e,1));
    }
    else
    {
        it->second++;
    }
}
for(const auto& kv : countMap)
{
    cout << kv.first << ":" << kv.second << endl;
}

/法二:
for(auto e : arr)
{
    countMap[e]++;
//因为如果键不存在就会插入  countMap[e] 映射到的就是每个对应的值 
//类型为int +1就是给值键对应的值+1 
}
for(const auto& kv : countMap)
{
    cout << kv.first << ":" << kv.second << endl;
}

std::map::operator[]

通过key返回value

当使用[]访问不存在的键的时候

std::cout << "Cherry: " << myMap["cherry"] << std::endl; // 输出: Cherry: 0

就会使这个键存在

使用 operator[] 访问不存在的键会导致默认值的插入,可能会影响性能。如果你只想检查键是否存在,应该使用 find 方法或 count 方法。

map<string, string> dict;
dict.insert(make_pair("string","字符串"));
dict.insert(make_pair("sort","排序"));
dict.insert(make_pair("insert","插入"));

[]的功能///

cout << dict["sort"] << endl;   //查找和读
dict["map"];                    //插入
dict["map"] = "映射,地图”;      //修改
dict["insert"] = "***";        //修改
dict["set"] = "集合";          //插入+修改

class Solution 
{
public:
    Node* copyRandomList(Node* head) 
    {
        //创建map 存原节点与对应节点之间的映射关系 键是原节点 值是复制节点
        map<Node*, Node*> nodeCopyMap;
        //初始化两个指针 分别指向复制链表的头和尾
        Node* copyhead = nullptr, *copytail = nullptr;

        //复制链表
        Node* cur = head;//创建一个指针指向原链表的头节点
        while(cur) //直到cur指向空截止
        {
            Node* copynode = new Node(cur->val); //每执行一次循环,都会创建一个新节点,
                                                //新节点的值是cur指针指向的节点的值
            if(copytail == nullptr)//检查是否为空
            {
                copyhead = copytail = copynode;//为空将copyhead和copytail都指向copynode
            }
            else
            {
                copytail->next = copynode;//
                copytail = copytail->next;//更新copytial
            }

            nodeCopyMap[cur] = copynode;//将原节点cur和其对应的copynode存到nodeCopyMap中
            cur = cur->next;
        }

        //控制random
        cur = head;//将cur重新指向原链表的头节点
        Node* copy = copyhead;
        while(cur)
        {
            if(cur->random == nullptr)
                copy->random = nullptr;
            else
                copy->random = nodeCopyMap[cur->random];//原链表的random通过map找到复制节点的random
            cur = cur->next;
            copy = copy->next;
        }

        return copyhead;

    }
};

map <string,int> Mymap;

Mymap["apple"] = 5;

cout << "Apple" << ":" << Mymap["apple"] << endl;

//Apple: 5

两组数找交集、差集

交集:1、首先将两组值放到set中进行有序化

1 3 5 6 7  ptr1    两个指针都指向第一个数   两个数进行比较 小的++ ;相等就是交集 同时++

3 4 6 9  ptr2

并集:1、放进set进行有序化

同上两个指针,小的就是差集,小的++ ;相等同时++

首先,分析题目, 前k个 出现次数由高到低排序 如果出现次数相同 按照字典顺序排序

需要注意的几点:1、map[]++统计完次数,是按照字典顺序输出的

2、sort:可以对数组或其他容器进行排序

#include <algorithm>

void sort(RandomIt first, RandomIt last);

void sort(RandomIt first, RandomIt last, Compare comp);

  • first 和 last:指定排序范围的迭代器。
  • comp:可选的比较函数,定义元素的排序顺序

默认是升序。sort是不稳定的,对于两个出现次数相同的原素可能会交换位置。

  • 影响:在某些情况下,保持相等元素的相对顺序很重要,比如在对对象排序时,可能需要根据其他属性进行排序。

stable_sort:  stable_sort(RandomIt first, RandomIt last);

  • first:指向要排序的范围的起始迭代器。
  • last:指向范围的结束迭代器(不包含该位置的元素)。

stable_sort(RandomIt first, RandomIt last, Compare comp);

  • comp:一个可选的比较函数,定义了两个元素的排序顺序。应返回 true 如果第一个元素应在第二个元素之前。

默认升序。stable_sort是稳定的,对于两个出现次数相同的元素按照原容器的相对位置进行排序。

class Solution 
{
public:
    struct Greater//使用operator()使c++的类可以像函数一样被调用
    {
        bool operator()(const pair<string, int>&kv1, const pair<string, int>& kv2)
        {
            return kv1.second > kv2.second;
        }
    };
    vector<string> topKFrequent(vector<string>& words, int k)
    {
        map<string, int> countMap;
        for(const auto& e : words)
        {
            countMap[e]++; //统计出现次数
        }

        //将键值转化为数组,以便后面进行排序
        vector<pair<string,int>> kvVec(countMap.begin(), countMap.end());
        //排序 按照Greater的方式 (频率降序)因为map统计完次数是按照字典顺序排序的 使用stable_sort排序完 如果次数相同还是按照原排序的顺序排序
        stable_sort(kvVec.begin(),kvVec.end(),Greater());

        vector<string> ret;
        for(int i = 0; i < k; ++i)
        {
            ret.push_back(kvVec[i].first);
        }
        return ret;
    }
};

4、底层结构

4.1、AVL树

1、AVL树的概念

二叉搜索树虽可以缩短查找的效率,但 如果数据有序或接近有序二叉搜索树将退化为单支树,查
找元素相当于在顺序表中搜索元素,效率低下 。因此,两位俄罗斯的数学家 G.M.Adelson-Velskii
E.M.Landis 1962
发明了一种解决上述问题的方法: 当向二叉搜索树中插入新结点后,如果能保证每个结点的左右
子树高度之差的绝对值不超过 1( 需要对树中的结点进行调整 ) ,即可降低树的高度,从而减少平均
搜索长度。
一棵 AVL 树或者是空树,或者是具有以下性质的二叉搜索树:
它的左右子树都是 AVL
左右子树高度之差 ( 简称平衡因子 ) 的绝对值不超过 1(-1/0/1) =右子树的高度-左子树的高度
1、新增在左,parent平衡因子减减
2、新增在右,parent平衡因子加加
更新后parent平衡因子 ==0,说明parent所在的子树高度不变,不会再影响祖先,不用再继续沿着root的路径往上更新------(插入结束)
更新后parent平衡因子 ==1 or -1,说明parent所在的子树高度变化,会影响祖先,需要沿着root的路径往上更新---------(继续执行1、2)
更新后parent平衡因子 ==2 or -2,说明parent所在的子树高度变化且不平衡,对parent所在子树进行旋转,让它平衡  -----(插入结束)
更到根节点 -----(插入结束)
1. 新节点插入较高左子树的左侧 --- 左左:右单旋
旋转的时候需要注意的问题:
1、保证它还是搜索树
2、变成平衡树且降低这个子树的高度
void Rotatel(Node* parent)
{
	Node* cur = parent->_right;
	Node* curleft = cur->_left;

	parent->_right = curleft;//将parent旋转下来 这样仍然满足搜索树
	if (curleft)//cur的左不为空时
	{
		curleft->_parent = parent;//它的父亲就是parent
	}
	cur->_left = parent;  //将parent变为cur的左

	Node* ppnode = parent->_parent;  //旋转的这部分可能是一整棵也有可能是树的一部分
	parent->_parent = cur;

	if (parent == _root) //parent是根
	{
		_root = cur;    //那么现在cur是根
		cur->_parent = nullptr;
	}
	else                 //parent不是根  将ppnode的孩子变为cur
	{
		if (ppnode->_left == parent)  //parent在根的左边
		{
			ppnode->_left = cur;
		}
		else                         //parent在根的右边
		{
			ppnode->_right = cur;
		}
		cur->_parent = ppnode;      //cur的父亲是ppnode
	}
	parent->_bf = cur->_bf = 0; //因为平衡因子的绝对值不会超过2 所以进行反转时候会变为0
}

2. 新节点插入较高右子树的右侧 --- 右右:左单旋
//面试一般不会手撕//
using namespace std;

template<class K, class V>
struct AVLTreeNode
{
	pair<K, V> _kv;
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;
	int _bf;

	AVLTreeNode(const pair<K, V>& kv)
		:_kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		,_parent(nullptr)
		,_bf(0)
	{}
};
template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}
		cur = new Node(kv);
		if (parent->_kv.first < kv.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 || parent->_bf == -2)
			{
				//子树不平衡了。需要旋转
				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)
				{
					RotateRL(parent);
				}
				else if(parent->_bf == -2 && cur->_bf == 1)
				{
					RotateLR(parent);
				}

				break;
				
			}
			else 
			{
				assert(flase);//加断言当代码出错时就可以知道这里出现问题
			}
		}

	}
//
	//左单旋///

	void RotateL(Node* parent)
	{
		Node* cur = parent->_right;
		Node* curleft = cur->_left;

		parent->_right = curleft;//将parent旋转下来 这样仍然满足搜索树
		if (curleft)//cur的左不为空时
		{
			curleft->_parent = parent;//它的父亲就是parent
		}
		cur->_left = parent;  //将parent变为cur的左

		Node* ppnode = parent->_parent;  //旋转的这部分可能是一整棵也有可能是树的一部分
		parent->_parent = cur;

		if (parent == _root) //parent是根
		{
			_root = cur;    //那么现在cur是根
			cur->_parent = nullptr;
		}
		else                 //parent不是根  将ppnode的孩子变为cur
		{
			if (ppnode->_left == parent)  //parent在根的左边
			{
				ppnode->_left = cur;
			}
			else                         //parent在根的右边
			{
				ppnode->_right = cur;
			}
			cur->_parent = ppnode;      //cur的父亲是ppnode
		}
		parent->_bf = cur->_bf = 0; //因为平衡因子的绝对值不会超过2 所以进行反转时候会变为0
	}
///
	//右单旋

	void RotateR(Node* parent)
	{
		Node* cur = parent->_left;
		Node* curright = cur->_right;

		parent->_left = curright;
		if (curright)
		{
			curright->_parent = parent;
		}
		cur->_right = parent;
		Node* ppnode = parent->_parent;
		parent->_parent = cur;//先定义ppnode 再更新parent的_parent

		if (parent == _root)
		{
			_root = cur;
			cur->_parent = nullptr;
		}
		else
		{
			
			if (parent = ppnode->_left)
			{
				ppnode->_left = cur;
			}
			else
			{
				ppnode->_right = cur;
			}
			cur->_parent = ppnode;
		}
		cur->_bf = parent -> _bf = 0;
	}
/
	//左双旋
///
	void RotateRL(Node* parent)
	{
		Node* cur = parent->_right;
		Node* curleft = cur->_left;
		int bf = curleft->_bf;

		RotateR(parent->_right);
		RotateL(parent->);

		if (bf == 0)
		{
			cur->_bf = 0;
			curleft->_bf = 0;
			parent->_left = 0;

		}
		else if (bf == 1) //右边插入
		{
			cur->_bf = 0;
			curleft->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == -1)//左边插入
		{
			cur->_bf = 1;
			cueleft->_bf = 0;
			parent->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}
///
//右双旋
//
	void RotaleLR(Node* parent)
	{
		Node* cur = parent->_left;
		Node* curright = cur->_right;
		int bf = curright->_bf;

		RotateL(parent->_left);
		Rotatel(parent);

		if (bf == 0)
		{
			parent->_bf = 0;
			cur->_bf = 0;
			curright->_bf = 0;
		}
		else if (bf == -1)
		{
			parent->_bf = 1;
			cur->_bf = 0;
			curright->_bf = 0;
		}
		else if (bf == 1)
		{
			parent->_bf = 0;
			cur->_bf = -1;
			curright->_bf = 0;
		}
	}
private:
	Node* _root = nullptr;
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值