因为树的结构不同所以需要分情况考虑:
-
当树为二叉排序树,寻找给定两节点的最低公共祖先
-
当树为普通树,每个节点中有指针指向其父节点
-
当树为二叉树,每个节点仅有左右孩子指针
-
当树为普通树,每个节点仅有左右孩子指针
排序二叉树
排序二叉树相对简单,根据排序二叉树的特点,根节点的左孩子永远小于根节点的值,右孩子的值永远大于根节点的值,对给定节点Node1、Node2,要找其最低公共祖先=>实际上就是寻找一个节点,使得节点的值位于两个节点的值中间,即原问题转化为二叉树的遍历问题。
5 ======排序二叉树
/ \
3 7
/ \ / \
2 4 6 9
/
1
树的节点定义如下:
class Node:
def __init__(self, data):
self.data = data # 节点内包含的值
self.father = None # 用来标记当前节点的父节点
self.lchild = None # 左孩子
self.rchild = None # 右孩子
class SortTree:
def __init__(self):
self.root = None
实现寻找父节点的核心代码如下:
def findParent(self, node_1, node_2, root):
if root is None:
return None
elif root.data>node_1.data and root.data>node_2.data:
root = self.findParent(node_1, node_2, root.lchild)
elif root.data<node_1.data and root.data<node_2.data:
root = self.findParent(node_1, node_2, root.rchild)
return root
测试代码
def SortTreeParent():
# 排序二叉树
# 建树
n5 = Node(5)
n3 = Node(3)
n7 = Node(7)
n2 = Node(2)
n4 = Node(4)
n6 = Node(6)
n9 = Node(9)
n1 = Node(1)
n5.lchild = n3
n5.rchild = n7
n3.lchild = n2
n3.rchild = n4
n7.lchild = n6
n7.rchild = n9
n2.lchild = n1
tree = SortTree()
tree.root = n5
commonFather = tree.findParent(n2, n4, tree.root)
print(commonFather.data)
如上图排序二叉树,寻找节点2和节点4的公共最低祖先,结果为:
普通树
普通树的每个节点有一个指向父节点的指针,当要寻找公共父节点,在节点正确给定的情况下,两者最高层的祖先就是根节点,实际上,从Node1,通过父节点指针,可以找到到Node1的所有祖先(最后一个为根节点),同理,也可以找到Node2节点的所有祖先,即原问题转化为求两链表的第一个公共节点(链表不一定等长)。
A ======普通树(带父节点指针)
/ \
B C
/ \
D E
/ \ / \
F G H I
class Node:
def __init__(self, data):
self.data = data # 节点内包含的值
self.father = None # 用来标记当前节点的父节点
self.lchild = None # 左孩子
self.rchild = None # 右孩子
class NormalTree:
def __init__(self):
self.root = None
寻找最低公共父节点核心代码:
# 寻找两个链表的第一个公共节点
def findParent(self, node_1, node_2):
# 节点不为空
if node_1 is None or node_2 is None:
return None
# 得到两链表的长度
len1 = 1
temp = node_1
while(temp.father is not None):
len1 += 1
temp = temp.father
len2 = 1
temp = node_2
while(temp.father is not None):
len2 += 1
temp = temp.father
stap = int(abs(len1 - len2))
tempLong = node_1
tempShort = node_2
if len1 >= len2:
tempLong = node_1
tempShort = node_2
elif len1 < len2:
tempLong = node_2
tempShort = node_1
for i in range(stap):
tempLong = tempLong.father
for i in range(min(len1, len2)):
if tempLong.data == tempShort.data:
return tempShort
tempShort = tempShort.father
tempLong = tempLong.father
I
return None
测试代码
def NormalTreeParent():
na = Node("A")
nb = Node("B")
nc = Node("C")
nd = Node("D")
ne = Node("E")
nf = Node("F")
ng = Node("G")
nh = Node("H")
ni = Node("I")
na.lchild = nb
na.rchild = nc
nb.lchild = nd
nb.rchild = ne
nb.father = na
nc.father = na
nd.lchild = nf
nd.rchild = ng
nd.father = nb
ne.lchild = nh
ne.rchild = ni
ne.father = nb
nf.father = nd
ng.father = nd
nh.father = ne
ni.father = ne
tree = NormalTree()
tree.root = na
commonFather = tree.findParent(nf, nb)
print(commonFather.data)
实例:如上图所示,寻找节点D和节点I的最低公共祖先,结果如下:
普通二叉树(无指向父节点的指针)
当无指向父节点的指针时,要想找到最低公共祖先,可结合普通树的思维方式,考虑从根节点root开始,找出root到Node1和Node2的路径,通过路径找最低公共祖先,即原问题转化为寻找二叉树中根节点到指定节点的路径(需要利用回溯的思想)+寻找链表最后一个公共节点的问题。
A ======普通二叉树
/ \
B C
/ \
D E
/ \ / \
F G H I
树节点的定义:
class Node:
def __init__(self, data):
self.data = data # 节点内包含的值
self.lchild = None # 左孩子
self.rchild = None # 右孩子
class GenericTree:
"""普通的二叉树(没有父节点)
"""
def __init__(self):
self.root = None
寻找的过程主要分为两步:先找出路径,再求路径上最后一个公共节点,代码如下:
def findPath(self, root, node, path):
# 回溯方法寻找根节点到某节点的路径
if root is None or node is None:
return False
if root.data == node.data:
path.append(root)
return True
# 递归左右孩子节点
if self.findPath(root.lchild, node, path):
path.append(root)
return True
if self.findPath(root.rchild, node, path):
path.append(root)
return True
return False
def findParent(self, node_1, node_2):
# 路径是从目标节点到根节点
path1 = []
self.findPath(self.root, node_1, path1)
path2 = []
self.findPath(self.root, node_2, path2)
len1 = len(path1)
len2 = len(path2)
if len1 > len2:
path1 = path1[len1-len2:]
else:
path2 = path2[len2-len1:]
for i in range(min(len1, len2)):
if path1[i] == path2[i]:
# 因为至少会有一个根节点是共同的祖先,所以在循环内return就可以,无需另外判断
return path2[i]
测试代码
def GenericTreeParent():
na = Node("A")
nb = Node("B")
nc = Node("C")
nd = Node("D")
ne = Node("E")
nf = Node("F")
ng = Node("G")
nh = Node("H")
ni = Node("I")
na.lchild = nb
na.rchild = nc
nb.lchild = nd
nb.rchild = ne
nd.lchild = nf
nd.rchild = ng
ne.lchild = nh
ne.rchild = ni
tree = GenericTree()
tree.root = na
commonFather = tree.findParent(nh, nc)
print(commonFather.data)
实例:寻找节点H到节点C的最低公共祖先,运行结果如下:
普通树
对二叉树的操作,总是仅涉及最多左右孩子两个指针的问题,然而对于具有普通结构的树,每个节点的孩子节点数不同,我们就需要改变树节点的定义,可以考虑用一个结合来存储树的孩子节点的信息,思路相对不变,仅在查找路径时有些许改变。
A ======普通树(不带父节点指针)
/ | \
B C K
/ \
D E
/ \ /|\
F G H I J
树的定义:
class TreeNode:
def __init__(self, data):
self.data = data
self.children = []
class Tree:
def __init__(self):
self.root = None
核心代码
def findPath(self, root, node, path):
# 回溯方法寻找根节点到某节点的路径
if root is None or node is None:
return False
if root.data == node.data:
path.append(root)
return True
# 递归所有孩子节点
for child in root.children:
if self.findPath(child, node, path):
path.append(root)
return True
return False
def findParent(self, node_1, node_2):
path1 = []
self.findPath(self.root, node_1, path1)
path2 = []
self.findPath(self.root, node_2, path2)
len1 = len(path1)
len2 = len(path2)
if len1 > len2:
path1 = path1[len1-len2:]
else:
path2 = path2[len2-len1:]
for i in range(min(len1, len2)):
if path1[i] == path2[i]:
# 因为至少会有一个根节点是共同的祖先,所以在循环内return就可以,无需另外判断
return path2[i]
测试代码
def TreeParent():
na = TreeNode("A")
nb = TreeNode("B")
nc = TreeNode("C")
nd = TreeNode("D")
ne = TreeNode("E")
nf = TreeNode("F")
ng = TreeNode("G")
nh = TreeNode("H")
ni = TreeNode("I")
nj = TreeNode("J")
nk = TreeNode("K")
na.children.append(nb)
na.children.append(nc)
na.children.append(nk)
nb.children.append(nd)
nb.children.append(ne)
nd.children.append(nf)
nd.children.append(ng)
ne.children.append(nh)
ne.children.append(ni)
ne.children.append(nj)
tree = Tree()
tree.root = na
commonFather = tree.findParent(ng, nj)
print(commonFather.data)
实例:寻找节点G与节点J的最低公共祖先,运行结果如下:
害,关于树的问题实在是太多了,但是分解成一个个的小问题就不显得那么难了,递归在树中的应用实在太多了,最低公共祖先就算是更新完了,完整代码链接