树与二叉树的学习总结


金三银四面试季,必然少不了关于数据结构与算法的面试,数据结构最重要的恐怕就是树与链表了,而关于树的定义、用途、操作等,必然在面试中要问到。以前也零散的看过一些这方面的知识,终于决定做一下系统的总结,以备后用。

树的基本概念

  • 树(Tree)是由一个或多个结点组成的有限集合T,其中有一个特定的称为根的结点;其余结点可分为m(m≥0)个互不相交的有限集T1,T2,T3 ,…,Tm,每一个集合本身又是一棵树,且称为根的子树。
  • 结点(Node):树中的元素,包含数据项及若干指向其子树的分支。
  • 结点的度(Degree):所有结点当中,子树分支最最多的就是树的度
  • 结点的层次:从根结点开始算起,根为第一层,
  • 叶子(Leaf):度为零的结点,也称端结点。
  • 孩子(Child):结点子树的根称为该结点的孩子结点。
  • 双亲(Parent):孩子结点的上层结点,称为这些结点的双亲。
  • 兄弟(Sibling):同一双亲的孩子。
  • 深度(Depth): 树中结点的最大层次数
  • 森林(Forest):互不相交的树的集合

一般树与二叉树的区别

  1. 树的结点个数至少为1,而二叉树的结点个数可以为0;
  2. 树的结点最大度数没有限制,而二叉树结点的最大度数为2;
  3. 树的结点无左、右之分,而二叉树的结点有左、右之分。

二叉树

二叉树是一种重要的树形结构,其结构定义为:二叉树是n(n≥0)个结点的有限集,它或为空树(n=0),或由一个根结点和两棵分别称为根的左子树和右子树的、互不相交的二叉树组成。
既不是满二叉树也不是完全二叉树,则为一般二叉树
在这里插入图片描述
二叉树的每个结点至多有二棵子树(不存在度大于2的结点),二叉树的子树有左右之分,次序不能颠倒。

  • 1.在二叉树的第K层上,最多有 2k-1 (K >= 1)个结点
  • 2.深度为K的二叉树,最多有 2k - 1 个结点(K>=1)
  • 3.对于任何一棵二叉树,如果其叶子结点的个数为K,度为2的结点数为M,则K=M+1
  • 4.对于一棵有 n 个结点的完全二叉树的结点按层次进行编号(如上图,从第一层到第 (log 2n 向下取整),每层从左到右),对任意结点 i (1<i<n),有:
    如果i=1,则结点i无父结点,是二叉树的根,如果i>1,则父结点为 i/2 向下取整
    如果2i>n,则结点i为叶子结点,无左子结点,否则,其左子结点为2i
    如果2i+1>n,则结点i无右子结点,否则,其右子结点是结点2i+1

二叉树存储方式一般分为两种,顺序存储与链表式存储
顺序存储:
在这里插入图片描述
在这里插入图片描述
链式存储:
g)
顺序存储可能造成存储空间的浪费。

二叉树存储结构

  • 孩子表示法:用指针指出每个节点的孩子节点
class Node:
    def __init__(self,item,l_child=None, r_child=None):
        self.item = item
        self.l_child = l_child
        self.r_child = r_child
#优点:寻找一个节点的孩子节点比较方便。 
#缺点:寻找一个节点得双亲节点很不方便。
  • 双亲表示法:用指针表示出每个节点的双亲节点
class Node:
    def __init__(self,item,parent=None):
        self.item = item
        self.parent = parent

#优点:寻找一个节点得双亲节点操作实现很方便 
#缺点:寻找一个节点的孩子节点很不方便
  • 孩子双亲表示法:用指针既表示出每个节点得双亲节点,也表示出每个节点的孩子节点
class Node:
    def __init__(self,item,parent=None,l_child=None, r_child=None):
        self.item = item
        self.parent = parent
        self.l_child = l_child
        self.r_child = r_child

#优点:找某个节点的双亲节点和孩子节点非常方便 
  • 孩子兄弟表示法:即表示出每个节点的第一个孩子节点,也表示出每个节点的下一个兄弟节点
