在说《二叉搜索树》时,就说结局会浮现出来,其实前面的数据结构,都是为这篇博文的。
》关联式容器
在前面说到vector、list、deque等序列式容器,其数据的存储都是线性的,其查询的时候时间复杂度都比较高。关联式容器也是存储的数据,但是这里的数据都是以键值对的形式存在的,其检索的效率是非常高的。关联式容器有两种不同的结构,接下来要说的是树型结构的关联式 。
该结构的容器主要有四种:map、set、multimap、multiset。这四种容器的共同点是:使用红黑树作为其底层结果,容器中的元素是一个有序的序列。
》键值对
用来表示两种数据一一对应的数据结构,就像一把钥匙对应一把锁,该结构由两个成员构成,key和value。key对应的就是value,key就是钥匙,value就是锁。
SGI-STL中关于键值对的定义。
template <class T1, class T2>
struct pair
{
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
pair()
: first(T1())
, second(T2())
{}
pair(const T1& a, const T2& b)
: first(a)
, second(b)
{}
};
》map
1、概念
map关联式容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元素。在内部,map中的元素总是按照键值key进行比较排序的。因此键值key用于排序和惟一地标识元素,而值value中存储与此键值key关联的内容。
map支持下标访问符,即在[]中放入key,就可以找到与key对应的value。其他树形结构都没有重载[]。
map中key是独一无二的,不能重复,而value是可以重复的。就像一把钥匙只能开一把锁,但是一把锁可以被多把钥匙打开。
2、使用
map的详细介绍和使用,这里有点我。下面只是简单的使用一下。
#include<map>
void MapTest()
{
map<int, string> mp1;
mp1.insert(make_pair(2, "大学"));
mp1.insert(make_pair(4, "中国"));
mp1.insert(make_pair(3, "陕西"));
for (const auto& e : mp1)
{
cout << e.first << "--->" << e.second << endl;
}
//对于[]的操作,如果这个key存在则会修改原本的value
//不存在,则会插入这个key和其value。
mp1[5] = "中学";
mp1[4] = "China";
cout << "===================" << endl;
for (const auto& e : mp1)
{
cout << e.first << "--->" << e.second << endl;
}
}
》multimap
1、概念
multimap其实就是map的进化版本,它的key是可以重复的。仅此而已,它的头文件都是<map>。由于key值可以相同,于是multimap没有重载[],因为[]访问会有二义性。
2、使用
#include<map>
void MultimapTest()
{
multimap<int, string> mulp;
mulp.insert(make_pair(4, "四"));
mulp.insert(make_pair(4, "4"));
mulp.insert(make_pair(4, "four"));
for (const auto& e : mulp)
{
cout << e.first << "--->" << e.second << endl;
}
}
》set
1、概念
set的value就是它的key,即set的键值对就是<value, value>,但是set的键还是不能重复,由于value和key是相同的,所以在进行操作时,不用构建键值对,正是由于value和key相同,于是又达成数据去重的成就。set没有重载[]。
2、使用
void SetTest()
{
int arr[] = { 1,5,4,9,6,3,4,7,1,8,5 };
set<int> sarr;
for (int i = 0; i < sizeof(arr) / 4; ++i)
{
sarr.insert(arr[i]);
}
for (const auto& e : sarr)
{
cout << e << endl;
}
}
》multiset
1、概念
multiset是set的进阶,也就是说multiset的key也是可以重复的。
2、使用
void MultisetTest()
{
int arr[] = { 1,5,4,9,6,3,4,7,1,8,5 };
multiset<int> sarr;
for (int i = 0; i < sizeof(arr) / 4; ++i)
{
sarr.insert(arr[i]);
}
for (const auto& e : sarr)
{
cout << e << endl;
}
}
与set进行对比。
》OJ实践
leetcode——692. 前K个高频单词
给一非空的单词列表,返回前 k 个出现次数最多的单词。
返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率,按字母顺序排序。
class Solution {
public:
vector<string> topKFrequent(vector<string>& words, int k) {
map<string, int> mp1;
multimap<int, string, greater<int>> mp2;//降序排序
vector<string> ret;
for(const auto& str : words)//先用map进行数量上的排序
++mp1[str];
for(const auto& m : mp1)
mp2.insert(make_pair(m.second, m.first));//在用multimap输出,保证重复次数的单词也会统计
auto b = mp2.begin();
while(b != mp2.end() && k--)
{
ret.push_back(b->second);
++b;
}
return ret;
}
};
》又见红黑树
1、红黑树的改造
前面所说的红黑树,只是为了实验红黑树的性质,以及验证代码实现的结果。上面的四种容器都是以红黑树为底层结构,所以一棵红黑树却可以实现四种容器,这就需要对前面的红黑树进行改造。
由于set和map不一样,set只有key,map有key和val所以,需要用到仿函数。
前面的herder,就是用到红黑树的迭代器部分的end()。
改造的的源代码,请点击红黑树的改造
主要要说的是我们的红黑树迭代器。
2、红黑树的迭代器
在前面说红黑树在创建的时候增加了一个headers的头节点,所以这次来看看这个结点的作用。
迭代器的简单来说,就是指针。于是我们实现的功能大抵有++,--,->,*等。这里主要阐述一下++。
红黑树是一棵二叉搜索树,所以对于其的中序遍历是一个有序的数组,其实它的++遍历就是一个中序遍历。所以实现++就是把中序遍历分解成一步一步的操作。
红黑树的迭代器的begin是headers的left_,就是红黑树最左边的结点,即最小的结点。中序遍历便是从这里开始的。
现在开始分解步骤,假设cur是当前要++的结点,类比中序遍历,分为下面的三种情况:
cur是左孩子:下一次访问的应该是它的父结点,因为右孩子 > 父结点 > 左孩子
cur是父结点:下一次访问的是右孩子的左子树,如果左子树不存在,就是cur的右孩子
cur是右孩子:由于父结点已经被访问了,则需要向上回溯。
综上,可以归结为++后有没有右孩子。
如果存在右孩子:就直接找右孩子的最左边的结点,如果不存在左子树,++后就是该结点的右孩子。
如果不存在右孩子:就要向上回溯了,找到一个父亲结点,这个父亲结点不是祖父结点的右孩子就行,因为如果是右孩子,这个父结点又是被访问过的。就继续向上回溯。
template <class T>
struct RBTreeIterator
{
typedef RBTreeNode<T>* pNode;
typedef RBTreeIterator<T> Self;
pNode node_;
RBTreeIterator(pNode node)
:node_(node)
{}
T& operator*()
{
return node_->data_;
}
T* operator->()
{
return &operator*();
}
bool operator!=(Self rbt)
{
return node_ != rbt.node_;
}
Self operator++()
{
if (node_->pRight_)
{
node_ = node_->pRight_;
while (node_->pLeft_)
{
node_ = node_->pLeft_;
}
}
else
{
pNode parent = node_->pParent_;
while (node_ == parent->pRight_)
{
node_ = parent;
parent = node_->pParent_;
}
node_ = parent;
}
return *this;
}
Self operator++(int)
{
Self tmp(*this);
++(*this);
return tmp;
}
};
彩蛋
既然是两种结构的容器,那么另一种……