与图论中的“度”不同,树的度是如下定义的:有根树T中,结点x的子女数目称为x的度。也就是:在树中,结点有几个分叉,度就是几。
二叉树的一些基本性质:
!!! 如果二叉树中除去最后一层节点为满二叉树,且最后一层的结点依次从左到右分布,则此二叉树被称为完全二叉树。
二叉树的顺序存储结构: 使用数组存储二叉树,只适用于完全二叉树
如果我们想顺序存储普通二叉树,需要提前将普通二叉树转化为完全二叉树,只需加入一些dummy node将普通的二叉树补全即可
从顺序表中还原完全二叉树也很简单。 我们知道,完全二叉树具有这样的性质,将树中节点按照层次并从左到右依次标号(1,2,3,…), 若节点
i 有左右孩子,则其左孩子节点为 2i,右孩子节点为 2i+1。
二叉树的链式存储结构: 采用链式存储二叉树时,其节点结构由 3 部分构成:
指向左孩子节点的指针(Lchild);
节点存储的数据(data);
指向右孩子节点的指针(Rchild);
class TreeNode:
def __init__(self,val,left=None,right=None) -> None:
self.val=val
self.left=left
self.right=right
二叉树的遍历:数据结构规定,在遍历过程中,先遍历左子树,再遍历右子树;故得出(深度遍历上的)三种遍历方式
!!!对于二叉树,有深度遍历和广度遍历,深度遍历:前序、中序、后序三种;广度遍历即我们平常所说的层次遍历。
四种主要的遍历思想为:主要看根节点是第几个访问
(1)前序遍历:根结点 —> 左子树 —> 右子树
(2)中序遍历:左子树—> 根结点 —> 右子树
(3)后序遍历:左子树 —> 右子树 —> 根结点
(4)层次遍历:只需按层次遍历即可
def preOrder(self, root: TreeNode):
if not root:
return None
print(root.val)
self.preOrder(root.left)
self.preOrder(root.right)
def inOrder(self, root:TreeNode):
if not root:
return None
self.inOrder(root.left)
print(root.val)
self.inOrder(root.right)
def postOrder(self, root:TreeNode):
if not root:
return None
self.postOrder(root.left)
self.postOrder(root.right)
print(root.val)
非递归解法(参阅文章:二叉树非递归解)即迭代解法:本质上是在模拟递归,因为在递归的过程中使用了系统栈,所以在迭代的解法中常用Stack来模拟系统栈。
注意代码中栈的先进后出顺序,输出出来的就是倒序打印。
'''
算法1:先序遍历非递归访问,使用栈即可实现。先序遍历的非递归访问在所有的遍历中算是最简单的了。
主要思想就是先将根结点压入栈,然后根结点出栈并访问根结点,而后依次将根结点的右孩子、左孩子入栈,直到栈为空为止。
'''
def preOrder(root: TreeNode):
if not root:
return None
stack=[]
stack.append(root)
while stack:
ans=stack[-1]
stack.pop()
print(ans.val)
# 注意栈是先进后出,所以遍历时先访问左子树再访问右子树,因此压入栈时先压右子树再压左子树
if ans.right:
stack.append(ans.right)
if ans.left:
stack.append(ans.left)
'''
'''
算法2:先序遍历的非递归算法另一算法,也是用的栈,只是稍微复杂点,当左子树遍历完后,需要回溯并遍历右子树。
'''
def preOrder(root: TreeNode):
stack=[]
while root or stack:
if root:
print(root.val) # 访问结点并入栈
stack.append(root)
root=root.left # 一直遍历左子树,直到左子树为空
else:
root=stack[-1] # 回溯父亲结点
stack.pop()
root=root.right # 访问右子树
'''
中序遍历非递归算法也是采用栈实现,与上面的先序遍历算法2类似,只是访问根结点的时机不同。
'''
def inOrder(root: TreeNode):
stack=[]
while root or stack:
if root:
stack.append(root)
root=root.left
else:
root=stack[-1] # 将所有的左子树都访问完压入栈后,再访问根结点
print(root.val)
stack.pop()
root=root.right # 访问右子树
'''
后序遍历的非递归算法较复杂,使用一个栈可以实现,但是过程很繁琐,这里可以巧妙的用两个栈来实现后序遍历的非递归算法。
注意到后序遍历可以看作是下面遍历的逆过程:即先遍历某个结点,然后遍历其右孩子,然后遍历其左孩子。
上面这个过程逆过来就是后序遍历。算法步骤如下:
1.Push根结点到第一个栈s中。
2.从第一个栈s中Pop出一个结点,并将其Push到第二个栈output中。
3.然后Push结点的左孩子和右孩子到第一个栈s中。
4.重复过程2和3直到栈s为空。
5.完成后,所有结点已经Push到栈output中,且按照后序遍历的顺序存放,直接全部Pop出来即是二叉树后序遍历结果。
'''
def postOrder(root: TreeNode):
if not root:
return
stack=[]
output=[]
stack.append(root)
while stack:
cur=stack[-1]
output.append(cur)
stack.pop()
if cur.left:
stack.append(cur.left)
if cur.right:
stack.append(cur.right)
while output:
print(output[-1].val)
output.pop()
参阅文章:二叉树遍历的非递归解
如果不考虑分层换行打印,则用一个队列可以很容易的通过非递归实现层序遍历。但是要每打印一层换一行,就显得稍微复杂了一点。可以有两种方法,第一种使用两个队列,代码很简练,第二种方法则是使用一个队列,代码稍显复杂。
'''
第一个队列currentLevel用于存储当前层的结点,第二个队列nextLevel用于存储下一层的结点。
当前层currentLevel为空时,表示这一层已经遍历完成,可以打印换行符了。
然后将第一个空的队列currentLevel与队列nextLevel交换,然后重复该过程直到结束。这个算法比较好理解。
'''
# from queue import Queue
import queue
# Python中的队列模块queue详解见链接:https://www.cnblogs.com/itogo/p/5635629.html或https://blog.csdn.net/brucewong0516/article/details/84025027
# 队列类似于一条管道,元素先进先出,进put(arg),取get( )。put(item,[]): 将item放入队列中;get(): 从队列中移除并返回一个数据
# 需要注意的是:队列都是在内存中操作,进程退出,队列清空,另外,队列也是一个阻塞的形态。
def levelOrder1(root: TreeNode):
if not root:
return
currentLevel=queue.Queue()
nextLevel=queue.Queue()
currentLevel.put(root)
while currentLevel:
cur=currentLevel.get()
if cur: # currentLevel中只要有元素就一直访问
print(cur.val)
# 将cur结点的孩子们加入到下一层的队列中
nextLevel.put(cur.left)
nextLevel.put(cur.right)
if currentLevel.empty(): # 当前层遍历完了,转移到下一层
swap(currentLevel,nextLevel)
def swap(cur:queue, next:queue): # 将next队列中的node,全部put到cur队列中
while not next.Empty():
node=next.get()
cur.put(node)
# 只使用一个队列的话,需要额外的两个变量来保存当前层结点数目以及下一层的结点数目。
def levelOrder2(root:TreeNode):
if not root:
return
q=queue.Queue()
curNodeNumber=1 # 保存当前层结点数目
nextNodeNumber=0 # 保存下一层结点数目
q.put(root)
while not q.empty():
cur=q.get()
curNodeNumber-=1
if cur:
print(cur.val)
q.put(cur.left)
q.put(cur.right)
nextNodeNumber+=2
if curNodeNumber==0:
curNodeNumber=nextNodeNumber
nextNodeNumber=0