数据结构-树

树是一种一对多的数据结构,每一个数据有一个前置,但是可能有多个后置。如下图:

数据结构:树之遍历_数据结构

数据按照某种次序放在树上,会大大提升查找时的性能。比如对只有两个分支的树,第一层1个数据,第二层2个数据,第三层四个数据,第N层2n-1个数据。一千个数据要十层,一百万数据要20层。
这个对数级的查找,用有序的序列不是也一样吗?对。不过,序列的插入删除性能很低,因为要逐个移动数据。而用树形结构,插入删除性能也不错。


树之遍历

我们先手工构建一棵二叉树,然后在树上遍历所有元素。
先看树的结构怎么表达,一棵树,由节点组成,一个节点,有数据和左子树和右子树。我们这么表示:

class TreeNode(object):
    def __init__(self,data=None):
        self.left = None
        self.right = None
        self.data = data

left是左边的子树,right是右边的子树。可见树是一种递归型的定义。
对于如下的一棵树,我们可以这么构造:

数据结构:树之遍历_数据结构_02

root = TreeNode("a")
root.left = TreeNode("b")
root.right = TreeNode("c")
root.left.left = TreeNode("d")
root.left.right = TreeNode("e")
root.right.right = TreeNode("f")

现在我们手头有了一颗树,我们再看怎么遍历(Traversal)。
我们根据树的递归定义,从a开始,接下来是左树b,然后对b同样操作,到d,再到e,左树完成后,接着数右边的子树c,然后对c同样操作,直到f。
这是一种深度优先的搜索,分成本节点N,左子树L,右子树三部分R。所以遍历的次序可以组合成NLR,LNR和LRN三种。分别称为前序遍历、中序遍历和后序遍历(前中后是指本节点的次序)。
我们试一下下面的代码:

def preorder(node):
    if node is None:
        return
    print(node.data)
    preorder(node.left)
    preorder(node.right)
def inorder(node):
    if node is None:
        return
    inorder(node.left)
    print(node.data)
    inorder(node.right)
def postorder(node):
    if node is None:
        return
    postorder(node.left)
    postorder(node.right)
    print(node.data)

print("------preorder--------")    
preorder(root)
print("------inorder--------") 
inorder(root)
print("------postorder--------") 
postorder(root)

运行结果:

------preorder--------
a b d e c f
------inorder--------
d b e a c f
------postorder--------
d e b f c a

你们自己对着这棵树按照这个次序走一遍直观了解前中后序。我在这里只演示前序遍历。核心代码为下面几行,是递归调用:

    print(node.data)
    preorder(node.left)
    preorder(node.right)

调用时,最先传入的是根节点a。程序先打印本节点,我们用红心标识它走过了,如图:

数据结构:树之遍历_数据结构_03

接下来,对a的左树进行递归。程序执行preorder(node.left),传入的是节点b。程序先打印本节点,我们用红心标识它走过了,如图:

数据结构:树之遍历_数据结构_04

接下来,对b的左树进行递归。程序执行preorder(node.left),传入的是节点d。程序先打印本节点,我们用红心标识它走过了,如图:

数据结构:树之遍历_数据结构_05

接下来,对d的左树进行递归。程序执行preorder(node.left),传入的是None,返回。然后对d的右树进行递归。程序再执行preorder(node.right),传入的是None,返回。
这次返回到上层递归,回到了b节点。对b的右树进行递归。程序执行preorder(node.right),传入的是节点e。程序先打印本节点,我们用红心标识它走过了,如图:

数据结构:树之遍历_数据结构_06

接下来,对e的左树进行递归。程序执行preorder(node.left),传入的是None,返回。然后对e的右树进行递归。程序再执行preorder(node.right),传入的是None,返回。
这次返回到上层递归,回到了b节点。这个时候,b节点的左右树都处理完了。返回。
这次返回的上层递归是回到了a节点。对a的右树进行递归。程序执行preorder(node.right),传入的是节点c。程序先打印本节点,我们用红心标识它走过了,如图:

数据结构:树之遍历_数据结构_07

接下来,对c的左树进行递归。程序执行preorder(node.left),传入的是None,返回。然后对c的右树进行递归。程序再执行preorder(node.right),传入的是f。程序先打印本节点,我们用红心标识它走过了,如图:

数据结构:树之遍历_数据结构_08

接下来,对f的左树进行递归。程序执行preorder(node.left),传入的是None,返回。然后对f的右树进行递归。程序再执行preorder(node.right),传入的是None,返回。
这次返回到上层递归,回到了c节点。这个时候,c节点的左右树都处理完了。返回。
这次返回到更上层的递归,回到了a节点。这个时候,a节点的左右树都处理完了。返回。
程序结束。
按照刚才的遍历路径,打印出a b d e c f。

还可以按照广度优先来遍历,从a开始,接下来是a的下一层b和c,这样第二层的节点走完了,就对b和c同样操作。这个程序不用到递归,而是用到一个队列,队列初始为空,第一步把树的根节点a放进队列里面,然后对整个队列里面的逐个元素执行循环:
从队列里面弹出第一个元素,打印,如果这个元素的节点有左子树,则把左子树的根节点放进队列末尾,再看如果这个元素的节点有右子树,则把右子树的根节点放进队列末尾。
一步步演示如下:

第一步放树根,也就是a节点,队列为:
a
弹出第一个元素,为节点a。这时队列为空。
打印a。然后看到a有左树也有右树,所以分别放进队列,如下:
b    c
弹出第一个元素,为节点b。这时队列里面还有一个c节点。
打印b。然后看到b有左树也有右树,所以分别放进队列,如下:
c    d   e
弹出第一个元素,为节点c。这时队列里面还有两个节点d和e。
打印c。然后看到c没有左树但是有右树,所以把右树放进队列,如下:
d    e   f
弹出第一个元素,为节点d。这时队列里面还有两个节点e和f。
打印d。然后看到d没有左树也没有右树,什么也不做,如下:
e    f
弹出第一个元素,为节点e。这时队列里面还有一个节点f。
打印e。然后看到e没有左树也没有右树,什么也不做,如下:
f
弹出第一个元素,为节点f。这时队列为空。
打印e。然后看到e没有左树也没有右树,什么也不做。
队列空了,程序结束。

代码如下:

def bft(node):
    if node is None:
        return
    q=[]
    q.append(node)
    while len(q)>0:
        node = q.pop(0)
        print(node.data)
        if not node.left is None:
            q.append(node.left)
        if not node.right is None:
            q.append(node.right)
print("------bft--------") 
bft(root)

运行结果为:

------bft--------
a b c d e f