python 读取内存二叉树_Python 二叉树遍历的优雅写法

(本文代码放到了 自己的网站)

我们从二叉树的顺序存储说起。

我们知道,对于一个完全二叉树,做广度优先搜索的话,每个节点对应的序号是固定的。

例如,下面这个二叉树

这个二叉树和一个顺序存储相互可以唯一表示的:

[2, 1, 3, 0, 7, 9, 1]

给定二叉树,可以唯一确定这个顺序存储;反之亦然。

这么存储的好处是:寻址很快,例如,想知道二叉树第

行第

个节点的值,直接寻址

,复杂度 O(1)

插入、修改都挺快的,堆结构虽然是二叉树的原理,但实际上是存到顺序存储的

每个节点的孩子节点,也是立即可以确定的,例如,

号节点的两个孩子序号是

然后我们考虑一个非完全二叉树

我们用空值 None 来“填充”上面的二叉树,使之变成完全二叉树,也可以做类似的操作

也可以与一个顺序存储相互唯一表示:(没找到教科书上有命名,我称之为稀疏型顺序存储)

[2, 1, 3, 0, 7, 9, 1, 2, None, 1, 0, None, None,

8, 8, None, None, None, None, None, None, 7,

None, None, None, None, None, None, None,

None, None]

稀疏型顺序存储顾名思义,浪费了大量的存储空间。想做一下改进。

我们发现,修改一下映射规则:

None节点的子节点省略掉。

发现仍然实现 “这个二叉树和一个顺序存储相互可以唯一表示“ 的效果:(我称之为紧凑型顺序存储)

[2, 1, 3, 0, 7, 9, 1, 2, None, 1, 0, None, None, 8, 8, None, None, None, None, 7]

是不是短了很多!

前言完事儿了,下面是代码实现。

这段代码每一行都是反复修改的,兼顾优美、可扩展、可读性,是可以背下来的

我们用Python的类和对象来模拟每个节点

# Definition for a binary tree node.

class TreeNode(object):

def __init__(self, x):

self.val = x

self.left = None

self.right = None

def __repr__(self):

return 'Node val = {}'.format(str(self.val))

顺序存储转链式存储:

class Transform:

# 稀疏型顺序存储:二叉树的空节点也占一个位置,空节点的两个孩子(虽然实际不存在)也占一个位置

# 所以顺序表的第i个元素,其孩子节点序号必是 2 * i + 1, 2 * i + 2

# 紧凑型顺序存储:空节点占一个位置,但空节点的孩子不再占位置

def list2tree1_1(self, list_num, i=0):

# 顺序结构转稀疏型顺序存储,递归法

if i >= len(list_num):

return None

treenode = TreeNode(list_num[i])

treenode.left = self.list2tree1_1(list_num, 2 * i + 1)

treenode.right = self.list2tree1_1(list_num, 2 * i + 2)

return treenode

def list2tree1_2(self):

# 顺序结构转稀疏型顺序存储,迭代法

if not nums:

return None

nodes = [None if val is None else TreeNode(val) for val in nums]

kids = nodes[::-1]

root = kids.pop()

for node in nodes:

if node:

if kids: node.left = kids.pop()

if kids: node.right = kids.pop()

else:

if kids:

kids.pop()

if kids:

kids.pop()

return root

def list2tree2_2(self, nums):

# 顺序结构转紧凑顺序存储,迭代法

if not nums:

return None

nodes = [None if val is None else TreeNode(val) for val in nums]

kids = nodes[::-1]

root = kids.pop()

for node in nodes:

if node:

if kids: node.left = kids.pop()

if kids: node.right = kids.pop()

return root

链式存储上的遍历

class Travel:

# 注意,三个 DFS 算法中,空节点处理为[],而不是[None]

# 有些场景还是需要空节点返回[None]的,灵活去改动

def InOrder(self, root): # LDR

return [] if (root is None) else self.InOrder(root.left) + [root.val] + self.InOrder(root.right)

def PreOrder(self, root): # DLR

return [] if (root is None) else [root.val] + self.PreOrder(root.left) + self.PreOrder(root.right)

def PostOrder(self, root): # LRD

return [] if (root is None) else self.PostOrder(root.left) + self.PostOrder(root.right) + [root.val]

def level_order(self, root):

# BFS, tree转稀疏型顺序存储。

q, ret = [root], []

while any(q):

ret.extend([node.val if node else None for node in q])

q = [child for node in q for child in [node.left if node else None, node.right if node else None]]

return ret

def level_order2(self, root):

# BFS, tree转紧凑型顺序存储。

q, ret = [root], []

while any(q):

ret.extend([node.val if node else None for node in q])

q = [child for node in q if node for child in [node.left, node.right]]

# 结尾的 None 无意义,清除掉

while ret[-1] is None:

ret.pop()

return ret

def level_order_nary(self, root):

# 针对N-ary Tree的方法,非常漂亮,前面几个 level_order 都是参考这个

# https://leetcode.com/problems/n-ary-tree-level-order-traversal/description/

q, ret = [root], []

while any(q):