class Node:
    def __init__(self,item,NextBrother=None,l_child=None, r_child=None):
        self.item = item
        self.NextBrother = NextBrother #指向其下一个兄弟节点
        self.l_child = l_child
        self.r_child = r_child

#优点:找某个节点的兄弟结点节点和孩子节点非常方便 

满二叉树

一棵深度为k,且有2k - 1个节点的树是满二叉树。
通俗解释:除叶结点外,每一个结点都有左右子树,且叶结点都处在最底层的二叉树。

  • 如果一颗树深度为h,最大层数为k,且深度与最大层数相同,即k=h;
  • 它的叶子数是: 2(h-1)
  • 第k层的结点数是: 2(k-1)
  • 总结点数是: 2k - 1
  • 树高:h=log2(n+1)

如上图a所示,就是一个满二叉树,从图形上看,满二叉树是一个三角形。

完全二叉树

完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。

若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第h 层所有的结点都连续集中在最左边,这就是完全二叉树。

满二叉树一定是完全二叉树,完全二叉树不一定是满二叉树。

  • 深度为k的完全二叉树,至少有2(k-1)个节点,至多有2k-1个节点。
  • 树高h=log2n + 1。

二叉排序树

二叉排序树又叫二叉查找树或者二叉搜索树,它首先是一个二叉树,而且必须满足下面的条件:

  • 若左子树不空,则左子树上所有结点的值均小于它的根节点的值;
  • 若右子树不空,则右子树上所有结点的值均大于它的根结点的值
  • 左、右子树也分别为二叉排序树
  • 没有键值相等的节点

二叉树的遍历

在这里插入图片描述

先序遍历

  • 访问根结点
  • 访问左子树的左子树,再访问左子树中的右子树
  • 访问右子树的左子树,再访问右子树中的右子树
  • 任意子树输出顺序为:父结点——左子结点——右子结点
  • 如上图先序遍历顺序为:A-B-D-H-I-E-J-C-F-K-G

中序遍历

  • 先访问左子树中的左子树,再访问左子树中的右子树
  • 访问根结点。
  • 后访问右子树中的左子树,再访问右子树中的右子树
  • 任意子树输出顺序为:左子结点——父结点——右子结点
  • 如上图中序遍历顺序为:H-D-I-B-E-J-A-F-K-C-G

后序遍历

  • 先访问左子树中的左子树,再访问左子树中的右子树
  • 再访问右子树中的左子树,再访问右子树中的右子树
  • 访问根结点
  • 任意子树输出顺序为:左子结点——右子结点——父结点
  • 如上图后序遍历顺序为:H-I-D-J-E-B-K-F-G-C-A

层次遍历

  • 访问根结点,即第1层,设为 i 层
  • 访问i+1层的结点,从左到右顺序访问
  • 层次遍历输出顺序为:根结点—— i 层结点从左到右—— i +1层结点从左到右
  • 如上图层次遍历顺序为:A-B-C-D-E-F-G-H-I-J-K

Python代码实现

class Node(object):
    def __init__(self, item):
        self.item   = item
        self.lchild = None
        self.rchild = None
 
class Tree(object):
    NodeList = []
    def __init__(self):
        self.root = None
        
    def add(self, item):
        node = Node(item)
        if self.root == None:
            self.root = node
            Tree.NodeList.append(self.root)
        else:
            while True:
                node = Tree.NodeList[0]
 
                if node.lchild == None:
                    node.lchild = node
                    Tree.NodeList.append(node.lchild)
                    return
                elif node.rchild == None:
                    node.rchild=node
                    Tree.NodeList.append(node.rchild)
                    Tree.NodeList.pop(0)
                    return
	def traverse(self):  # 层次遍历
        if not self.root:
            return None
        NodeList = [self.root]
        res = [self.root.item]
        while NodeList != []:
            pop_node = NodeList.pop(0)
            if pop_node.lchild is not None:
                NodeList.append(pop_node.lchild)
                res.append(pop_node.lchild.item)

            if pop_node.rchild is not None:
                NodeList.append(pop_node.rchild)
                res.append(pop_node.rchild.item)
        return res

    def preorder(self,root):  # 先序遍历
        if root is None:
            return []
        result = [root.item]
        left_item = self.preorder(root.lchild)
        right_item = self.preorder(root.rchild)
        return result + left_item + right_item

    def inorder(self,root):  # 中序序遍历
        if root is None:
            return []
        result = [root.item]
        left_item = self.inorder(root.lchild)
        right_item = self.inorder(root.rchild)
        return left_item + result + right_item

    def postorder(self,root):  # 后序遍历
        if root is None:
            return []
        result = [root.item]
        left_item = self.postorder(root.lchild)
        right_item = self.postorder(root.rchild)
        return left_item + right_item + result

