Python超全干货:【二叉树】基础知识大全

二叉树的概念:

二叉树是每个节点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)

二叉树的链式存储:

将二叉树的节点定义为一个对象,节点之间通过类似链表的链接方式来连接。

树的定义与基本术语

树型结构是一类重要的非线性数据结构,其中以树和二叉树最为常用,是以分支关系定义的层次结构。树结构在客观世界中广泛存在,如人类社会的族谱和各种社会组织机构;在计算机领域中也有广泛应用,如在编译程序中,可用树来表示源程序的语法结构;在数据库系统中,树型结构也是信息的重要组织形式之一;在机器学习中,决策树,随机森林,GBDT等是常见的树模型。树(Tree)是n(n>=0)个结点的有限集。在任意一棵树中:(1)有且仅有一个特定的称为根(Root)的节点;(2)当n>1时,其余节点可分为m(m>0)个互不相交的有限集,其中每一个集合本身又是一棵树,并且称为根的子树(SubTree)。

图1 树型结构

在图1,该树一共有13个节点,其中A是根,其余节点分成3个互不相交的子集:

T1={B,E,F,K,L},T2={C,G},T3={D,H,I,J,K};T1,T2,T3都是根A的子树,且本身也是一颗树。

例如T1,其根为B,其余节点分为两个互不相交的子集;T11={E,K,L},T12={F}。T11和T12都是B的子树。而在T11中E是根,{K}和{L}是E的两棵互不相交的子树,其本身又是只有一个根节点的树。

树的基本术语

树的结点包含一个数据元素及若干指向其子树的分支。

节点拥有的子树数量称为节点的度(Degree)。

在图1中,A的度为3,B的度为2,C的度为1,F的度为0。

度为0的结点称为叶子(Leaf)结点。

在图1中,K,L,F,G,M,I,J都是该树的叶子。度不为0的结点称为分支结点。

树的度是指树内个结点的度的最大值。

结点的子树的根称为该结点的孩子(Child),相应地,该结点称为孩子的双亲(Parent)

在图1,中,D是A的孩子,A是D的双亲。同一个双亲的孩子之间互称兄弟(Sibling)

H,I,J互为兄弟。结点的祖先是从根到该结点所经分支上的所有结点,M的祖先为A,D,H。

对应地,以某结点为根的子树中的任一结点都称为该结点的子孙。B的子孙为E,F,K,L。

树的层次(Level)是从根开始,根为第一层,根的孩子为第二层等。双亲在同一层的结点互为同兄弟。图1中,K,L,M互为堂兄弟。树中结点的最大层次称为树的深度(Depth)或高度,树的深度为4。

如果将树中结点的各子树看成从左到右是有次序的(即不能交换),则称该树为有序树,否则为无序树。

森林(Forest)是m(m>=0)棵互不相交的树的集合。对树中每个结点而言,其子树的集合即为森林。

在机器学习模型中,决策树为树型结构,而随机森林为森林,是由若干决策树组成的森林。

性质

性质1: 在二叉树的第i层上至多有2^(i-1)个结点(i>0)

性质2: 深度为k的二叉树至多有2^k - 1个结点(k>0)

性质3:对于任意一棵二叉树,如果其叶结点数为N0,而度数为2的结点总数为N2,则N0=N2+1;

性质4:具有n个结点的完全二叉树的深度为 log2(n+1)

性质5:对完全二叉树,若从上至下、从左至右编号,则编号为i 的结点,其左孩子编号必为2i,其右孩子编号必为2i+1;其双亲的编号必须为i/2(i=1 时为根,除外)

分类

1.完全二叉树——若设二叉树的高度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第h层有叶子结点,并且叶子结点都是从左到右依次排布。

2.满二叉树——除了叶结点外每一个结点都有左右子叶且叶子结点都处在最底层的二叉树。

二叉树Python实现

class Node(object): """节点类""" def __init__(self, elem=-1, lchild=None, rchild=None): self.elem = elem  # 自身 self.lchild = lchild  # 左孩子 self.rchild = rchild  # 右孩子class Tree(object): """树类""" def __init__(self, root=None): self.root = root def add(self, elem): node = Node(elem) if self.root == None: self.root = node else: queue = [] queue.append(self.root)  # 先将根节点添加到队列中 while queue:  # 遍历树 cur = queue.pop(0)  # 首先弹出了根节点 if cur.lchild == None:  # 如果没有做孩子的话 cur.lchild = node  # 添加node return elif cur.rchild == None:  # 如果没有有孩子的话 cur.rchild = node                   return else:  # 如果左右子树都不为空,加入队列继续判断 queue.append(cur.lchild) queue.append(cur.rchild)

二叉树的遍历:

常见的遍历方法有:先序遍历,中序遍历,后序遍历,层序遍历。这些遍历方法一般使用递归算法实现。

前序遍历:EACBDGF

前序遍历的操作定义为:若二叉树为空,为空操作;否则(1)访问根节点;(2)先序遍历左子树;(3)先序遍历右子树。

递归实现:

