what's the 二叉树

what's the 树

  在了解二叉树以前,首先咱们得有树的概念。java

  树是一种数据结构又可称为树状图,如文档的目录、HTML的文档树都是树结构,它是由n(n>=1)个有限节点组成一个具备层次关系的集合。把它叫作“树”是由于它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具备如下的特色:node

    • 每一个节点有零个或多个子节点;
    • 没有父节点的节点称为根节点;
    • 每个非根节点有且只有一个父节点;
    • 除了根节点外,每一个子节点能够分为多个不相交的子树; 

有关树的一些相关术语:算法

    •    节点的度:一个节点含有的子树的个数称为该节点的度;
    •  叶节点或终端节点:度为0的节点称为叶节点;
    •  非终端节点或分支节点:度不为0的节点;
    •  双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;
    •  孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点;
    •  兄弟节点:具备相同父节点的节点互称为兄弟节点;
    •  树的度:一棵树中,最大的节点的度称为树的度;
    •  节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
    •  树的高度或深度:树中节点的最大层次;
    •  堂兄弟节点:双亲在同一层的节点互为堂兄弟;
    •  节点的祖先:从根到该节点所经分支上的全部节点;
    •  森林:由m(m>=0)棵互不相交的树的集合称为森林;

 树的种类有:无序树、有序树、二叉树、霍夫曼树。其中最重要应用最多的就是二叉树,下面咱们来学习有关二叉树的知识。数据库

 

 二叉树

  二叉树的定义为度不超过2的树,即每一个节点最多有两个叉(两个分支)。上面那个例图其实就是一颗二叉树。数据结构

  二叉树是每一个节点最多有两个子树的树结构。一般子树被称做 “左子树”(left subtree)“右子树”(right subtree)。二叉树常被用于实现二叉查找树和二叉堆。
  二叉树的每一个结点至多只有二棵子树(不存在度大于2的结点),二叉树的子树有左右之分,次序不能颠倒。二叉树的第i层至多有2^{i-1}个结点;深度为k的二叉树至多有2^k-1个结点;对任何一棵二叉树T,若是其终端结点数为n_0,度为2的结点数为n_2,则n_0=n_2+1。
  一棵深度为k,且有2^k-1个节点的二叉树,称为满二叉树。这种树的特色是每一层上的节点数都是最大节点数。而在一棵二叉树中,除最后一层外,若其他层都是满的,而且最后一层或者是满的,或者是在右边缺乏连续若干节点,则此二叉树为彻底二叉树。具备n个节点的彻底二叉树的深度为log2n+1。深度为k的彻底二叉树,至少有2^(k-1)个节点,至多有2^k-1个节点。
  二叉树的存储方式分为链式存储和顺序存储(相似列表)两种
  二叉树父节点下标i和左孩子节点的编号下标的关系为2i+1,和右孩子节点的编号下标的关系为2i+2

 

二叉树有两个特殊的形态:满二叉树彻底二叉树app

满二叉树ide

  一个二叉树,若是除了叶子节点外每个层的结点数都达到最大值,则这个二叉树就是满二叉树。post

彻底二叉树学习

  叶节点只能出如今最下层和次下层,而且最下面一层的结点都集中在该层最左边的若干位置的二叉树为彻底二叉树。即右边的最下层和次下层能够适当缺一个右子数spa

  彻底二叉树是效率很高的数据结构

 

二叉树的遍历

  二叉树的链式存储:将二叉树的节点定义为一个对象,节点之间经过相似链表的连接方式来链接。

二叉树结点的定义

#二叉树结点的定义
class BiTreeNode:
    def __init__(self, data):
        self.data = data
        self.lchild = None
        self.rchild = None

 

  二叉树的遍历分为四种——前序遍历、中序遍历、后序遍历和层级遍历