t = Tree()
for i in range(10):
    t.add(i)
print('层序遍历:',t.traverse())
print('先序遍历:',t.preorder(t.root))
print('中序遍历:',t.inorder(t.root))
print('后序遍历:',t.postorder(t.root))

根据遍历结果推出树形结构

  • 已知先序与中序遍历结果,可以推导出树形结构

  • 已知中序与后序遍历结果,可以推导出树形结构

  • 已知先序与后序遍历结果,无法推导出树形结构,因为无法判断根结点的之前或之后的结点是属于左子树还是右子树

红黑树

红黑树(Red Black Tree) 是一种自平衡二叉查找树。红黑树是每个节点都带有颜色属性的二叉查找树,颜色或红色或黑色。红黑树的性质有以下5点

  • 1、结点是红色或黑色。
  • 2、根结点是黑色。
  • 3、每个叶结点(NIL节点,空节点)是黑色的。
  • 4、每个红色结点的两个子结点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色结点)
  • 5、从任一节点到其每个叶子的所有路径都包含相同数目的黑色结点。

平衡二叉树:又称AVL树,它或者是颗空树,或者是具有下列性质的二叉树:它的左子树和右子树都是平衡二叉树,且左子树和右子树的深度之差的绝对值不超过1。若将二叉树节点的平衡因子BF定义为该节点的左子树的深度减去它的右子树的深度,则平衡二叉树上所有节点的平衡因子只可能为-1,0,1。只要二叉树上有一个节点的平衡因子的绝对值大于1,那么这颗平衡二叉树就失去了平衡。

  • 非叶子节点最多拥有两个子节点;
  • 非叶子节值大于左边子节点、小于右边子节点;
  • 树的左右两边的层级数相差不会大于1;
  • 没有值相等重复的节点

左旋

在这里插入图片描述
所谓以X为中心左旋,就是调整X与其右子树的关系,原 X 的右子节点 Y 变为父节点,将 X 父节点变为左子节点;那原先的子节点在旋转之后如何分布呢?其实不必要计较哪些规则语句,因为可以推导出来
推导步骤如下:

  • 根据二叉搜索树的规则,左子结点比父结点小,父结点比右子结点小,因此左旋第一步就是将X的右子结点Y提升为父结点
  • 由于X比Y小,因此X只能作为Y的左子结点
  • 原X的左子树,仍然比X小,所以只能作为调整后X的左子树
  • 原Y的右子树,仍然比Y大,所以只能作为调整后Y的右子树
  • 原Y的左子树因为是Y的子树,所以其比X要大,因此只能作为X的右子树

右旋

在这里插入图片描述
所谓以X为中心右旋,就是调整X与其左子树的关系,原 X 的左子节点 Y 变为父节点,将 X 父节点变为右子节点;那原先的子节点在旋转之后如何分布呢?
推导步骤如下:

  • 根据二叉搜索树的规则,左子结点比父结点小,父结点比右子结点小,因此右旋第一步就是将X的左子结点Y提升为父结点
  • 由于X比Y大,因此X只能作为Y的右子结点
  • 原X的右子树,仍然比X大,所以只能作为调整后X的右子树
  • 原Y的左子树,仍然比Y小,所以只能作为调整后Y的左子树
  • 原Y的右子树因为是Y的子树,所以其比X要小,因此只能作为X的左子树

红黑树的插入

