(Python3)数据结构——10.二叉树的原理及实现

前言

  • 有Python基础
  • 有数据结构基础更佳

原理

  • 先了解一下基本的概念:
  1. 什么是树
由一个或多个(n≥0)结点组成的有限集合T,有且仅有一个结点称为根(root),
当n>1时,其余的结点分为m(m≥0)个互不相交的有限集合T1,T2,…,Tm。
每个集合本身又是棵树,被称作这个根的子树 。

看起来是很抽象吧。那用比较通俗的话来阐述下。
树这种数据结构,亦是来源于生活,跟生活中的树一样,有根有叶子。这里的根是指根结点,叶子即为叶子结点(终端结点)。在进行后续的操作的时候一般是从根节点开始(因此要重视根结点)。借用某PPT。如下图。根结点只有一个,往下衍生出一大堆结点。树的一个节点可以有很多的分叉,二叉树最多只能有两个分叉。

在这里插入图片描述

在这里插入图片描述

  1. 二叉树:是n(n≥0)个结点的有限集合,由一个根结点以及两棵互不相交的、分别称为左子树和右子树的二叉树组成。
    在这里插入图片描述

  2. 其余的一些关于二叉树的概念请翻翻书本

  • 既然说是了解,那么说明侧重点不在概念讲解这里。
  • 关于二叉树,本文希望读者能知道二叉树大概长什么样,有根节点,左子树右子树。掌握二叉树的遍历的方法、插入的方法。(划重点)
  • 先讲下遍历。
  1. 层次遍历。很好理解,从根节点出发,一层一层地拨开它的心(一层一层地从左到右把树的节点元素打印出来)。层次遍历也称为广度优先遍历。就拿下面的这棵树举例,遍历结果就是从A到O。第一层只有A,之后看第二层,从左到右是BC。之后第三层,从左到右,DEFG。第四层,HIJKLMNO。

在这里插入图片描述

  1. 接下来讲下考试常考的三种遍历(当然,公司的笔试和面试应该也有),前序、中序、后序遍历。他们的遍历顺序的区别——前序遍历:根左右;中序:左根右;后序:左右根
    接下来举个例子说明下这个记法。比如有下面的这样的一棵树。根节点是A。

在这里插入图片描述

a) 根据“根左右”的原则可以得到它的前序遍历结果:ABDEC
先看整棵树,根节点是A,因此先记下A,左子树是B为代表的二叉树,右子树是C为代表的二叉树。“根”进行完了,接下来应该访问“左”。在B为代表的左子树中同样要遵守这个规则,依然是“根左右”,在这个二叉树中,B是根节点,D是左子树,E是右子树。因此根据“根左右”,可以得到BDE,记下这个结果。最大的这棵树的根节点访问完了,左子树也访问完了,那么接下来应该是访问右子树了,此时右子树中只有一个C元素,因此记下C。根据这个过程得到的结果是ABDEC。
b) 根据“左根右”的原则可以得到它的中序遍历结果:DBEAC
同样,分析过程类似上面的前序遍历。中序遍历是“左根右”,先访问左子树,再访问根节点,最后再看右子树。因此在这个例子中,先访问B为代表的左子树,在B这棵树中依然遵守这个规则,“左根右”,先访问D,之后这棵树的根节点B,之后再轮到右子树E。记下这个结果DBE。之后最大的这棵树的左子树访问完了,应该轮到这颗大树的根节点了,因此记下A。之后是这颗大树的右子树了,记下C。得到最后的结果:DBEAC。
c) 根据“左右根”的原则可以得到它的后序遍历结果:DEBCA
同样,可以利用类似上面分析的思路,利用“左右根”的原则对树进行后序遍历。(读者自己尝试下哦)

实现

  • 既然是有根节点、叶子结点,那就说明我们需要跟链表一样去定义结点的class。但是不同于链表,树的结点不仅有item,还有指向左子树和右子树的指针(或者说链接域)。初始化的时候,结点的left和right是指向空的。就只是一个孤立的结点。
class Node(object):
    def __init__(self, item):
        self.item = item
        self.left = None
        self.right = None
  • 树的初始化:仅声明一个根节点即可
