[map的实现原理--红黑树]
map--红黑树
事情起因于pta的某道题-树种统计
数据结构题肯定不能用STL里面的map,但是这个题明显就是用map做,于是我想能不能自己实现一个map呢?了解到map的实现原理是红黑树!
前置知识:二叉搜索树
红黑树的基本原理就是二叉搜索树,二叉搜索树又叫二叉排序树,定义是左儿子比他小,右儿子比他大。那么这样查找的时候,就可以按照这种方式以logn的级别去查找,效率很高。但要考虑一种最坏的情况那就是从开头到结尾,元素的插入都是按照从大到小(或从大到小)的顺序去插入,比如插入的数是 5 4 3 2 1,那么这个树的形状就是这个样子
那么这样的话,查找的复杂度就会变成O(n),因为他每次都会从他的左儿子往下找,就相当于遍历一个数组一样,那么有没有一种方法可以解决这种问题呢?有!它就是红黑树,那么它为什么可以控制这个链状的结构呢,那就是跟他的性质有关系了。
性质
1.结点有颜色,且要么是红,要么是黑。
2.根结点是黑色。
3.所有叶子都是黑色。(注意这里的叶子指的是空结点)
4.每个红色结点的两个子结点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色结点)
5.从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点。
这些约束强制了红黑树的关键性质: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长,如图所示
这样的话,因为每条路径的黑结点数要相同,所以要使路径足够长的话,就是往任意两个黑结点中间插入一个红结点。就是这样的性质,保证了他不会出现只有一条链的情况,他的复杂度可以稳定在O(logn)
维护
既然了解了它的性质,那么如何在插入的时候就保持这样的性质呢?
首先新插入的结点把它设置为红色结点。然后我们来看这么一种情况
我们发现违反了性质4,那么我们要修正他,情况1:自己的父亲和叔叔都是红色,那么就令他的爸爸和叔叔的颜色都设置为黑色,把它爷爷的颜色设置为红色,那么他就会变成这个样子
那么我们来考虑情况2:如果将它爷爷设置为红色后,发现它爷爷的上一个也是红色怎么办?
我们发现此时它爷爷的叔叔并不是红色,那么此时就要考虑变色+旋转,方法是将新结点爷爷的父亲改为黑色,将它爷爷的爷爷设置为红色,然后以它爷爷的爷爷为旋转点旋转。(此情况为右旋),旋转为如图所示
我们再来稍微多考虑一个情况2中的小情况比如这样:
我们发现它不同于情况1的描述,与情况2的描述,但是又跟情况2有相似之处,因为他的叔叔空节点(默认为黑色),此时我们要先以它的父亲为旋转点左旋,如图所示
然后再接着跟情况2一样了,如果没有左旋的话,直接右旋会出现问题,具体可以自己手动模拟一下(自主思考还是很重要的!)
然后最后放一下树种统计的代码,如果会二叉搜索树的话,只需要看如何稳定时间logn就好了!
#include <cstdio>//表示颜色:0黑1红
#include <cstring>
#include <iostream>
typedef long long LL;
using namespace std;
const int maxn = 1e5+7;
int n,root; char s[55];
struct Map{
char fi[55];//存串
double se;//存出现的数量
int fa,color,ls,rs;//父亲,颜色,左儿子,右儿子
}tree[maxn];
int cmp(char s1[],char s2[])//比较两个串谁的字典序比较小
{
int len1 = strlen(s1);
int len2 = strlen(s2);
for(int i=0;i<min(len1,len2);i++)
{
if(toupper(s1[i]) == toupper(s2[i])) continue;
if(toupper(s1[i]) < toupper(s2[i])) return -1;
else return 1;
}
return len1 - len2;
}
struct Tree{
int sonx,nx;int tot = 0;
void put(char x[])//插入数据的函数
{
if(tot == 0)
{
tot ++;root = 1;
strcpy(tree[1].fi,x);
tree[1].se ++;tree[1].color = 0;
return ;
}
nx = root;
while(1)
{
if(cmp(tree[nx].fi,x) < 0)
{
if(tree[nx].rs == 0)
{
tot ++;
strcpy(tree[tot].fi,x);
tree[tot].se ++;
tree[tot].color = 1;
tree[tot].fa = nx;
tree[nx].rs = tot;
break;
}
else nx = tree[nx].rs;
}
else if(cmp(tree[nx].fi,x) > 0)
{
if(tree[nx].ls == 0)
{
tot ++;
strcpy(tree[tot].fi,x);
tree[tot].se ++;
tree[tot].color = 1;
tree[tot].fa = nx;
tree[nx].ls = tot;
break;
}
else nx = tree[nx].ls;
}
else{
tree[nx].se ++;
return ;
}
}
nx = tot;int pr = tree[nx].fa;//维护
while(nx != 0 && pr != root && tree[tree[nx].fa].color == 1)
{
int uncle;
if(tree[nx].fa == tree[tree[tree[nx].fa].fa].ls)
{
uncle = tree[tree[tree[nx].fa].fa].rs;
if(tree[uncle].color == 1)
{
tree[tree[nx].fa].color = 0;
tree[uncle].color = 0;
tree[tree[tree[nx].fa].fa].color = 1;
nx = tree[tree[nx].fa].fa;
}
else{
if(nx == tree[tree[nx].fa].rs)
{
nx = tree[nx].fa;
left_rotate(nx);
}
tree[tree[nx].fa].color = 0;
tree[tree[tree[nx].fa].fa].color = 1;
right_rotate(tree[tree[nx].fa].fa);
}
}
else{
uncle = tree[tree[tree[nx].fa].fa].ls;
if(tree[uncle].color == 1)
{
tree[tree[nx].fa].color = 0;
tree[uncle].color = 0;
tree[tree[tree[nx].fa].fa].color = 1;
nx = tree[tree[nx].fa].fa;
}
else{
if(nx == tree[tree[nx].fa].ls)
{
nx = tree[nx].fa;
right_rotate(nx);
}
tree[tree[nx].fa].color = 0;
tree[tree[tree[nx].fa].fa].color = 1;
left_rotate(tree[tree[nx].fa].fa);
}
}
}
tree[root].color = 0;
/*printf("--------------------------------------------\n");
for(int i=1;i<=tot;i++)
printf("%d:%d %d %d %d\n",i,tree[i].fa,tree[i].ls,tree[i].rs,tree[i].color);
printf("--------------------------------------------\n");*/
}
void left_rotate(int x)//左旋
{
int y = tree[x].rs;
if(tree[y].ls != 0)
tree[tree[y].ls].fa = x;
tree[y].fa = tree[x].fa;
if(root == x) root = y;
else if(x == tree[tree[x].fa].ls)
tree[tree[x].fa].ls = y;
else tree[tree[x].fa].rs = y;
tree[tree[y].ls].fa = x;
tree[x].rs = tree[y].ls;
tree[y].ls = x;
tree[x].fa = y;
return ;
}
void right_rotate(int x)//右旋
{
int y = tree[x].ls;
if(tree[y].rs != 0)
tree[tree[y].rs].fa = x;
tree[y].fa = tree[x].fa;
if(root == x) root = y;
else if(x == tree[tree[x].fa].ls)
tree[tree[x].fa].ls = y;
else tree[tree[x].fa].rs = y;
tree[tree[y].rs].fa = x;
tree[x].ls = tree[y].rs;
tree[y].rs = x;
tree[x].fa = y;
return ;
}
}m;
void dfs(int rt)//按照中序遍历输出,因为这样建树,中序遍历即为从小到大的顺序
{
if(tree[rt].ls != 0) dfs(tree[rt].ls);
printf("%s %.4lf%%\n",tree[rt].fi,tree[rt].se * 100 / n);
if(tree[rt].rs != 0) dfs(tree[rt].rs);
return ;
}
int main()
{
// freopen("1.txt","w",stdout);
scanf("%d",&n);getchar();
for(int i=1;i<=n;i++)
{
fgets(s,55,stdin);//处理串
int len = strlen(s);
s[len-1] = '\0';
m.put(s);
}
dfs(root);
return 0;
}
/*测试数据
5
Red Alder
Yellow Birch
Red Oak
White Oak
Black Walnut
5
Red Alder
Yellow Birch
Red Oak
White Oak
Red Alder
*/