def preorder(root): if not root: return print(root.val) preorder(root.left) preorder(root.right)

迭代实现:

def preorder(root): stack = [root] while stack: s = stack.pop() if s: print(s.val) stack.append(s.right) stack.append(s.left)

中序遍历:ABCDEGF

中序遍历的操作定义为:若二叉树为空,为空操作;否则(1)中序遍历左子树;(2)访问根结点;(3)中序遍历右子树。

递归实现:

def inorder(root): if not root: return inorder(root.left) print(root.val) inorder(root.right)

迭代实现:

def inorder(root): stack = [] while stack or root: while root: stack.append(root) root = root.left root = stack.pop() print(root.val) root = root.right

后序遍历:BDCAFGE

后序遍历的操作定义为:若二叉树为空,为空操作;否则(1)后序遍历左子树;(2)后序遍历右子树;(3)访问根结点。

递归实现:

def postorder(root): if not root: return postorder(root.left) postorder(root.right) print(root.val)

迭代实现:

def postorder(root): stack = [] while stack or root: while root:                 # 下行循环,直到找到第一个叶子节点 stack.append(root) if root.left:           # 能左就左,不能左就右 root = root.left else: root = root.right s = stack.pop() print(s.val) #如果当前节点是上一节点的左子节点,则遍历右子节点 if stack and s == stack[-1].left: root = stack[-1].right else: root = None

层次遍历:EAGCFBD

层序遍历的操作定义为:若二叉树为空,为空操作;否则从上到下、从左到右按层次进行访问。

递归实现:

def BFS(root): queue = [root] while queue: n = len(queue) for i in range(n): q = queue.pop(0) if q: print(q.val) queue.append(q.left if q.left else None) queue.append(q.right if q.right else None)

代码实现:

# _*_ coding=utf-8 _*_"""实现一个二叉树结果,并进行遍历 E /  \ A    G \    \ C    F / \ B   D"""from collections import dequeclass BinaryTree(object): def __init__(self, data): self.data = data self.child_l = None self.child_r = None# 创建a = BinaryTree("A")b = BinaryTree("B")c = BinaryTree("C")d = BinaryTree("D")e = BinaryTree("E")f = BinaryTree("F")g = BinaryTree("G")# 构造节点关系e.child_l = ae.child_r = ga.child_r = cc.child_l = bc.child_r = dg.child_r = f# 设置根root = edef pre_order(tree): """ 前序遍历:root -> child_l -> child_r :param tree: the root of tree :return: """ if tree: print(tree.data, end=',') # print("") pre_order(tree.child_l) pre_order(tree.child_r)def in_order(tree): """ 中序遍历:child_l -> root -> child_r :param tree: :return: """ if tree: in_order(tree.child_l) print(tree.data, end=',') in_order(tree.child_r)def post_order(tree): """ 后序遍历:child_l -> child_r -> root :param tree: :return: """ if tree: post_order(tree.child_l) post_order(tree.child_r) print(tree.data, end=',')def level_order(tree): """ 层次遍历:E -> AG -> CF -> BD 使用队列实现   :param tree: :return: """ queue = deque() queue.append(tree)          # 先把根添加到队列 while len(queue):           # 队列不为空 node = queue.popleft()       print(node.data, end=',') if node.child_l: queue.append(node.child_l) if node.child_r: queue.append(node.child_r)pre_order(root)print('')in_order(root)print('')post_order(root)print('')level_order(root)

二叉树的最大深度

基本思路就是递归,当前树的最大深度等于(1+max(左子树最大深度,右子树最大深度))。代码如下:

def maxDepth(root): if not root: return 0 return 1+max(maxDepth(root.left),maxDepth(root.right))

二叉树的最小深度

最小深度是从根节点到最近叶子节点的最短路径上的节点数量。可以通过递归求左右节点的最小深度的较小值,也可以层序遍历找到第一个叶子节点所在的层数。

递归方法:

class Solution: def minDepth(self, root: TreeNode) -> int: if not root: return 0 if not root.left and not root.right: return 1 if not root.right: return 1+self.minDepth(root.left) if not root.left: return 1+self.minDepth(root.right) return 1+min(self.minDepth(root.left),self.minDepth(root.right))

迭代方法:

class Solution: def minDepth(self, root: TreeNode) -> int: if not root: return 0 ans,count = [root],1 while ans: n = len(ans) for i in range(n): r = ans.pop(0) if r: if not r.left and not r.right: return count ans.append(r.left if r.left else []) ans.append(r.right if r.right else []) count+=1

二叉树的所有路径

根节点到叶子节点的所有路径。

def traverse(node): if not node.left and not node.right: return [str(node.val)]   left, right = [], [] if node.left: left = [str(node.val) + x for x in traverse(node.left)] if node.right: right = [str(node.val) + x for x in traverse(node.right)] return left + right

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,100评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,308评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,718评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,275评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,376评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,454评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,464评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,248评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,686评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,974评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,150评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,817评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,484评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,140评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,374评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,012评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,041评论 2 351

推荐阅读更多精彩内容