洛谷3884-二叉树问题-python-(dfs+bfs+lca+倍增)

对于一个二叉树来讲,我们通常的问题一般有一下几种:
①求解二叉树的深度
②求解二叉树的宽度
③求解两个结点最近的公共祖先

下面就利用一些算法来实现以上的要求
该代码存储树结点的数据结构主要是利用嵌套列表,即每一个结点都用一个列表存储,列表内一共有四个元素,分别是该结点的左孩子结点,该结点的右孩子结点,该结点的父亲结点,该结点的深度。

对于题目中的数据的存储方式的代码

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)

fa0123456
1-1
21
31
42
52
63
73
85
95
106

其次我们要利用我们初始化之后的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数组

fa0123456
1-1-1-1-1-1-1-1
21-1-1-1-1-1-1
31-1-1-1-1-1-1
421-1-1-1-1-1
521-1-1-1-1-1
631-1-1-1-1-1
731-1-1-1-1-1
852-1-1-1-1-1
952-1-1-1-1-1
1063-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])

最后就是我们的答案

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值