zoukankan      html  css  js  c++  java
  • 二叉树与AVL树

    二叉树

    什么是二叉树?

        父节点至多只有两个子树的树形结构成为二叉树。如下图所示,图1不是二叉树,图2是一棵二叉树。

            

                                         图1 普通的树                                                                                   图2 二叉树

         如果一棵树所有的非叶子节点都有两个子节点,则称该树为完全二叉树,图2就是一棵完全二叉树。

    二叉查找树(ADT

          二叉树一个重要的应用是二差查找树,顾名思义,二叉查找树是二叉树在查找方面的应用。根据以往的知识,如果给你一个数组或者是链表,可能需要遍历一整个数组或者是量表才能找到需要查找的目标。使用二叉树作为查找的数据结构,能够大大缩小查找的深度(若链表和数组的长度为N,一棵二叉树的深度为logN),提高查找的效率。

    如何定义一棵查找树?

         设每一个节点对应一个键值,而该节点的左子节点对用的数值小于该节点的值,该节点的右子节点的数值大于该节点的值。如下图所示:

     

                                    图3 二差查找树                                                             图4 非二差查找树

    图3是二叉查找树,因为他总数满足上述的条件,而图4不是二叉查找树,图中红色框住的部分不满足二叉查找树的条件(父节点6<左子节点7)。

    代码:

    struct Tree{

        double value;

        Tree *left;

        Tree *right;

    };

    TreeNode表示二叉树节点。其中value表示树对应的值,left表示该节点的左节点指针,right表示该节点的右节点指针。

    二叉查找树的重要操作

    1.查找

    1.1 查找固定的数值

         给定一个数值,查找二叉树中是否有对应的数值,如果树中包含该数值,则返回数值对应的节点,否则,返回NULL。查找的过程:

         首先定位到跟节点,如果查找的数值跟节点的数值相等,则根节点为所求;否则,如果数值小于根节点的数值,则查找根节点的左节点,相反,则查找跟节点的右节点。这样一直遍历下去,知道找到对应的或者或者已经不能继续往下查找为止(即已经到达子节点)。

    代码:

     1 Tree* Find(double value,Tree *t){
     2     if(t==NULL)
     3         return NULL;
     4     if(value==t->value)
     5         return t;
     6     else if(value<t->value)
     7         return Find(value,t->left);
     8     else
     9         return Find(value,t->right);
    10 }

    1.2查找最大值或最小值

          二叉查找树一个很大的特点是左子节点的数值<父节点的数值,而右子节点的数值>父节点的数值,因此查找最大值或者最小值就相当的方便,只需要从跟节点开始,不断遍历左子结点,直到到达叶子节点,就可以得到最小值;而查找最大值则从根节点开始遍历右子结点,直到到达叶子节点,对用的数值即为最大值。

    代码:

    超找最小元素的节点

    1 Tree* FindMin(Tree* t){
    2     if(t==NULL)
    3         return NULL;
    4     if(t->left==NULL)
    5         return t;
    6     else{
    7         return FindMin(t->left);
    8     }
    9 }

    查找最大元素的节点

    1 Tree* FindMax(Tree *t){
    2     if(t==NULL)
    3         return NULL;
    4     if(t->right==NULL)
    5         return t->right;
    6     else
    7         return FindMax(t->right);
    8 }

    2. 插入

          插入操作是将一个数值插入到二叉查找树中的过程,插入后的树依然满足二叉查找树的条件。一棵二叉查找树的构建过程,就是一个不断将元素插入到二叉树的过程。

          插入的操作最关键的是一个查找的过程,如果在二叉树中找到要插入的数值,则什么都不用做,如果找不到,只需要在最后我们查找的叶子节点上新增加一个子节点即可。举一个例子:

     

    上图是一棵二叉树,如果我们要插入11,则按以下步骤进行:

    再比如要找17,按以下步骤查找:

    代码:

     1 void Insert(double value,Tree* t){
     2     if(t==NULL){
     3         t = new Tree();
     4         if(t==NULL)
     5             return;
     6         else{
     7             t->value=value;
     8             t->left=NULL;
     9             t->right=NULL;
    10         }
    11     }
    12     if(t->value>value)
    13         Insert(value,t->left);
    14     else
    15         Insert(value,t->right);
    16 }

    3. 删除

         删除操作就是删除键值与数值相同的节点。删除操作相比查找和插入来说是一个较为复杂的过程。如果要删除的节点就是叶子节点,直接将该节点删除即可,但是,如果要删除非叶子节点,就需要通过调整其他节点的位置来构建新的二叉树。一般来说,直接用该节点的右子树的最小值替换该节点的值,然后再删除右子树的最小节点即可。

    例子:

    AVL

         虽说二叉查找树是一种优秀的数据结构,能够大大降低数据查询的复杂度。但是,并不是说有情况下二叉树都能够达到快速查找的目的。

     

         我们发现,如果按照[7,10,11,12,14,15,18]这样的顺序一个个元素进行插入的话就会出现右图所示的二叉树,这样的二叉树跟一个链表几乎是没有区别的,查找的效率一样,没有体现出二叉树的优势。出现这种原因是构建二叉树的过程中没有平衡节点的左右子树的高度。根节点7的右子树有很高的深度,但是左子树是空的。我们需要的是一棵左右节点平衡的二叉树,而其中一种传统的平衡二叉树是AVL树。

         定义:AVL树是二叉树,其各个节点的左右子树的高度相差不超过1。

         定义:数的高度可以看做是节点与最低的叶子节点的距离。跟节点的高度最大,而叶子节点的高度为0,一个不存在的节点的高度定义为-1。

         例如:左图中节点12的高度为2,节点18的高度为0,而右图中节点12的高度为3,节点18的高度为1。

    左图是一棵AVL树,右图不是一棵AVL树,因为右图节点15的左右子树的高度相差2(左子树的高度为-1,右子树的盖度为1)。

    AVL树的构建

         AVL树的构建同样是不断将元素插入的过程,但是与二叉查找树不同,AVL树在插入的过程中需要满足AVL树的条件,如果发现插入新的元素后不能满足AVL条件,需要通过调整元素的位置直到满足条件。

         元素的插入无非就只有以下四种情况:1.左子树插入一个左节点;2.左子树插入一个右节点;3.右子树插入一个左节点;4.右子树插入一个右节点。

    其中,1和4、2和3是对称的操作,因此下面只讨论1和2两种情况。

    1. 左子树插入一个左节点

     

         如上图所述,左边是原始的二叉树,该二叉树满足AVL树的条件,在插入元素5后变成了右边的二叉树,而此时不满足AVL树的条件,因为节点10的左右两棵子树的高度相差2(左子树的高度为1,右子树的高度为-1)。

         此时需要通过对子树进行调整才能让二叉树再次满足AVL的条件。

         如上图所示,调整后的二叉树重新变成一棵AVL树,则种调整的方法称为“左旋转”,通过旋转调整节点的位置,使二叉树满足AVL树的条件。同理,如果新增的元素为右子树的右子树,而且新增后子树的左右子树高度相差为2,此时进行“右旋转”即可调整为AVL树。

    2. 左子树插入一个右节点

          如上图所述,左边是原始的二叉树,该二叉树满足AVL树的条件,在插入元素8后变成了右边的二叉树,而此时不满足AVL树的条件,因为节点10的左右两棵子树的高度相差2(左子树的高度为1,右子树的高度为-1)。

          与情况1(左子树新增左节点)不一样,此时不能通过一个简单的“坐旋转”来调整左右子树的高度。怎么办呢?需要通过两次“旋转”,显示通过对元素7进行右旋转,然后再对10进行左旋转。如下图所示:

          同理,对于右子树插入一个左节点的情况,如果此时不符合AVL树的条件,需要先进性“左旋转”,再进行“右旋转”即可。

    代码:

     1 AVLTree* AVLInsert(double value,AVLTree * tree){
     2     if(tree==NULL){
     3         tree = new AVLTree();
     4         tree->value=value;
     5         tree->left=NULL;
     6         tree->right=NULL;
     7         tree->height=0;
     8         return;
     9     }
    10     if(value>tree->value){
    11         tree->right=AVLInsert(value,tree->right);
    12         if(Height(tree->right)-Height(tree->left)==2){
    13             if(value>tree->right->value)
    14                 tree=SingleRotateWithRight(tree);
    15             else
    16                 tree=DoubleRouteWithRight(tree);
    17         }
    18     }
    19     else{
    20         tree->left=AVLInsert(value,tree->left);
    21         if(Height(tree->left)-Height(tree->right)==2){
    22             if(value<tree->left->value)
    23                 tree=SingleRotateWithLeft(tree);
    24             else
    25                 tree=DoubleRouteWithLeft(tree);
    26         }
    27     }
    28     tree->height = Height(tree->left)>Height(tree->right)?Height(tree->left)+1:Height(tree->right)+1;
    29     return tree;
    30 }
    31 //左旋转
    32 AVLTree* SingleRotateWithLeft(AVLTree * tree){
    33     AVLTree * left = tree->left;
    34     tree->left=left->right;
    35     left->right=tree;
    36     tree->height=Height(tree->left)>Height(tree->right)?Height(tree->left)+1:Height(tree->right)+1;
    37     left->height=Height(left->left)>Height(left->right)?Height(left->left)+1:Height(left->right)+1;
    38     return left;
    39 }
    40 //右旋转
    41 AVLTree* SingleRotateWithRight(AVLTree * tree){
    42     AVLTree * right = tree->right;
    43     tree->right=right->left;
    44     right->left=tree;
    45     tree->height=Height(tree->left)>Height(tree->right)?Height(tree->left)+1:Height(tree->right)+1;
    46     right->height=Height(right->left)>Height(right->right)?Height(right->left)+1:Height(right->right)+1;
    47     return right;
    48 }
    49 //右—左双旋转
    50 AVLTree* DoubleRouteWithLeft(AVLTree* tree){
    51     tree->left = SingleRotateWithRight(tree->left);
    52     return SingleRotateWithLeft(tree);
    53 }
    54 //右—左双旋转
    55 AVLTree* DoubleRouteWithRight(AVLTree* tree){
    56     tree->right = SingleRotateWithLeft(tree->right);
    57     return SingleRotateWithRight(tree);
    58 }
    View Code
  • 相关阅读:
    GridView合并表头多重表头
    C# 导出Excel或Word
    GridView的分页功能?
    如何在GridView中判断Radio被选中?
    GridView無數據時,顯示表頭
    Oracle replace函数使用
    获取数据后导出Excel
    Oracel用rownum实现真分页
    转载C#泛型集合—Dictionary<K,V>使用技巧
    临时向表插入有自增的字段的记录
  • 原文地址:https://www.cnblogs.com/yonghao/p/5204848.html
Copyright © 2011-2022 走看看