对于一个二叉树来讲,我们通常的问题一般有一下几种:
①求解二叉树的深度
②求解二叉树的宽度
③求解两个结点最近的公共祖先
下面就利用一些算法来实现以上的要求
该代码存储树结点的数据结构主要是利用嵌套列表,即每一个结点都用一个列表存储,列表内一共有四个元素,分别是该结点的左孩子结点,该结点的右孩子结点,该结点的父亲结点,该结点的深度。
对于题目中的数据的存储方式的代码
n=int(input())
tree=[[0 for j in range(4)] for i in range(n+1)]
#用列表来存储 元素分别是:左孩子 右孩子 父亲 深度
for i in range(n-1):
begin,end=map(int,input().split())
if tree[begin][0]==0:
tree[begin][0]=end
else:
tree[begin][1]=end
tree[end][2]=begin
tree[end][3]=tree[begin][3]+1
测试数据
10
1 2
1 3
2 4
2 5
3 6
3 7
5 8
5 9
6 10
8 6
我们构建的树大概是这个样子
其中一级列表的下标为结点,四个元素分别如上面所述,对比了一下图片,没啥问题。
[[0, 0, 0, 0],
[2, 3, 0, 0],
[4, 5, 1, 1],
[6, 7, 1, 1],
[0, 0, 2, 2],
[8, 9, 2, 2],
[10, 0, 3, 2],
[0, 0, 3, 2],
[0, 0, 5, 3],
[0, 0, 5, 3],
[0, 0, 6, 3]]
①解决二叉树的深度问题无疑是利用dfs算法一直向下搜索,到终点后记录深度,最后输出最大深度。
从根节点搜起,只要还有左孩子或者右孩子,就继续往下搜索。
#利用DFS
def get_deep(point,de):
global deep
if point==0:
return
deep=max(deep,de)
for i in range(2):
get_deep(tree[point][i],de+1)
deep=0
get_deep(1,1)
最后的deep就是我们要的最大的深度
②求解树的宽度,我利用bfs算法得出树的宽度。首先开一个以为数组,数组的大小为树的深度,我们记录每一个深度下的结点的个数,然后取最大值就是树的宽度。
我们在初始化的时候就已经得出了树的深度了,所以我们利用bfs,算出每一层的结点数目
#利用BFS
def get_wide(point):
global wide
rec=[0]*(101)
queue=[]
queue.append([point,1])
while len(queue)!=0:
front=queue.pop(0)
rec[front[1]]+=1
for i in range(2):
if tree[front[0]][i]!=0:
queue.append([tree[front[0]][i],front[1]+1])
wide=max(rec)
wide=0
get_wide(1)
③求解两个结点的LCA
首先我们要让两个结点处于同一深度,然后一起向上走,看到达的结点是否相同,相同就停止寻找,返回该结点就是这两个结点的公共祖先。这种方法不管是升至同一深度还是两个结点一起向上走,每次都是走一次,直至达到所求。这样做的缺点就是当有很多组询问,并且树比较大的时候,是比较消耗时间的,我们希望对此进行优化。
我们想到了倍增数组来进行时空权衡
定义一个二维数组fa[n+1] [7],一个一维数组dep[n+1]
fa数组是记录某一结点往上面跳2^i 次后所位于的结点,例如fa[6][2]代表6号结点往上移动2^2=4次后所位于的结点,这里的向上是指找到当前结点的父结点
dep数组用来记录某结点的深度,事实上深度我们在初始化的时候已经实现,这一步可以不用做
首先我们要对fa数组进行初始化,即得出每一个结点的父结点,在这里我们可以看到我们初始化的是fa[i][0],2^0=1,也就是求出每一个结点的父结点
#父结点数组
fa=[[-1 for i in range(7)] for j in range(n+1)]
#结点深度数组
dep=[-1 for i in range(n+1)]
#利用dfs算法的出每一个结点祖先
def dfs(root,deep):
dep[root]=deep
for i in range(2):
if tree[root][i]!=0:
fa[tree[root][i]][0]=root
dfs(tree[root][i],deep+1)
我们当前得出的状态如下表格所示:(空格都是-1)
fa | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|---|
1 | -1 | ||||||
2 | 1 | ||||||
3 | 1 | ||||||
4 | 2 | ||||||
5 | 2 | ||||||
6 | 3 | ||||||
7 | 3 | ||||||
8 | 5 | ||||||
9 | 5 | ||||||
10 | 6 |
其次我们要利用我们初始化之后的fa数组来完善fa数组
#得出倍增数组
def get_fa():
for i in range(1,n+1):
for j in range(1,7):
#当前结点的上一个元素存在时
if fa[i][j-1]!=-1:
fa[i][j]=fa[fa[i][j-1]][j-1]
假设我们要求fa[1][1],就是1号结点的前2个结点,等于求1号结点的前一个结点的前一个结点,而一号结点之前的结点已经得出,可以利用以求推出。
得出如下所示fa数组
fa | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|---|
1 | -1 | -1 | -1 | -1 | -1 | -1 | -1 |
2 | 1 | -1 | -1 | -1 | -1 | -1 | -1 |
3 | 1 | -1 | -1 | -1 | -1 | -1 | -1 |
4 | 2 | 1 | -1 | -1 | -1 | -1 | -1 |
5 | 2 | 1 | -1 | -1 | -1 | -1 | -1 |
6 | 3 | 1 | -1 | -1 | -1 | -1 | -1 |
7 | 3 | 1 | -1 | -1 | -1 | -1 | -1 |
8 | 5 | 2 | -1 | -1 | -1 | -1 | -1 |
9 | 5 | 2 | -1 | -1 | -1 | -1 | -1 |
10 | 6 | 3 | -1 | -1 | -1 | -1 | -1 |
我们得出了每一个结点往上跳2^i步所到达的结点的序号,-1表示跳当前步数没有结点
接下来我们利用这个数组来对“向上走”进行优化。
我们将结点移动到同一深度,再一起向上走
#x为较深的那一层
if dep[x]<dep[y]:
x,y=y,x
#将x,y调整到同一层
for i in range(6,-1,-1):
if (dep[x]-2**i)>=dep[y]:
x=fa[x][i]
if x==y:
return x
#一起向上跳
for i in range(6,-1,-1):
if fa[x][i]!=-1 and fa[x][i]!=fa[y][i]:
x=fa[x][i]
y=fa[y][i]
return fa[x][0]
找出父结点之后,一切都变得简单起来
dfs(1,1)
get_fa()
print(fa)
anc=LCA(x,y)
print((dep[x]-dep[anc])*2 +dep[y]-dep[anc])
最后就是我们的答案