概述:
要求:
1. 同一层的节点的纵坐标一样,不同层的节点的纵坐标的差成比例
2. 同一层相邻的节点的横坐标距离要大于某个值
3. 父亲的x轴位置是最左儿子和最右儿子的中间
4. 整幅图的宽度要尽量小

图1-多叉树
做法:
1. 对于每个节点来说,都要确定其横坐标和纵坐标,然后对应画圆画方和画线
2. 求纵坐标相对简单,每一层的纵坐标呈等差数列即可,亦即是只需要确定每个节点的深度,这里使用了广度搜索,复杂度是O(n)
3. 求横坐标的过程相对复杂,考虑每个点都需要求出一个偏移量,偏移量的定义是这个节点相对于父亲的x坐标.我们采用后序遍历的方式求解,并且每个节点的偏移量是由父亲求出而不是自己.
4. 对每个节点的操作是,首先访问所有子树,然后求出所有儿子的偏移量.我们先让第一个儿子的偏移量为0,然后我们只需要计算出每对相邻的儿子的距离
5. 一对相邻儿子(Ai,Ai+1) 的距离,是由子孙决定的.具体来说,我们要求出Ai+1 左边的所有兄弟的所有子孙中在每一层最右边的子孙相对于当前节点的偏移量,和Ai+1 的所有子孙中在每一层最左边的子孙相对于Ai+1 的偏移量.并且同一层的最右减去最左,得到一个距离.这些距离的最大值,即为(Ai,Ai+1) 的距离
6. 求完所有距离之后,我们也就是知道了所有儿子的偏移量,由于当前节点是要处于所有儿子的中心,这时候只要所有儿子的偏移量减去总距离的一半即可
7. 显然要求的距离有O(n) 个,而每次求距离最坏情况下要遍历整棵树,因此画图操作的复杂度是O(n2)

图2-算法图解
优化:
1. 事实上,我们可以把算法的复杂度优化成O(n) ,关键在于求距离这里.
2. 之前说过,求距离实际上是求出每层的最右子孙和最左子孙,然后将其总偏移量相减得到距离,再取最大的距离.我们不妨把每层的最右子孙和最左子孙称为一对关键子孙.显然,只有是同一层并且相邻的节点对才可能是关键子孙.
3. 那么,我们可以知道,我们要求的距离,实际上只需要询问所有关键子孙产生的距离即可.同时,我们发现,一对关键子孙只可能影响某一对节点的距离,我们称这对节点是目标兄弟.并且,容易发现目标兄弟的父亲就是关键子孙的最近公共祖先.

图3-概念图解
4. 我们可以用广度搜索,把所有节点按层次排成一个邻接表.然后每对相邻的节点可以作为关键子孙,求出它的最近公共祖先,来确定它们的目标兄弟.这里要求出O(n) 对节点的最近公共祖先,我们可以采用离线查询的方法,用tarjan 算法+并查集+路径压缩维护,在O(n) 的时间求出所有的最近公共祖先,并把这些关键子孙储存到目标兄弟里.
5. 对于每对要求距离的节点,我们只需求出关键子孙的总偏移量作为距离,再取最大距离即可得到这对节点的距离.但是,求出关键子孙的总偏移量意味着我们要从关键子孙开始往上走,走到当前目标兄弟这层为止.当这棵树的高度比较大的时候,这个步骤的时间复杂度会退化.
6. 我们可以用带权值的并查集去处理这个问题,点的权值就是当前的总偏移量.就像最近公共祖先维护并查集一样,只要加上路径压缩,那么获取某个节点的总偏移量的时间就能降低到平均摊分O(1)
7. 关键子孙的总个数是O(n)个,而求关键子孙的总偏移量是O(1),一共要求O(n)个距离,因此总的复杂度是O(n).这是一个非常优秀的线性算法