红黑树首先是一棵二叉排序树,所以它的插入操作要遵循二叉排序树的插入原则;先把要插入的节点的key与根进行比较,小则和根的左孩子做比较,大则跟右孩子作比较,直到找到叶子节点。
在这里插入图片描述
现在按照二叉查找树的插入方式插入了节点21,那么这个21该是什么颜色呢?
在这里插入图片描述
1、如果是黑色,那么不管原来的红黑树是什么样的,这样一定会破坏平衡,因为原来的树是平衡的,现在在这一条路径上多了一个黑色,必然违反了性质5
2、如果是红色,那么也有可能会破坏平衡,主要可能是违反了性质4,新插入的点21的父节点22为红色。但是却有一种特殊情况,比如上图中,如果我插入一个key=0的节点。把0这个节点置为红色,并不会影响原来树的平衡,因为0的父节点是黑色。如下图:
在这里插入图片描述
所以选择插入的21节点为红色
21的颜色置为红色,他的父亲也是红色,违反性质4,需要调整,怎么调整呢?现在就要看新插入的点的叔叔节点了。
因为21的叔叔27是红色的。所以要保持平衡,可以把25设置成红色,22,27分别设置成黑色,
在这里插入图片描述
新的问题:17是红色的,孩子25也是红色的,违反了性质4,需要继续调整,将17、18调整为黑色,此时根结点根据变换规则会变成红色,则不符合性质2,因此继续调整根结点的颜色为黑色,至此所有规则都符合,插入完成。
在这里插入图片描述
这只是插入操作一个示例,总的来说红黑树的插入操作需要变色、左旋、右旋来完成,更详细的说明可以参考我下面列出的博客。

红黑树python代码实现

class RBNode:
    def __init__(self, val, color="black"):
        self.key = x
        self.left = None
        self.right = None
        self.parent = None
        self.color = 'black'
        self.size=None
 
    def is_black_node(self):
        return self.color == "black"
 	def is_red_node(self):
        return self.color == "red"
        
    def set_black_node(self):
        self.color = "black"
 
    def set_red_node(self):
        self.color = "red"
 