class Tree(object):
    def __init__(self):
        self.root = None
  • 定义一下树的一些方法(比如判空,虽然感觉好像也没什么用)
 def isEmpty(self):
        return self.root is None
  • 之后写下插入新的node的方法。类似于链表中的策略,传入一个item之后传到Node的构造函数,产生一个新的node,之后判断该挂在哪个地方。如果根节点是空的,那么它就是新的根节点。代码中用了一个list类型的q来存放结点。
    def addNode(self, item):
        node = Node(item)
        if self.isEmpty():
            self.root = node
            return
        q = [self.root]
        while q:
            nd = q.pop(0)		# 第一次的话这个nd就是根结点
            if nd.left is None:
                # 左孩子结点是空的那么就挂左边
                nd.left = node
                break
            else:
            	# 否则先加到q中
                q.append(nd.left)
            if nd.right is None:
            	# 右孩子结点是空的那么就挂右边
                nd.right = node
                break
            else:
                q.append(nd.right)
  • 一般会写遍历的方法来验证结果。根节点为空直接return,不然将其存放到list类型的q中,之后对其操作。这种方式是层次遍历。
    def travel(self):
        if self.root is None:
            return
        q = [self.root]
        while q:
            nd = q.pop(0)
            print(nd.item, end= ' ')		# 先打出节点的item
            # 看看左孩子有没有,有的话加到q,再看看右孩子,有的话也加进去
            if nd.left:
                q.append(nd.left)
            if nd.right:
                q.append(nd.right)
        print('')
  • 接下来实现下前序、中序和后序遍历,这里的代码好好体会
       def forward(self, root):
        if root is None:
            return
        print(root.item, end=' ')
        self.forward(root.left)
        self.forward(root.right)

    def middle(self, root):
        if root is None:
            return
        self.middle(root.left)
        print(root.item, end=' ')
        self.middle(root.right)

    def back(self, root):
        if root is None:
            return
        self.back(root.left)
        self.back(root.right)
        print(root.item, end=' ')
  • 之后就可以验证了,以下是完整代码
class Node(object):
    def __init__(self, item):
        self.item = item
        self.left = None
        self.right = None


class Tree(object):
    def __init__(self):
        self.root = None

    def isEmpty(self):
        return self.root is None

    def addNode(self, item):
        node = Node(item)
        if self.isEmpty():
            self.root = node
            return
        q = [self.root]
        while q:
            nd = q.pop(0)
            if nd.left is None:
                # 左孩子结点是空的那么就挂左边
                nd.left = node
                break
            else:
                q.append(nd.left)
            if nd.right is None:
                nd.right = node
                break
            else:
                q.append(nd.right)

    def travel(self):
        if self.root is None:
            return
        q = [self.root]
        while q:
            nd = q.pop(0)
            print(nd.item, end=' ')
            if nd.left:
                q.append(nd.left)
            if nd.right:
                q.append(nd.right)
        print('')

    def forward(self, root):
        if root is None:
            return
        print(root.item, end=' ')
        self.forward(root.left)
        self.forward(root.right)

    def middle(self, root):
        if root is None:
            return
        self.middle(root.left)
        print(root.item, end=' ')
        self.middle(root.right)

    def back(self, root):
        if root is None:
            return
        self.back(root.left)
        self.back(root.right)
        print(root.item, end=' ')


tree = Tree()
for i in range(5):
    tree.addNode(i)
print('层次遍历:', end=' ')
tree.travel()
print('前序遍历:', end=' ')
tree.forward(tree.root)
print('')
print('中序遍历:', end=' ')
tree.middle(tree.root)
print('')
print('后序遍历:', end=' ')
tree.back(tree.root)
  • 结果

在这里插入图片描述

  • 分析一下,上面代码中是用for循环往二叉树里面添加结点。那么这个添加后的树是什么样的呢,不知道树是什么样的还怎么验证对错?
  • 这里需要重新回去看上面的添加元素的函数,细细体会可以发现,没有结点的时候,第一个进来的结点(0的那个结点)会变成头结点。第二个元素(1)进来后会发现根节点的左孩子结点是空的,因此挂到左孩子的位置。第三个进来会发现左孩子的位置没了,右孩子的位置还空着,所以第三个进来的节点(2)就成为了根节点的右孩子。第四个节点(3)进来,发现根节点和根节点的左右孩子的位置都没了,此时左孩子和右孩子这两个节点已经被加入到q里面了,下一次while循环的时候,左孩子先弹出,这个节点也就成为了while循环里的nd,之后会发现他的左孩子是空的,因此加入的新节点挂在这个地方。最后一个元素进来的过程按照类似的方法分析,这个节点会挂在(1)这个节点的右孩子位置。因此,最后可以得出树的结构图,如下。

在这里插入图片描述

  • 可以发现,图中的遍历结果是和上面程序的结果是一样的
发布了28 篇原创文章 · 获赞 12 · 访问量 4121

猜你喜欢

转载自blog.csdn.net/sf9898/article/details/105007041