设树结构为:

        

  • 前序遍历:先打印根,再递归其左子树,后递归其右子数    E ACBD GF
  • 中序遍历:以根为中心,左边打印左子树,右边打印右子树(注意,每一个子树也有相应的根和子树)   A BCD E GF
  • 后序遍历:先递归左子树,再递归右子树,后打印根(注意,每一个子树也有相应的根和子树BDC A FG E
  • 层次遍历:从根开始一层一层来,同一层的从左到右输出E AG CF BD

四种遍历方法的代码实现:

from collections import deque
#结点的定义
class BiTreeNode:
    def __init__(self, data):
        self.data = data
        self.lchild = None
        self.rchild = None
#二叉树结点
a = BiTreeNode('A')
b = BiTreeNode('B')
c = BiTreeNode('C')
d = BiTreeNode('D')
e = BiTreeNode('E')
f = BiTreeNode('F')
g = BiTreeNode('G')
#结点之间的关系
e.lchild = a
e.rchild = g
a.rchild = c
c.lchild = b
c.rchild = d
g.rchild = f

root = e

#前序遍历:先打印根,再递归左孩子,后递归右孩子
def pre_order(root):
    if root:
        print(root.data, end='')
        pre_order(root.lchild)
        pre_order(root.rchild)
#中序遍历:以根为中心,左边打印左子树,右边打印右子树(注意,每一个子树也有相应的根和子树)
#(ACBD) E (GF)-->(A(CBD)) E (GF)-->(A (B C D)) E (G F)
def in_order(root):
    if root:
        in_order(root.lchild)
        print(root.data, end='')
        in_order(root.rchild)

#后序遍历:先递归左子树,再递归右子数,后打印根(注意,每一个子树也有相应的根和子树)
# (ABCD)(GF)E-->((BCD)A)(GF)E-->(BDCA)(FG)E
def post_order(root):
    if root:
        post_order(root.lchild)
        post_order(root.rchild)
        print(root.data, end='')

#层次遍历:一层一层来,同一层的从左到右输出
def level_order(root):
    queue = deque()
    queue.append(root)
    while len(queue) > 0:
        node = queue.popleft()
        print(node.data,end='')
        if node.lchild:
            queue.append(node.lchild)
        if node.rchild:
            queue.append(node.rchild)

pre_order(root)#EACBDGF
print("")
in_order(root)#ABCDEGF
print("")
post_order(root)#BDCAFGE
print("")
level_order(root)#EAGCFBD
前序遍历、中序遍历、后序遍历、层级遍历代码实现

 

二叉搜索树

  二叉搜索树(Binary Search Tree),它或者是一棵空树,或者是具备下列性质的二叉树: 若它的左子树不空,则左子树上全部结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上全部结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉搜索树。

二叉搜索树一个很好玩的网址,集成了增删改的功能:https://visualgo.net/en/bst

二叉搜索树的中序遍历获得的是原来列表按升序排序的列表

由列表生成二叉搜索树、经过二叉搜索树查询值和删除值的示例代码:

#结点定义
class BiTreeNode:
    def __init__(self, data):
        self.data = data
        self.lchild = None
        self.rchild = None
#创建二叉搜索树(循环列表,插入值)
class BST:
    def __init__(self, li=None):
        self.root = None
        if li:
            self.root = self.insert(self.root, li[0])#列表的第一个元素是根
            for val in li[1:]:
                self.insert(self.root, val)
    #生成二叉搜索树递归版本
    def insert(self, root, val):
        if root is None:
            root = BiTreeNode(val)
        elif val < root.data:#插入的值小于root,要放到左子树中(递归查询插入的位置)
            root.lchild = self.insert(root.lchild, val)
        else:#插入的值大于root,要放到右子树中(递归查询插入的位置)
            root.rchild = self.insert(root.rchild, val)
        return root
    #生成二叉搜索树不递归的版本
    def insert_no_rec(self, val):
        p = self.root
        if not p:
            self.root = BiTreeNode(val)
            return
        while True:
            if val < p.data:
                if p.lchild:
                    p = p.lchild
                else:
                    p.lchild = BiTreeNode(val)
                    break
            else:
                if p.rchild:
                    p = p.rchild
                else:
                    p.rchild = BiTreeNode(val)
                    break
    #查询递归版本
    def query(self, root, val):
        if not root:
            return False
        if root.data == val:
            return True
        elif root.data > val:
            return self.query(root.lchild, val)
        else:
            return self.query(root.rchild, val)
    #查询非递归版本
    def query_no_rec(self, val):
        p = self.root
        while p:
            if p.data == val:
                return True
            elif p.data > val:
                p = p.lchild
            else:
                p = p.rchild
        return False

    #中序遍历,获得的是升序的列表
    def in_order(self, root):
        if root:
            self.in_order(root.lchild)
            print(root.data, end=',')
            self.in_order(root.rchild)


tree = BST()
for i in [1,5,9,8,7,6,4,3,2]:
    tree.insert_no_rec(i)
tree.in_order(tree.root)
#print(tree.query_no_rec(12))
列表生成二叉搜索树、二叉搜索树查询值和删除值的方法

 

二叉搜索树的应用——AVL树、B树、B+树

AVL树

  AVL树:AVL树是一棵自平衡的二叉搜索树。

  AVL树具备如下性质: 根的左右子树的高度之差的绝对值不能超过1 根的左右子树都是平衡二叉树

  AVL的实现方式:旋转 

B树

  B树是一棵自平衡的多路搜索树。经常使用于数据库的索引。

  一棵m阶B树(balanced tree of order m)是一棵平衡的m路搜索树。它或者是空树,或者是知足下列性质的树:
    一、根结点至少有两个子女;
    二、每一个非根节点所包含的关键字个数 j 知足:┌m/2┐ - 1 <= j <= m - 1;
    三、除根结点之外的全部结点(不包括叶子结点)的度数正好是关键字总数加1,故内部子树个数 k 知足:┌m/2┐ <= k <= m ;
    四、全部的叶子结点都位于同一层。
  在B-树中,每一个结点中关键字从小到大排列,而且当该结点的孩子是非叶子结点时,该k-1个关键字正好是k个孩子包含的关键字的值域的分划。
  由于叶子结点不包含关键字,因此能够把叶子结点当作在树里实际上并不存在外部结点,指向这些外部结点的指针为空,叶子结点的数目正好等于树中所包含的关键字总个数加1。
  B-树中的一个包含n个关键字,n+1个指针的结点的通常形式为: (n,P0,K1,P1,K2,P2,…,Kn,Pn)其中,Ki为关键字,K1<K2<…<Kn, Pi 是指向包括Ki到Ki+1之间的关键字的子树的指针。

  在B-树中查找给定关键字的方法是,首先把根结点取来,在根结点所包含的关键字K1,…,Kn查找给定的关键字(可用顺序查找或二分查找法),若找到等于给定值的关键字,则查找成功;不然,必定能够肯定要查找的关键字在Ki与Ki+1之间,Pi为指向子树根节点的指针,此时取指针Pi所指的结点继续查找,直至找到,或指针Pi为空时查找失败。

B+ 树

  B+ 树是一种树数据结构,是一个n叉排序树,每一个节点一般有多个孩子,一棵B+树包含根节点、内部节点和叶子节点。根节点多是一个叶子节点,也多是一个 包含两个或两个以上孩子节点的节点。
  B+ 树一般用于数据库和操做系统的文件系统中。NTFS, ReiserFS, NSS, XFS, JFS, ReFS 和BFS等文件系统都在使用B+树做为元数据索引。B+ 树的特色是可以保持数据稳定有序,其插入与修改拥有较稳定的对数时间复杂度。B+ 树元素自底向上插入。
  B+树是应文件系统所需而出的一种B树的变型树。一棵m阶的B+树和m阶的B-树的差别在于:
    1.有n棵子树的结点中含有n个关键字,每一个关键字不保存数据,只用来索引,全部数据都保存在叶子节点。
    2.全部的叶子结点中包含了所有关键字的信息,及指向含这些关键字记录的指针,且叶子结点自己依关键字的大小自小而大顺序连接。
    3.全部的非终端结点能够当作是索引部分,结点中仅含其子树(根结点)中的最大(或最小)关键字。
  一般在B+树上有两个头指针,一个指向根结点,一个指向关键字最小的叶子结点。

 

 

B+树的查找

  对B+树能够进行两种查找运算:
  1.从最小关键字起顺序查找;
  2.从根结点开始,进行随机查找。
  在查找时,若非终端结点上的关键值等于给定值,并不终止,而是继续向下直到叶子结点。所以,在B+树中,无论查找成功与否,每次查找都是走了一条从根到叶子结点的路径。其他同B-树的查找相似。
  如下是从根节点查找叶子节点k的伪代码[1]   :
1
2
3
4
5
6
7
8
9
10
Function: search (k)  
     return  tree_search (k, root); Function: tree_search (k, node)  
     if  node is a leaf then         return  node;  
     switch  do     case  k < k_0    
         return  tree_search(k, p_0);  
     case  k_i ≤ k < k_{i+ 1 }    
         return  tree_search(k, p_{i+ 1 });  
     case  k_d ≤ k    
         return  tree_search(k, p_{d+ 1 });
//伪代码假设没有重复值
 

B+树的插入

  m阶B树的插入操做在叶子结点上进行,假设要插入关键值a,找到叶子结点后插入a,作以下算法判别:
    ①若是当前结点是根结点而且插入后结点关键字数目小于等于m,则算法结束;
    ②若是当前结点是非根结点而且插入后结点关键字数目小于等于m,则判断若a是新索引值时转步骤④后结束,若a不是新索引值则直接结束;
    ③若是插入后关键字数目大于m(阶数),则结点先分裂成两个结点X和Y,而且他们各自所含的关键字个数分别为:u=大于(m+1)/2的最小整数,v=小于(m+1)/2的最大整数;
      因为索引值位于结点的最左端或者最右端,不妨假设索引值位于结点最右端,有以下操做:
      若是当前分裂成的X和Y结点原来所属的结点是根结点,则从X和Y中取出索引的关键字,将这两个关键字组成新的根结点,而且这个根结点指向X和Y,算法结束;
      若是当前分裂成的X和Y结点原来所属的结点是非根结点,依据假设条件判断,若是a成为Y的新索引值,则转步骤④获得Y的双亲结点P,若是a不是Y结点的新索引值,则求出X和Y结点的双亲结点P;而后提取X结点中的新索引值a’,在P中插入关键字a’,从P开始,继续进行插入算法;
    ④提取结点原来的索引值b,自顶向下,先判断根是否含有b,是则须要先将b替换为a,而后从根结点开始,记录结点地址P,判断P的孩子是否含有索引值b而不含有索引值a,是则先将孩子结点中的b替换为a,而后将P的孩子的地址赋值给P,继续搜索,直到发现P的孩子中已经含有a值时,中止搜索,返回地址P。

 

B+树的删除

  B+树的删除也仅在叶子结点进行,当叶子结点中的最大关键字被删除时,其在非终端结点中的值能够做为一个“分界关键字”存在。若因删除而使结点中关键字的个数少于m/2 (m/2结果取上界,如5/2结果为3)时,其和兄弟结点的合并过程亦和B-树相似。
相关文章
相关标签/搜索