python算法二叉树总结整理
整理B站视频教程:https://www.bilibili.com/video/BV1TV41177uv?p=1
内容:
(1)N个结点不同二叉树的个数
(2)二叉树的创建
(3)遍历二叉树
(4)二叉树的深度
(5)拷贝二叉树
(6)二叉树实际应用--二叉搜索树
一、N个节点不同二叉树的个数
N个结点可以构成多少不同的二叉树?
分析:n个结点,其中一定有一个是根结点(root);另外的n-1个结点在根结点的左子树或者右子树,然后左子树看作根结点,又有不同的左右个数分配……
假设n=4,那么除去根结点还有3个结点,可以是根结点左边0个结点、右边3个节点,或者根结点左边1个结点,或者左边两个结点,或者左边3个结点;而每一次左边有多个结点时,第三层结点分布又有不同的排列组合……
假设n个结点,左边有k个结点,则右边有n-1-k个结点。设左边k个结点的组合方式为cl,右边结点的组合方式为cr,所以共有的组合方式:cl*cr
python代码实现:
1 # def count(n): 2 # # root :1 3 # # left :k [0,n-1] 4 # #right :n-k-1 5 # if n == 0: 6 # return 1 7 # s = 0 8 # for k in range(n): 9 # s += count(k) * count(n-k-1) 10 # return s 11 #=========================================加入缓存机制,可以超级快的提高计算速度,因为属于迭代算法,每一次计算都要去先算前面n的值,所以加入缓存可以将之前算的都存下来================== 12 def count(n): 13 # root :1 14 # left :k [0,n-1] 15 #right :n-k-1 16 # if n == 0: 17 # return 1 #直接将n=0,s=1这种情况加入字典 18 s = count.cache.get(n,0) 19 if s: 20 return s #如果s不是0,直接返回 21 # s = 0 #s = count.cache.get(n,0)的意思就是,如果缓存cache中有n,那么就将n赋值给s,如果缓存cache中没有n,那么就将缓存的默认参数0赋值给s 22 # 所以,上面的代码就已经将s=0的可能考虑在内了 23 for k in range(n): 24 s += count(k) * count(n-k-1) 25 count.cache[n] = s #将新计算的n和对应的S加入缓存字典 26 return s 27 count.cache = {0:1} #创建一个缓存,用于存储已经计算过的n,默认情况,n=0时,s=1 28 print(count(100))
二、创建二叉树
分析:创建二叉树,其实就是先创建结点,然后给各个结点建立父子或者兄弟关系;采用python中的类来创建结点,利用构造器来给这个类初始化
python代码实现:
1 class TreeNode(object): 2 def __init__(self,data,left = None, right = None): #初始化结点,因为左右结点都可能为空,所以默认设置左右结点的值为空,如果没有传入左右结点就是空,有的话则是传入的结点 3 self.data = data #这个结点需要有数值 4 self.left = left #这个结点可能有左结点 5 self.right = right #这个结点可能有右结点 6 7 def __str__(self): #对结点数值输出做的改进,输出结点的时候,自动输出为结点的数值,也就是不用写C.right.data中的data 8 return (str(self.data)) 9 #============================调用TreeNode类来创建结点 10 # A = TreeNode("A") 11 # B = TreeNode("B") 12 # C = TreeNode("C") 13 # D = TreeNode("D",left=A) #上面的A、B、C只是构造出来,并没有给他们赋予左右子结点,构造D的时候就直接在复制时将A作为D的左结点 14 # E = TreeNode('E') 15 # E.left = B #在构造E的时候并没有给E的左结点赋值,只是构造出来,但是可以通过后期给E.left赋值 16 A,B,C,D,E,F,G,H,I = [TreeNode(X) for X in 'ABCDEFGHI'] #利用列表迭代的方法创建独立结点 17 #=================================给各个结点之间创建联系============================= 18 A.left = B 19 A.right = C 20 B.right = D 21 C.left = E 22 C.right = F 23 E.left = G 24 F.left = H 25 F.right = I 26 # print(C.right.data) #输出测试,输出C的右结点的值,也就是F,但是可以做改进,不用写data 27 print(C.right)
三、遍历二叉树
遍历二叉树也就是二叉树的每一个结点都要访问到,根据遍历的顺序,分为先序遍历、中序遍历、后序遍历和层序遍历;先序遍历:根左右;中序遍历:左根右;后续遍历:左右根;层序遍历:每一层自左向右。
(1)先序遍历
分析:遍历的过程先遍历传入的根结点root,然后递归的遍历根结点的左结点作为根结点的子树,然后递归的遍历根结点的右结点作为根结点的子树;整个递归的结束条件,应该是这个根结点不再有左右结点。
代码实现:
from TreeNode import TreeNode def creatTree(): A, B, C, D, E, F, G, H, I = [TreeNode(X) for X in 'ABCDEFGHI'] # 利用列表迭代的方法创建独立结点 # =================================给各个结点之间创建联系============================= A.left = B A.right = C B.right = D C.left = E C.right = F E.left = G F.left = H F.right = I return A def preOrder(node): if node is None: return print(node.data) preOrder(node.left) preOrder(node.right) if __name__ == '__main__': root = creatTree() preOrder(root)
(2)中序遍历
分析:和先序遍历斯思想一样,但是不是先输出根结点,而是先递归的找到左结点
python代码实现:
1 from TreeNode import TreeNode 2 def creatTree(): 3 A, B, C, D, E, F, G, H, I = [TreeNode(X) for X in 'ABCDEFGHI'] # 利用列表迭代的方法创建独立结点 4 # =================================给各个结点之间创建联系============================= 5 A.left = B 6 A.right = C 7 B.right = D 8 C.left = E 9 C.right = F 10 E.left = G 11 F.left = H 12 F.right = I 13 return A 14 #===============先序遍历========================================== 15 # def preOrder(node): 16 # if node is None: 17 # return 18 # print(node.data) 19 # preOrder(node.left) 20 # preOrder(node.right) 21 22 #================中序遍历=========================================== 23 def inOrder(node): 24 if node is None: 25 return 26 inOrder(node.left) 27 print(node.data) 28 inOrder(node.right) 29 30 if __name__ == '__main__': 31 root = creatTree() 32 # preOrder(root) 33 inOrder(root)
(3)后序遍历
分析:思路和先序一样,但是是先递归找到树的左结点和右结点,然后是根
python代码实现:
1 from TreeNode import TreeNode 2 def creatTree(): 3 A, B, C, D, E, F, G, H, I = [TreeNode(X) for X in 'ABCDEFGHI'] # 利用列表迭代的方法创建独立结点 4 # =================================给各个结点之间创建联系============================= 5 A.left = B 6 A.right = C 7 B.right = D 8 C.left = E 9 C.right = F 10 E.left = G 11 F.left = H 12 F.right = I 13 return A 14 #===============先序遍历========================================== 15 # def preOrder(node): 16 # if node is None: 17 # return 18 # print(node.data) 19 # preOrder(node.left) 20 # preOrder(node.right) 21 22 #================中序遍历=========================================== 23 # def inOrder(node): 24 # if node is None: 25 # return 26 # inOrder(node.left) 27 # print(node.data) 28 # inOrder(node.right) 29 #================后序遍历=========================================== 30 def postOrder(node): 31 if node is None: 32 return 33 postOrder(node.left) 34 postOrder(node.right) 35 print(node.data) 36 37 if __name__ == '__main__': 38 root = creatTree() 39 # preOrder(root) 40 # inOrder(root) 41 postOrder(root)
(4)回溯迭代版本的先序
分析:前面的思路是迭代版本,还可以通过回溯的方法实现。回溯首先需要有一个栈,用来存储遍历的根结点,每次都去找左结点找根结点,一直到根为0,就应该弹出最后的根结点然后访问这个结点的右结点,将右结点作为新的根。回溯结束的条件应该是栈为空,也就是说,等到所有的都抛出去,也就是所有的右结点也都访问到了,此时结束。
python代码实现:
1 from TreeNode import TreeNode 2 3 4 def creatOrder(): 5 A,B,C,D,E,F,G,H,I = [TreeNode(X) for X in 'ABCDEFGHI'] 6 A.left = B 7 A.right = C 8 B.right = D 9 C.left = E 10 C.right = F 11 E.left = G 12 F.left = H 13 F.right = I 14 return A 15 def preOrder(node): 16 s = [] 17 while True: 18 while node: 19 print(node) 20 s.append(node) 21 node = node.left 22 if not s: 23 break 24 node = s.pop().right 25 if __name__ == "__main__": 26 root = creatOrder() 27 preOrder(root)
(5)层序遍历
分析:层序遍历是指按着一层一层去遍历结点,可以采用队列的方式进行,每次遍历根据出队的结点顺序。先是父结点入队,然后父结点出队的时候子节点入队,保证父结点始终在子节点前面,而且子节点入队的时候也是左边的子节点先入队,保证出队先左节点后右结点。等到队列为空的时候,也就是所有结点都遍历到了。
python代码实现:
1 from collections import deque 2 3 from TreeNode import TreeNode 4 5 6 def creatTree(): 7 A,B,C,D,E,F,G,H,I = [TreeNode(X) for X in 'ABCDEFGHI'] 8 A.left = B 9 A.right = C 10 B.right = D 11 C.left = E 12 C.right = F 13 E.left = G 14 F.left = H 15 F.right = I 16 return A 17 def levelOrder(node): 18 q = deque([node]) 19 while q: 20 node = q.popleft() #父结点先出队 21 print(node) 22 if node.left: 23 q.append(node.left) #如果有左结点,左结点入队 24 if node.right: 25 q.append(node.right) 26 27 if __name__ == '__main__': 28 root = creatTree() 29 levelOrder(root)
四、树的深度
(1)递归方法计算
分析:计算树的深度,就是子节点中深度的最大值加1,求每个子节点的深度的时候,又递归的分为子节点深度最大值加1.
python代码实现:
1 from TreeNode import TreeNode 2 3 4 def creatTree(): 5 A,B,C,D,E,F,G,H,I = [TreeNode(X) for X in 'ABCDEFGHI'] 6 A.left = B 7 A.right = C 8 B.right = D 9 C.left = E 10 C.right = F 11 E.left = G 12 F.left = H 13 F.right = I 14 return A 15 def deep(node): 16 if node is None: 17 return 0 18 dl = deep(node.left) 19 dr = deep(node.right) 20 dp = max(dl,dr) + 1 21 return dp 22 if __name__ == '__main__': 23 root = creatTree() 24 print(deep(root))
(2)迭代 方法计算
分析:思考层序遍历的方法思路,在进行层序遍历的时候,最后一个出队的结点也就在最深一层,所有只要知道最后一个出队的结点的深度也就知道整个树的深度。所以在向队中存储结点的时候,需要把这个结点的深度也存进去,那就可以用一个元祖包含结点值和结点深度。
python代码实现:
1 from collections import deque 2 3 from TreeNode import TreeNode 4 5 6 def creatTree(): 7 A,B,C,D,E,F,G,H,I = [TreeNode(X) for X in 'ABCDEFGHI'] 8 A.left = B 9 A.right = C 10 B.right = D 11 C.left = E 12 C.right = F 13 E.left = G 14 F.left = H 15 F.right = I 16 return A 17 def deep(node): 18 if node is None: 19 return 0 20 dl = deep(node.left) 21 dr = deep(node.right) 22 dp = max(dl,dr) + 1 23 return dp 24 #================================================以迭代的方式计算深度============================================ 25 def deep2(node): 26 q = deque([(node,1)]) #以元祖的形式来表示结点的值和深度 27 while q: 28 (node,d) = q.popleft() 29 if node.left: 30 q.append((node.left,d+1)) #前面抛出过程中,抛出的d是父结点的深度,给子节点赋值深度的时候就需要在父结点深度的基础上加1 31 if node.right: 32 q.append((node.right,d+1)) 33 return d 34 if __name__ == '__main__': 35 root = creatTree() 36 print(deep2(root))
五、树的拷贝
分析:采用递归的思想,先拷贝左子树和右子树,然后创建根结点,在创建根结点时将拷贝到的左右子树赋值给创建的结点
python代码实现:
1 from collections import deque 2 3 from TreeNode import TreeNode 4 def creatTree(): 5 A,B,C,D,E,F,G,H,I = [TreeNode(X) for X in 'ABCDEFGHI'] 6 A.left = B 7 A.right = C 8 B.right = D 9 C.left = E 10 C.right = F 11 E.left = G 12 F.left = H 13 F.right = I 14 return A 15 def treeCopy(node): 16 if node is None: 17 return None 18 cl = treeCopy(node.left) 19 cr = treeCopy(node.right) 20 node = TreeNode(node.data,cl,cr) 21 return node 22 def levelOrder(node): 23 q = deque([node]) 24 while q: 25 node = q.popleft() #父结点先出队 26 print(node) 27 if node.left: 28 q.append(node.left) #如果有左结点,左结点入队 29 if node.right: 30 q.append(node.right) 31 if __name__ == "__main__": 32 root = creatTree() 33 new_Tree = treeCopy(root) 34 levelOrder(new_Tree)
六、树的实际应用-二叉搜索树
包括二叉树的搜索(search)和插入(insert)
分析:二叉树的搜索,类似与二分法的搜索,每次将要搜索的值与父结点比较,在父结点不为空以及父结点的值和要搜索的值不相等的情况下,比较父结点的值和要搜索的值大小关系,小于父结点,则将左结点取作父结点,否则将右结点取作父结点。对搜索进行改进可以有利于插入算法,改进就是在原来返回结点基础上返回该结点的父结点。
二叉树的插入,调用改进的搜索算法,先利用搜索算法检索二叉树中是否有这个值,如果有,那么不必插入,直接返回,停止操作;如果没有,那么就创建一个数值等于要插入数的结点,然后,如果这是一个空树,直接将该结点作为二叉树的根结点,如果不是空树,根据改进的搜索算法是返回值中的parent值,搜索返回值中node = None,但是parent返回的是最接近K值的一个结点,所以只需要比较K值和父结点数值的大小:K<父结点数值,将新结点插入到父结点左侧,否则插入到父结点右侧
python代码实现:
1 from TreeNode import TreeNode 2 from levelOrder import levelOrder,creatTree 3 class BinarySearchTree(): 4 def __init__(self): 5 self.root = None 6 #======================搜索算法===================== 7 def search(self,k): 8 node = self.root 9 while node != None and k != node.data: 10 if k < node.data: 11 node = node.left 12 else: 13 node = node.right 14 return node 15 #=====================改进的搜索算法,不止找到这个数所在的结点,也找到该结点的子结点================== 16 def _search(self,k): 17 parent = None 18 node = self.root 19 while node and k != node.data: 20 parent = node 21 if k < node.data: 22 node = node.left 23 else: 24 node = node.right 25 return node ,parent 26 #=====================插入算法,调用了改进的搜索算法================================================= 27 def insert(self,k): 28 node,parent = self._search(k) 29 if node: 30 return #如果node有值,也就意味着在原本的树的结构中含有要插入的这个值,直接返回,不必进行插入操作了 31 32 node = TreeNode(k) #创建一个新的结点,这个结点的数值为k,这个结点的左右结点都为空 33 if parent is None: 34 self.root = node # 考虑极端情况,假设是一个空树,每一项都是空,那么查找的时候查找的父结点自然也是空,这时直接将要插入的数作为这个树的根结点即可 35 # 在调用搜索函数查找的过程中,虽然没有找到和K匹配的结点,但是父结点就已经是和这个k值最接近的结点,只需要根据k值和父结点的大小就行比较,确定将新结点作为父结点的左结点还是右结点即可 36 elif k < parent.data: 37 parent.left = node 38 else: 39 parent.right = node 40 41 if __name__ == '__main__': 42 # root = creatTree() 43 bst = BinarySearchTree() #只是创建了一个二叉树类 44 bst.insert(10) 45 bst.insert(5) 46 bst.insert(15) 47 levelOrder(bst.root)