目录
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树的概念
1、新增在左,parent平衡因子减减2、新增在右,parent平衡因子加加更新后parent平衡因子 ==0,说明parent所在的子树高度不变,不会再影响祖先,不用再继续沿着root的路径往上更新------(插入结束)更新后parent平衡因子 ==1 or -1,说明parent所在的子树高度变化,会影响祖先,需要沿着root的路径往上更新---------(继续执行1、2)更新后parent平衡因子 ==2 or -2,说明parent所在的子树高度变化且不平衡,对parent所在子树进行旋转,让它平衡 -----(插入结束)更到根节点 -----(插入结束)
旋转的时候需要注意的问题:
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
}
//面试一般不会手撕//
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;
};