what's the 二叉树

what's the 树

  在了解二叉树之前,首先我们得有树的概念。

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

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

有关树的一些相关术语:

    •    节点的度:一个节点含有的子树的个数称为该节点的度;
    •  叶节点或终端节点:度为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

 

二叉树有两个特殊的形态:满二叉树完全二叉树

满二叉树

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

完全二叉树

  叶节点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树为完全二叉树。即右边的最下层和次下层可以适当缺一个右子数

  完全二叉树是效率很高的数据结构

 

二叉树的遍历

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

二叉树结点的定义

#二叉树结点的定义
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路搜索树。它或者是空树,或者是满足下列性质的树:
    1、根结点至少有两个子女;
    2、每个非根节点所包含的关键字个数 j 满足:┌m/2┐ - 1 <= j <= m - 1;
    3、除根结点以外的所有结点(不包括叶子结点)的度数正好是关键字总数加1,故内部子树个数 k 满足:┌m/2┐ <= k <= m ;
    4、所有的叶子结点都位于同一层。
  在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-树类似。

 

 

 

 

                                                     

 

posted @ 2018-02-03 15:03  ''竹先森゜  阅读(2424)  评论(0编辑  收藏  举报