map和set的遗留 + AVL树(1):
在讲解新的东西之前,我们把上节遗留的问题说一下:
遗留问题:
首先,我们的最上面的代码是一个隐式类型转换,我们插入了一对数据。
我们说了,我们的方括号的底层是insert,当我们调用operator的[]的时候,这时候他的底层是insert,当我们的map没有这个数据的时候,我们这时候就会把这个数据插入。
但是当我们调用operator[]的时候,我们的map二叉树里面有我们的这个数据的时候,我们的map就会返回我们的val的引用。这时候我们就可以对他进行修改,因为返回的是引用,我们这时候就可以直接的把val进行修改。
我们看我们的最后的代码,我们的[]调用我们的二叉树的key是left的时候,我们的[]的返回值就是我们的val的引用,这时候我们打印的结果就是 “左边”。
[] 总结:
如果[]引用的数据在的话,我们就返回val的引用。如果[]引用的数据不在的话,我们就把这个数据插入到我们的map里面。
我们再看一下我们的multimap:
这个是允许我们的数据冗余的map。
我们看上面的代码:1. key相同val相同的插入。2. key相同val不同的插入。
其实他插入的时候是不看我们的val的,只看我们的key,因为我们的key才是维持我们的二叉树的看点;(我们可以修改我们的val的值,但是不能修改我们的key,因为我们的key是维持我们的二叉树的左小于根小于右的基本结构)。
我们库里面的multimap是没有我们的[]的接口的,没有我们的这个接口,因为当我们使用[]的时候,我们有好几个同名的key,那么这时候返回哪个val的引用呢?所以就没有这个接口;
其他的接口,比如我们的erase,他还是把我们的数据删除,然后给你返回删除了几个。
我们看上面,我们把key传进去,他会把所有的相同的key删除掉。
旧的遗留的问题我们已经讲完了,我们来看今天我们要新学的东西:
AVL树:
我肯看上面的概念:AVL树要求我们的左右子树都是AVL树,并且左右子树的高度差不超过1,我们的AVL树相比较我们的普通的二叉搜索树就有了本质的提升,不会再有那种偏移很大的情况;
我们上面的有一点我们提到“平衡因子”,AVL树的任何的结点里面的平衡因子都是“0,1,-1”,这个不是必须的。但是可以方便我们去观察和控制树是否平衡。
我们看这个图片:我们的搜索二叉树,左边是AVL树,右边是普通的二叉搜索树,不是AVL树。
右边的搜索二叉树的结点(10)的右边的树的高度为2,左边的树的结点的高度为0,这就不满足我们的AVL树的要求了。
AVL树的插入:
我们看我们的AVL树的插入,我们的插入和我们的普通的搜索二叉树一样的方式我们去插入,然后我们插入后更新我们的平衡因子。
我们看我们的上面的第一种的,我们更新完平衡因子后,我们发现我们的平衡因子还是在正常的范围,就结束了。
(平衡因子)更新原则:
(平衡因子)更新的停止条件:
我们看我们的剩下的的两种方式,我们插入结点以后,我们更新我们的parent的平衡因子,我们不断的往上更新(更新的原则我们看上面的概念),最后发现我们的结点的平衡因子超出我们的范围了,这时候我们就要旋转控制平衡。
旋转:
我们的旋转首先当然是要保持好我们的搜索树的规则。我们的搜索树的基本的逻辑不能改变。
让我们的树从不平衡变得平衡,其次要降低我们的树的高度。
右单旋:
右单旋就是我们的左边的树有点高了,我们往右边压一点;
左边的树的高度高了(平衡因子有问题的时候),我们就右单旋。
我们这里的a子树,他是不能自身的产生旋转的,它必须是更新到我们的根节点然后整个二叉树发生旋转。
我们的旋转的核心逻辑就是:把我们的5的右子树给到我们的10的左子树,然后把5结点作为我们的树的新的根节点,10结点引领的树作为我们的5结点的右子树。
我们这里的h我们是抽象的树,我们的树的高度为h,然后我们看几个例子,表示我们的不同的h。
我们这里的a子树,他是不能自身的产生旋转的,它必须是更新到我们的根节点然后整个二叉树发生旋转。
我们的整个第三种情况,我们的a子树必须是x,不能是其他的两种,其他的两种的话他可能是不发生旋转,也可能是直接在a子树里面发生旋转了,我们的要求自身是不能直接进行旋转的,必须要更新到我们的根节点。
随着我们的h的变大,我们的场景次数爆炸式的增长。
但是不管是下面的子树a,b,c子树有多么的复杂,我们还是遵从我们的核心的逻辑就可以。
右单旋的代码的实现:
左单旋:
我们的AVL树的右边的子树的高度比较高(平衡因子有问题)的时候,我们就进行左单旋。
我们来看我们的左单旋,当我们给我们的b子树插入一个结点的时候,这时候我们的15结点的平衡因子就变成-1,然后我们的parent结点的平衡因子就变成-2。这时候我们的右边的子树的高度比较高,这时候我们就使用左旋来解决整个问题。
左旋和右旋非常相似,我们看我们的这个图片,当我们的b子树插入一个新的结点的时候,这时候b子树的高度+1
我们看我们左单旋的核心:我们把subRL作为我们的parent的右节点,然后让parent作为我们的subR的左节点,(我们实现代码的时候,我们不但要实现我们的两个结点直接的连接逻辑,不要忘记我们的结点里面还有一个_parent指针,我们还要把我们的父亲指针指向我们的父节点)。
这个是我们的左单旋的代码的实现;