ret.append([node.val for node in q])

q = [child for node in q for child in node.children if child]

return ret

def find_track(self, num, root, track_str=''):

'''

二叉树搜索

'''

track_str = track_str + str(root.val)

if root.val == num:

return track_str

if root.left is not None:

self.find_track(num, root.left, track_str + ' ->left-> ')

if root.right is not None:

self.find_track(num, root.right, track_str + ' ->right-> ')

上面的代码每一行我都反复斟酌过,优美、可扩展,用来备考杠杠的。

解释上面的代码:InOrder,PreOrder,PostOrder 三种深度优先搜索,各种一行实现。好好看看里面的逻辑,大多数这一块的算法题在这上面改吧改吧就能做出来,答案还惊艳。

level_order就是广度优先搜索,对应的结果其实就是前言介绍的“稀疏型顺序存储”,“紧凑型顺序存储”

二叉树很多算法有递归版本和迭代版本。递归版本往往更优雅,迭代版本往往更容易实现一些高级需求(不是必然的)。例如题目要求你在 level_order 的基础上加一个“当前层级”作为输出结果,怎么改?(增加两行即可:定义计数器、计数器自加,非常easy)

下面给个打印二叉树的示例,可以看到上面代码如何轻易修改满足新需求

class Draw:

def print_tree(self, root, total_width=36):

q, ret, level = [root], '', 0

while any(q):

nodes = [str(node.val) if node else ' ' for node in q]

col_width = int(total_width / len(nodes))

nodes = [node_name.center(col_width, ' ') for node_name in nodes]

ret += (''.join(nodes) + '\n')

q = [child for node in q for child in [node.left if node else None, node.right if node else None]]

level += 1

return ret

def print_tree_horizontal(self, root, i=0):

'''

打印二叉树,凹入表示法,相当于把树旋转90度看。算法原理根据 RDL

'''

tree_str = ''

if root.right:

tree_str += self.print_tree_horizontal(root.right, i + 1)

if root.val:

tree_str += (' ' * i + '-' * 3 + str(root.val) + '\n')

if root.left:

tree_str += self.print_tree_horizontal(root.left, i + 1)

return tree_str

def drawtree(self, root):

# 用 turtle 画 Tree

def height(root):

return 1 + max(height(root.left), height(root.right)) if root else -1

def jumpto(x, y):

t.penup()

t.goto(x, y)

t.pendown()

def draw(node, x, y, dx):

if node:

t.goto(x, y)

jumpto(x, y - 20)

t.write(node.val, align='center', font=('Arial', 12, 'normal'))

draw(node.left, x - dx, y - 60, dx / 2)

jumpto(x, y - 20)

draw(node.right, x + dx, y - 60, dx / 2)

import turtle

t = turtle.Turtle()

t.speed(0)

turtle.delay(0)

h = height(root)

jumpto(0, 30 * h)

draw(root, 0, 30 * h, 40 * h)

t.hideturtle()

turtle.mainloop()

这里使用了3种打印方式:

transform = Transform()

travel = Travel()

draw = Draw()

nums = [2, 1, 3, 0, 7, 9, 1, 2, None, 1, 0, None, None, 8, 8, None, None, None, None, 7]

root = transform.list2tree2_2(nums)

bfs_res1 = travel.level_order(root)

bfs_res2 = travel.level_order2(root)

print(bfs_res1)

print(bfs_res2)

上面的代码从顺序结构构建一个二叉树。

然后又把这个二叉树转换成 稀疏型、紧凑型 顺序存储结构

打印结果:

[2, 1, 3, 0, 7, 9, 1, 2, None, 1, 0, None, None, 8, 8, None, None, None, None, None, None, 7, None, None, None, None, None, None, None, None, None]

[2, 1, 3, 0, 7, 9, 1, 2, None, 1, 0, None, None, 8, 8, None, None, None, None, 7]

看一下画二叉树:

第一种画法,用level_order 改编而来

draw1 = draw.print_tree(root)

print(draw1)

结果:

2

1 3

0 7 9 1

2 1 0 8 8

7

第二种画法,用深度优先搜索改编而来

draw2 = draw.print_tree_horizontal(root)

print(draw2)

结果:(相当于把树横过来)

---8

---1

---8

---3

---9

---2

---7

---7

---1

---1

---2

第三种(深度优先搜索)

draw.drawtree(root)

输出:

(本文代码放到了 自己的网站)

思考题:如果要求层次遍历返回形如下面的结果,如何修改代码(只要改一个单词)

[

[2],

[1,3],

[0,7,9,1],

[2,1,0,8,8],

[7]

]

2. level_order 稍微修改一下代码,可以计算树的最大/最小深度,试一试吧

3. level_order 稍微修改一下,可以计算树的最大宽度,试一试吧

4. LeetCode 经典题目:给定一个树,镜像翻转这个树。遍历算法稍微修改一下即可,试一试吧

4. 传统的“深度优先搜索”和“广度优先搜索”分别使用了 stack 和 queue 实现的,这里用了Python 强大的列表表达式实现的。想一下不用列表表达式如何实现?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值