#定义红黑树
class RBTree(object):
    def __init__(self):
        self.nil = RBNode(0)
        self.root = self.nil
        
	#左旋转
	def LeftRotate( T, x):
		'''
         * 左旋示意图:对节点x进行左旋,左旋选择x的右结点
         *     parent               parent
         *    /                       /
         *   x                       y
         *  / \                     / \
         * lx  y   ---------->     x  ry
         *    / \                 / \
         *   ly ry               lx ly
		'''
	    y = x.right
	    x.right = y.left
	    if y.left != T.nil:
	        y.left.parent = x
	    y.parent = x.parent
	    if x.parent == T.nil:
	        T.root = y
	    elif x == x.parent.left:
	        x.parent.left = y
	    else:
	        x.parent.right = y
	    y.left = x
	    x.parent = y
	#右旋转
	def RightRotate( T, x):
		'''
		 * 右旋示意图:对节点x进行右旋,选择x的左结点
         *        parent           parent
         *       /                  /
         *      x                  y
         *     /  \               /  \
         *    y   rx   ----->   ly    x
         *   / \                     /  \
         * ly  ry                   ry  rx
		'''
	    y = x.left
	    x.left = y.right
	    if y.right != T.nil:
	        y.right.parent = x
	    y.parent = x.parent
	    if x.parent == T.nil:
	        T.root = y
	    elif x == x.parent.right:
	        x.parent.right = y
	    else:
	        x.parent.left = y
	    y.right = x
	    x.parent = y
	#红黑树的插入
	def RBInsert( T, z):
	    y = T.nil
	    x = T.root
	    while x != T.nil:
	        y = x
	        if z.key < x.key:
	            x = x.left
	        else:
	            x = x.right
	    z.parent = y
	    if y == T.nil:
	        T.root = z
	    elif z.key < y.key:
	        y.left = z
	    else:
	        y.right = z
	    z.left = T.nil
	    z.right = T.nil
	    z.color = 'red'
	    RBInsertFixup(T, z)
	    return z.key, '颜色为', z.color
	#红黑树的上色
	def RBInsertFixup( T, z):
	    while z.parent.color == 'red':
	        if z.parent == z.parent.parent.left:
	            y = z.parent.parent.right
	            if y.color == 'red':
	                z.parent.color = 'black'
	                y.color = 'black'
	                z.parent.parent.color = 'red'
	                z = z.parent.parent
	            else:
	                if z == z.parent.right:
	                    z = z.parent
	                    LeftRotate(T, z)
	                z.parent.color = 'black'
	                z.parent.parent.color = 'red'
	                RightRotate(T,z.parent.parent)
	        else:
	            y = z.parent.parent.left
	            if y.color == 'red':
	                z.parent.color = 'black'
	                y.color = 'black'
	                z.parent.parent.color = 'red'
	                z = z.parent.parent
	            else:
	                if z == z.parent.left:
	                    z = z.parent
	                    RightRotate(T, z)
	                z.parent.color = 'black'
	                z.parent.parent.color = 'red'
	                LeftRotate(T, z.parent.parent)
	    T.root.color = 'black'
	def RBTransplant( T, u, v):
	    if u.parent == T.nil:
	        T.root = v
	    elif u == u.parent.left:
	        u.parent.left = v
	    else:
	        u.parent.right = v
	    v.parent = u.parent

	def RBDelete(T, z):
	    y = z
	    y_original_color = y.color
	    if z.left == T.nil:
	        x = z.right
	        RBTransplant(T, z, z.right)
	    elif z.right == T.nil:
	        x = z.left
	        RBTransplant(T, z, z.left)
	    else:
	        y = TreeMinimum(z.right)
	        y_original_color = y.color
	        x = y.right
	        if y.parent == z:
	            x.parent = y
	        else:
	            RBTransplant(T, y, y.right)
	            y.right = z.right
	            y.right.parent = y
	        RBTransplant(T, z, y)
	        y.left = z.left
	        y.left.parent = y
	        y.color = z.color
	    if y_original_color == 'black':
	        RBDeleteFixup(T, x)
	#红黑树的删除
	def RBDeleteFixup( T, x):
	    while x != T.root and x.color == 'black':
	        if x == x.parent.left:
	            w = x.parent.right
	            if w.color == 'red':
	                w.color = 'black'
	                x.parent.color = 'red'
	                LeftRotate(T, x.parent)
	                w = x.parent.right
	            if w.left.color == 'black' and w.right.color == 'black':
	                w.color = 'red'
	                x = x.parent
	            else:
	                if w.right.color == 'black':
	                    w.left.color = 'black'
	                    w.color = 'red'
	                    RightRotate(T, w)
	                    w = x.parent.right
	                w.color = x.parent.color
	                x.parent.color = 'black'
	                w.right.color = 'black'
	                LeftRotate(T, x.parent)
	                x = T.root
	        else:
	            w = x.parent.left
	            if w.color == 'red':
	                w.color = 'black'
	                x.parent.color = 'red'
	                RightRotate(T, x.parent)
	                w = x.parent.left
	            if w.right.color == 'black' and w.left.color == 'black':
	                w.color = 'red'
	                x = x.parent
	            else:
	                if w.left.color == 'black':
	                    w.right.color = 'black'
	                    w.color = 'red'
	                    LeftRotate(T, w)
	                    w = x.parent.left
	                w.color = x.parent.color
	                x.parent.color = 'black'
	                w.left.color = 'black'
	                RightRotate(T, x.parent)
	                x = T.root
	    x.color = 'black'

	def TreeMinimum( x):
	    while x.left != T.nil:
	        x = x.left
	    return x
	#中序遍历
	def Midsort(x):
	    if x!= None:
	        Midsort(x.left)
	        if x.key!=0:
	            print('key:', x.key,'x.parent',x.parent.key)
	        Midsort(x.right)	   

代码参考博客:https://blog.csdn.net/z649431508/article/details/78034751
红黑树推荐博客文章:https://www.cnblogs.com/yangecnu/p/Introduce-Red-Black-Tree.html
AVL树LL、LR、RR、RL类型判断和调整:http://www.cnblogs.com/qingergege/p/7294892.html

B树、B-树、B+树、B*树

