13.二叉树基础
二叉树基础(上):什么样的二叉树适合用数组来存储?
树,总共包含4节内容。具体如下:
1.树、二叉树
2.二叉查找树
3.平衡二叉树、红黑树
4.递归树
一、树
1.树的常用概念
根节点、叶子节点、父节点、子节点、兄弟节点,还有节点的高度、深度以及层数,树的高度。
2.概念解释
节点:树中的每个元素称为节点
父子关系:相邻两节点的连线,称为父子关系
根节点:没有父节点的节点
叶子节点:没有子节点的节点
父节点:指向子节点的节点
子节点:被父节点指向的节点
兄弟节点:具有相同父节点的多个节点称为兄弟节点关系
节点的高度:节点到叶子节点的最长路径所包含的边数
节点的深度:根节点到节点的路径所包含的边数
节点的层数:节点的深度+1(根节点的层数是1)
树的高度:等于根节点的高度
二、二叉树(最常用)
1.概念
①什么是二叉树?
每个节点最多只有2个子节点的树,这两个节点分别是左子节点和右子节点。
②什么是满二叉树?
有一种二叉树,除了叶子节点外,每个节点都有左右两个子节点,这种二叉树叫做满二叉树。
③什么是完全二叉树?
有一种二叉树,叶子节点都在最底下两层,最后一层叶子节都靠左排列,并且除了最后一层,其他层的节点个数都要达到最大,这种二叉树叫做完全二叉树。
2.完全二叉树的存储
①链式存储
每个节点由3个字段,其中一个存储数据,另外两个是指向左右子节点的指针。我们只要拎住根节点,就可以通过左右子节点的指针,把整棵树都串起来。这种存储方式比较常用,大部分二叉树代码都是通过这种方式实现的。
②顺序存储
用数组来存储,对于完全二叉树,如果节点X存储在数组中的下标为i,那么它的左子节点的存储下标为2*i,右子节点的下标为2*i+1,反过来,下标i/2位置存储的就是该节点的父节点。注意,根节点存储在下标为1的位置。完全二叉树用数组来存储时最省内存的方式。


3.二叉树的遍历(面试常见)
①前序遍历:对于树中的任意节点来说,先打印这个节点,然后再打印它的左子树,最后打印它的右子树。
②中序遍历:对于树中的任意节点来说,先打印它的左子树,然后再打印它的本身,最后打印它的右子树。
③后序遍历:对于树中的任意节点来说,先打印它的左子树,然后再打印它的右子树,最后打印它本身。
前序遍历的递推公式:
preOrder(r) = print r->preOrder(r->left)->preOrder(r->right)
中序遍历的递推公式:
inOrder(r) = inOrder(r->left)->print r->inOrder(r->right)
后序遍历的递推公式:
postOrder(r) = postOrder(r->left)->postOrder(r->right)->print r
时间复杂度:3种遍历方式中,每个节点最多会被访问2次,所以时间复杂度是O(n)。
class Node(object): def __init__(self,value=None,left=None,right=None): self.value=value self.left=left self.right=right def preTraverse(root): if root == None: return print(root.value,end=" ") preTraverse(root.left) preTraverse(root.right) def midTraverse(root): if root == None: return midTraverse(root.left) print(root.value,end=" ") midTraverse(root.right) def afterTraverse(root): if root == None: return afterTraverse(root.left) afterTraverse(root.right) print(root.value,end=" ") if __name__ == "__main__": root = Node("A",Node("B",right=Node("F",right=Node("C"))),Node("G",Node("D",Node("E")))) preTraverse(root) print() midTraverse(root) print() afterTraverse(root)
三、思考
1.二叉树有哪几种存储方式?什么样的二叉树适合用数组来存储?
2.给定一组数据,比如1,3,5,6,9,10.你来算算,可以构建出多少种不同的二叉树?
3.我们讲了三种二叉树的遍历方式,前、中、后序。实际上,还有另一种遍历方式,也就是按层遍历,你知道如何实现吗?
4.如何用循环实现二叉树的遍历?
二叉树基础(下):有了如此高效的散列表,为什么还需要二叉树?
二叉查找(搜索)树支持动态数据集合的快速插入、删除、查找操作。散列表也是支持这些操作的,并且散列表的这些操作比二叉查找树更高效,时间复杂度是 O(1)。既然有了这么高效的散列表,使用二叉树的地方是不是都可以替换成散列表呢?有没有哪些地方是散列表做不了,必须要用二叉树来做的呢?
二叉查找树要求,在树中的任意一个节点,其左子树中的每个节点的值,都要小于这个节点的值,而右子树节点的值都大于这个节点的值。

