还没有学习的东西的指引:对于这个bst的中序、后序的非递归??
至于为什么这个avl树旋转是logN 这个其实我也不太知道。
这里再看:avl树的底层存储结构并不是说:并不是bst树,而是说同样地:存储根节点和size,原因:它的remove和add操作和普通bst不太一样的。
花了好久,终于把avl树的写法给弄好了,学会的技术细节:
思路历程需要学习的:
1.Math这个库是自带的,直接用Math就可以了,不用带什么库import
2.对于Node而言,需要添加height这个字段,这个是用来看高度到底为多少的,越往上,根越高。
3.height表示的是层次和高度,越往上,高度为高。 引入这个节点的目的是:为了去检测左右子树的深度差是否大于1,即是否平衡这一条件。如果大于1的话就认定是不平衡了。
4.
这是判定 是否为二叉搜索树的一个函数,就是中序遍历之后,查看是否从小到大排序。
如果非从小到大排序,那么就不是二叉搜索树。
5.对于树或者说递归之类的结构,必须要搞清楚 函数的定义是什么, 你需要拿这个函数返回的值是什么?
返回的是:这个根的高度?还是说这个以这个为根的子树的高度。
6.对于特殊情况来说,自己区分一下,有的时候和 出口是同样的意思,有的时候和出口不同,要和出口相区分。
public int getHeight(Node node) //height的定义,返回以node为根的树的高度
//key:必须清楚返回的是当前节点,还是说返回的是以node为根的树的高度
{
if(node==null) //其实在这里也让我看到,除了说:作为特殊情况,其实特殊情况和出口是一体的感觉
return 0; //空树没高度
else
{
return Math.max(getHeight(node.left),getHeight(node.right))+1;
}
}
7. 不管是哪些容器,底层都写好了一些add remove之类的。
8.平衡因子的概念: 左子树的高度减去右子树的高度。。 key:说的是左子树的高度减去右子树的
即左边去减去右边,是为了告诉你:左右子树是否相差1以及以上。
9.
balanced这件事:找准这个函数的定位就比较能知道怎么去写了。
函数的定位:以node为根的树是否平衡
就是说: 如果说是空树的话,那么是平衡的。 如果说当前是平衡的(平衡因子的概念就用到这里了),那么还要去看子树是不是平衡
如果说当前不是平衡的,那么就直接返回是错。
10.
右旋的含义
首先搞清楚:左左偏的含义(LL)
首先,搞清楚LL的含义,RR的含义,说的是前面的两根线。到达末尾的时候,添加两个子树
对于另外的两个而言,也认定补全其结构。像图2的F和G就可以补
对于左旋、右旋的含义是:对于根节点而言,左旋右旋。
其实是一个和LL RR对应的概念来的。(左旋的意思是逆时针,右旋的意思是顺时针)
意思是说:
记住:T1 T2在这个位置,一定是1级的,虽然说变矮了。但是高度是由它的子树的多少决定的,由子树的高度决定的,而不是由处于的位置决定的。
但是的话x和y可能下降: 原因:如果说t1是空 t2是空的话 那么y下降了。
如果说T3 T4 是要求不为空的, 那么x其实会随着y下降而下降吗?
其实不太会。
也就是说:旋转了之后,记得要调整高度,调整高度是很重要的。
旋转其实就是指针的改变,x和y的子树的指向的改变。
11.
记住,对于这里的话,只调用,不做其他的事情。
原因:对于这些height的改变,需要当场add完之后就改变。所以在它内在的调用函数实现height的改变
而不是说在外面调用的接口这做一些事情。
12.
在添加逻辑里面,其实这个为空的,既是特殊情况,也是出口条件
对它的子树添加完了之后,对于这个节点,需要改变其高度
也就是说改变高度这件事情是很重要。
13.对于旋转的选择时机逻辑:
key:意思是说: 对于大于1,或者说小于-1,那么说明不平衡 到一个地步了。
那么如果说是left小于等于0(记得等于也是ok的),这是等于0的情况
只需要知道根的不平衡情况达到了条件,并且知道相应的 方向的 不平衡情况 是 同一方向(等于0也算是同一方向)即可。
这里说的是: 需要知道 根的子树的方向是逆方向,必须是 逆的 所以必须是小于0
你细品即可。
对于什么时候应该选择旋转都知道了。
底部先旋转,得到答案,再往上。
双旋转,那么需要先对底层进行旋转,再对上层旋转。
先保存returnNode 然后的话size--肯定是在这里写。
在外面的remove接口不写size-- 原因: remove接口就只调用这件事,不做其他的事情。
size--的话,因为就算是左右都不为空的情况下,那么也是找最小的节点,它的子树为空的。
因为在外面的话,它的returnNode需要看,如果returnNode为空了,直接return
然后还有改变高度这件事情,也是在remove之后的外面逻辑去改变高度。
然后的话外面就是旋转的逻辑在了。
也就是说:对于节点新添加的字段,各种操作会怎么影响它这个新的字段,需要考虑
对于递归的增加,递归的删除。在相应部分对其子节点处理完之后,对该节点也需要进行高度的改变等操作。
改变的是子节点 还是该节点 需要注意,递归的返回,需要对该节点的高度等也进行处理。
处理完子之后处理当前
也就是说对于递归的结果的返回,如何改变高度这件事情,递归性返回改变的高度这个结果。 就是通过: 解决完子节点之后解决当前结点 来处理的。
并且 : 引入returnNode 先保存当前结果这件事是值得学习的。
avl树已经写完了,并且里面我写错的逻辑也进行了订正。
对于avl树去实现map,实现set都不用说,其实就是调用其中的函数而已。拿一个avl去作为底层的存储结构。
这里set和map其实就是说添加的逻辑不一样而已。只添加key,就是set 添加key和value就是map
avl树引入的目的:是为了退化成线性表的不被发生,防止的是最坏的情况都有logN