最近陆陆续续在刷树结构这类的问题,非线性结构的问题一直是软肋,尤其是这类问题大量使用递归,对于我这种混了好几年的小白来说不容易理解,因此针对Leetcode中树这一类问题进行了下总结,温故而知新嘛(之前学的都还给老师了…),本文主要讲基本操作的编码实现,同时也希望各位大神批评指正。
二叉树的基本概念具体可以参考:度娘带你走进二叉树
二叉树结构
class TreeNode:
def __init__(self, value = None):
self.value = value
self.left = None
self.right = None
leetcode中给出了节点类的定义,方便我们在编码的过程中确认变量名的使用问题,但是并没有给出创建的过程,在leetcode环境中,大多是传入一个列表,通过列表的层次顺序进行自上到下的构建。因此在本地IDE中进行调试的过程中,需要先编写构造规则再编写相关逻辑。
二叉树的构建
二叉树的递归层次构造
def createBiTree(self, root, lis, index):
if index < len(lis):
if lis[index] is None:
return None
root = TreeNode(lis[index])
#列表中索引为index的值,当它作为父节点时,子节点位于原列表索引为2*index+1
#和2*index+2处
root.left = root.createBiTree(root.left, lis, 2*index + 1)
root.right = root.createBiTree(root.right, lis, 2*index + 2)
return root
return root
传入的参数是一个root节点(空值,初始化树),给定列表,和索引值(从列表的第一个元素开始,初始为0)。
二叉树的非递归层次构造
def createBiTree(self,lis):
if len(lis) == 0:
return None
queue = []
#根节点入队列
root = TreeNode(lis.pop(0))
queue.append(root)
while len(lis) > 0:
#temp为下一层的节点
temp = []
for i in range(0,len(queue)):
#本层中的节点依次出队
node = queue[i]
#判断本层节点左右孩子的情况
if len(lis) != 0:
#从原始列表中弹出第一个元素(从左到右)
left = lis.pop(0)
if left is not None:
node.left = TreeNode(left)
#不为空时,将构造的节点存入temp中
temp.append(node.left)
if len(lis) != 0:
right = lis.pop(0)
if right is not None:
node.right = TreeNode(right)
temp.append(node.right)
#遍历完本层节点后,将下一层节点作为下一次遍历的对象
queue = temp
return root
非递归构造的传入参数为所需构造的列表,借用队列这一逻辑结构辅助完成(python中创建栈和队列的方式如此方便,有啥理由不爱上她!)
上述的构造只针对在进行leetcode相关二叉树问题线下调试的环节,下面说一下前序中序后序和层次遍历这些基础操作。
二叉树的遍历
二叉树的遍历在百度google铺天盖地的都是,这类基本操作难度不大,但是是二叉树的基础。
前序递归遍历二叉树
def preorderTraversal(self,root):
#由于返回值是以列表的形式,因此利用函数嵌套的方式
result = []
def impl(root):
if root is None:
return None
result.append(root.value)
root.left = impl(root.left)
root.right = impl(root.right)
root = impl(root)
return result
前序遍历的遍历顺序是根节点—>左子—>右子,如图是一棵二叉树(引用自百度百科):
在本地构建时先传入列表[‘F’,’C’,’E’,’A’,’D’,’H’,’G’,None,None,’B’,None,None,None,’M’,None],None表示节点为空,进行树的构建,之后从根节点开始,依次遍历每个被访问节点的左右子,直至子节点为空。前序遍历的顺序是[‘F’, ‘C’, ‘A’, ‘D’, ‘B’, ‘E’, ‘H’, ‘G’, ‘M’]。
前序非递归遍历二叉树
def preorderTraversal(self,root):
result = []
stack = []
if root is None:
return result
stack.append(root)
while len(stack) > 0:
node = stack.pop()
if node:
result.append(node.value)
stack.append(node.right)
stack.append(node.left)
return result
前序遍历的非递归方法用栈结构实现,基本思想是访问根节点后,先将其右子压栈(之所以这样做是在出栈时保证左子先出),若左子不为空,则沿左子依此访问(对于一个父节点来说,保证其左子树的所有节点都已经遍历过,再开始将右子树的根节点出栈)。代码的问题是不管是否为空都有压栈的操作,会造成频繁进栈出栈的问题,因此可以先判断是否为空再考虑是否压栈。
中序递归遍历二叉树
def inorderTraversal(self, root):
result = []
def impl(root):
if root is None:
return None
root.left = impl(root.left)
result.append(root.value)
root.right = impl(root.right)
root = impl(root)
return result
中序遍历的遍历顺序是左子—>根节点—>右子,实现方法和前序遍历相似,只是先递归遍历左子直至叶子节点。就前面的图来说,其遍历顺序为[‘A’, ‘C’, ‘B’, ‘D’, ‘F’, ‘H’, ‘E’, ‘M’, ‘G’]。
中序非递归遍历二叉树
def inorderTraversal(self,root):
result = []
stack = []
if root is None:
return result
node = root
while node or len(stack) > 0:
if node:
stack.append(node)
node = node.left
else:
node = stack.pop()
result.append(node.value)
node = node.right
return result
由于中序遍历时第一个访问的节点应该是根节点左子树的最左节点,因此中序非递归遍历方法的基本思想是先沿着每个节点的左子进行搜索,同时进行压栈操作,直到当前节点为根节点左子树的最左节点(此时其左子树为空),将其存入结果列表中,然后再访问其右子。
后序递归遍历二叉树
def postorderTraversal(self, root):
result = []
def impl(root):
if root is None:
return None
root.left = impl(root.left)
root.right = impl(root.right)
result.append(root.value)
root = impl(root)
return result
后序遍历的遍历顺序是左子—>右子—>根节点,由此可见,二叉树的前中后遍历是从父节点的角度出发,先访问父节点则为前序,第二顺位访问父节点则为中序,最后被访问则为后序。对于前面的图来说,其后序遍历的顺序是[‘A’, ‘B’, ‘D’, ‘C’, ‘H’, ‘M’, ‘G’, ‘E’, ‘F’]。
后序非递归遍历二叉树
def postorderTraversal(self, root):
stack = []
result = []
if root is None:
return result
stack.append(root)
while len(stack) >0 :
node = stack.pop()
if node:
stack.append(node.left)
stack.append(node.right)
result.append(node.value)
return result[::-1]
非递归遍历的代码和前序遍历的非递归代码乍一看是很相似的,但过程有些难以理解,可以用表格的形式来简单理解下步骤,例如:
后面的还有不少,有兴趣的自己可以试验下,加深理解。迭代到最后会发现,while结束时,result中的值恰好是[‘F’,’E’,’G’,’M’,’H’,’C’,’D’,’B’,’A’]。我们知道在一个二叉树的每一个子结构中,后序遍历都是按照左右父的顺序进行的,利用stack可以使得在每一个子结构中,左子率先进栈,待某个父节点右子树全部遍历完后才开始遍历左子树。因此你会发现每一个子结构(这里说的子结构是指只有父节点,左子节点和右子节点的一般二叉树结构),在进入结果列表中时都是按照父节点、右节点、左节点进入,而进入stack辅助栈时是从左往右的,因此最后只要将result反转一下即可。
层次遍历二叉树
层次遍历用非递归的形式比较多,主要是层次遍历的思想类似BFS,递归并不太适合,这里只说一下非递归的形式。
def levelorderTraversal(self, root):
result = []
queue = []
if root is None:
return result
queue.append(root)
result.append([root.value])
#第一个while中的queue是变化的,保证是否还有节点未被遍历到
while len(queue) > 0:
n = len(queue)
temp = []
#这里的n表示的是每一层的节点
while n:
node = queue.pop(0)
if node.left:
queue.append(node.left)
temp.append(node.left.value)
if node.right:
queue.append(node.right)
temp.append(node.right.value)
n -= 1
if len(temp) > 0:
result.append(temp)
return result
到这里只是简单的说了下有关二叉树的构建遍历等问题,有了这些基本实现,之后会继续针对leetcode上的其他二叉树题目进行总结整理~