class TreeNode(object): def __init__(self,val,left=None,right=None): self.val=val self.left=left self.right=right class BST(object): def __init__(self,node_list): self.root =TreeNode(node_list[0]) for data in node_list[1:]: self.insert(self.root,data) def query(self,root,val):#遍历root,并返回遍历结果 if root == None:#递归停止的条件 return if root.val is val: return 1 if root.val >val: return self.query(root.left,val)#遍历root.left,并返回遍历结果 else: return self.query(root.right,val) def insert(self,root,val):#对整个root完成插入 if root == None: root = TreeNode(val) else: if root.val>val: root.left=self.insert(root.left,val)#对整个root.left完成插入,需要root.left从新指向 elif root.val<val: root.right = self.insert(root.right,val) return root#对整个root完成插入,需要root从新指向 def findMax(self,root): if root.right !=None: return self.findMax(root.right) else: return root def findMin(self,root): if root.left !=None: return self.findMin(root.left) else: return root def delete(self,root,val): """ 关于二叉查找树的删除操作,还有个非常简单、取巧的方法,就是单纯将要删除的节点标记为“已删除”,但是并不真正从树中将这个节 点去掉。这样原本删除的节点还需要存储在内存中,比较浪费内存空间,但是删除操作就变得简单了很多。而且,这种处理方法也并没有 增加插入、查找操作代码实现的难度。 """ if root == None: return if root.val<val: root.right = self.delete(root.right,val) elif root.val>val: root.left =self.delete(root.left,val) else: if root.left !=None and root.right!=None: temp = self.findMin(root.right) root.val = temp.val root.right = self.delete(root.right,temp.val) else: if root.left == None: root = root.right elif root.right == None: root = root.left return root def midTraverse(self,root): if root != None: self.midTraverse(root.left) print(root.val,end=" ") self.midTraverse(root.right) if __name__ == "__main__": a = [49, 38, 65, 97, 60, 76, 13, 27, 5, 1] bst = BST(a) # 创建二叉查找树 bst.midTraverse(bst.root) # 中序遍历 print() print(bst.findMin(bst.root).val) bst.delete(bst.root,97) bst.midTraverse(bst.root) # 中序遍历 bst.insert(bst.root, 97) print() bst.midTraverse(bst.root) # 中序遍历
二叉查找树的时间复杂度分析

不管操作是插入、删除还是查找,时间复杂度其实都跟树的高度成正比,也就是 O(height)。
对于完全二叉查找树,若节点个数为n,那么n满足:
n >= 1+2+4+8+...+2^(L-2)+1
n <= 1+2+4+8+...+2^(L-2)+2^(L-1)
也就是说,完全二叉树的高度小于等于 log2n。


class TreeNode: def __init__(self, x): self.left = None self.right = None self.val = x class Solution(object): def __init__(self): self.listHead = None self.listTail = None def convert(self,root): """ 将二叉树转换为有序双向链表 :param root: :return: """ if root == None: return self.convert(root.left) if self.listHead == None: self.listHead = root self.listTail = root else: self.listTail.right = root root.left = self.listTail self.listTail = root self.convert(root.right) return self.listHead def printList(self,head): """ 获得链表的正向序和反向序 :param head: :return: """ while head.right: print(head.val,end=" ") head = head.right print(head.val) while head: print(head.val,end=" ") head =head.left print() def get_BST_with_pre_mid(self,pre,mid): """ 给定二叉树的前序遍历和中序遍历,获得该二叉树 :param pre: :param mid: :return: """ if len(pre) ==0 or len(mid)==0: return None root_c = TreeNode(pre[0]) for order,item in enumerate(mid): if root_c.val == item: root_c.left = self.get_BST_with_pre_mid(pre[1:order+1],mid[:order]) root_c.right = self.get_BST_with_pre_mid(pre[order+1:],mid[order+1:]) return root_c if __name__ == '__main__': solution = Solution() preorder_seq = [4,2,1,3,6,5,7] middleorder_seq = [1,2,3,4,5,6,7] root = solution.get_BST_with_pre_mid(preorder_seq, middleorder_seq) head = solution.convert(root) solution.printList(head)
浙公网安备 33010602011771号