这里的不同的博客有很多不同的说法,有的说B树是二叉搜索树,有的说是多路搜索树,有的说是平衡二叉搜索树,这里我更倾向于是平衡二叉搜索树。下面是按照多路搜索树的定义
B树:是一种多路搜索树(并不是二叉的)
B树与红黑树最大的不同在于,B树的结点可以有许多子女,从几个到几千个。那为什么又说B树与红黑树很相似呢?因为与红黑树一样,一棵含n个结点的B树的高度也为O(lgn),但可能比一棵红黑树的高度小许多,应为它的分支因子比较大。所以,B树可以在O(logn)时间内,实现各种如插入(insert),删除(delete)等动态集合操作。

  • 定义任意非叶子结点最多只有M个儿子;且M>2;
  • 根结点的儿子数为[2, M];
  • 除根结点以外的非叶子结点的儿子数为[M/2, M];
  • 每个结点存放至少M/2-1(取上整)和至多M-1个关键字;(至少2个关键字)
  • 非叶子结点的关键字个数=指向儿子的指针个数-1;
  • 非叶子结点的关键字:K[1], K[2], …, K[M-1];且K[i] < K[i+1];
  • 非叶子结点的指针:P[1], P[2], …, P[M];其中P[1]指向关键字小于K[1]的子树,P[M]指向关键字大于K[M-1]的子树,其它P[i]指向关键字属于(K[i-1], K[i])的子树;
  • 所有叶子结点位于同一层;

B-树

B-树的特性:

  • 关键字集合分布在整颗树中;
  • 任何一个关键字出现且只出现在一个结点中;
  • 搜索有可能在非叶子结点结束;
  • 其搜索性能等价于在关键字全集内做一次二分查找;
  • 自动层次控制;

B-树的搜索,从根结点开始,对结点内的关键字(有序)序列进行二分查找,如果命中则结束,否则进入查询关键字所属范围的儿子结点;重复,直到所对应的儿子指针为空,或已经是叶子结点;
在这里插入图片描述

B+树

B+树是B-树的变体,也是一种多路搜索树, 其定义基本与B-树同,其特点是:

  • 非叶子结点的子树指针与关键字个数相同;
  • 非叶子结点的子树指针P[i],指向关键字值属于[K[i], K[i+1])的子树(B-树是开区间);
  • 为所有叶子结点增加一个链指针;
  • 所有关键字都在叶子结点出现,搜索命中总在叶子节点;
    在这里插入图片描述

B+的特性:

  • 所有关键字都出现在叶子结点的链表中(稠密索引),且链表中的关键字恰好是有序的;
  • 不可能在非叶子结点命中;
  • 非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储(关键字)数据的数据层;
  • 更适合文件索引系统;-增 删文件(节点)时,效率更高,因为B+树的叶子节点包含所有关键字,并以有序的链表结构存储,这样可很好提高增删效率。

B*树

B*树 是B+树的变体,在B+树的非根和非叶子结点再增加指向兄弟的指针;
在这里插入图片描述

  • B*树定义了非叶子结点关键字个数至少为(2/3)*M,即块的最低使用率为2/3(代替B+树的1/2);
  • B+树的分裂:当一个结点满时,分配一个新的结点,并将原结点中1/2的数据复制到新结点,最后在父结点中增加新结点的指针;
  • B+树的分裂只影响原结点和父结点,而不会影响兄弟结点,所以它不需要指向兄弟的指针;
  • B树的分裂:当一个结点满时,如果它的下一个兄弟结点未满,那么将一部分数据移到兄弟结点中,再在原结点插入关键字,最后修改父结点中兄弟结点的关键字(因为兄弟结点的关键字范围改变了);如果兄弟也满了,则在原结点与兄弟结点之间增加新结点,并各复制1/3的数据到新结点,最后在父结点增加新结点的指针;所以,B树分配新结点的概率比B+树要低,空间使用率更高;

参考博客:https://www.cnblogs.com/qlqwjy/p/7965491.html

后面的关于红黑树、B树的代码实现我暂写不出来,只能写一写基本的定义以及说明,敬请谅解,经过这些总结,虽然写不出代码来,但至少理解了一些思想,还是有点收获的。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值