• 纸上谈兵: 树, 二叉树, 二叉搜索树(转载)


    作者:Vamei 出处:http://www.cnblogs.com/vamei 

    树的特征和定义

    树(Tree)是元素的集合。我们先以比较直观的方式介绍树。下面的数据结构是一个树:

    树有多个节点(node),用以储存元素。某些节点之间存在一定的关系,用连线表示,连线称为边(edge)。边的上端节点称为父节点,下端称为子节点。树像是一个不断分叉的树根。

    每个节点可以有多个子节点(children),而该节点是相应子节点的父节点(parent)。比如说,3,5是6的子节点,6是3,5的父节点;1,8,7是3的子节点, 3是1,8,7的父节点。树有一个没有父节点的节点,称为根节点(root),如图中的6。没有子节点的节点称为叶节点(leaf),比如图中的1,8,9,5节点。从图中还可以看到,上面的树总共有4个层次,6位于第一层,9位于第四层。树中节点的最大层次被称为深度。也就是说,该树的深度(depth)为4。

    如果我们从节点3开始向下看,而忽略其它部分。那么我们看到的是一个以节点3为根节点的树:

    三角形代表一棵树

    再进一步,如果我们定义孤立的一个节点也是一棵树的话,原来的树就可以表示为根节点和子树(subtree)的关系:

    上述观察实际上给了我们一种严格的定义树的方法:

    1. 树是元素的集合。

    2. 该集合可以为空。这时树中没有元素,我们称树为空树 (empty tree)。

    3. 如果该集合不为空,那么该集合有一个根节点,以及0个或者多个子树。根节点与它的子树的根节点用一个边(edge)相连。

    上面的第三点是以递归的方式来定义树,也就是在定义树的过程中使用了树自身(子树)。由于树的递归特征,许多树相关的操作也可以方便的使用递归实现。我们将在后面看到。

    (上述定义来自"Data Structures and Algorithm Analysis in C, by Mark Allen Weiss"。 我觉得有一点不太严格的地方。如果说空树属于树,第三点应该是 “...以及0个和多个非空子树...” )

    树的实现

    树的示意图已经给出了树的一种内存实现方式: 每个节点储存元素和多个指向子节点的指针。然而,子节点数目是不确定的。一个父节点可能有大量的子节点,而另一个父节点可能只有一个子节点,而树的增删节点操作会让子节点的数目发生进一步的变化。这种不确定性就可能带来大量的内存相关操作,并且容易造成内存的浪费。

    一种经典的实现方式如下:

    树的内存实现

    拥有同一父节点的两个节点互为兄弟节点(sibling)。上图的实现方式中,每个节点包含有一个指针指向第一个子节点,并有另一个指针指向它的下一个兄弟节点。这样,我们就可以用统一的、确定的结构来表示每个节点。

    计算机的文件系统是树的结构,比如Linux文件管理背景知识中所介绍的。在UNIX的文件系统中,每个文件(文件夹同样是一种文件),都可以看做是一个节点。非文件夹的文件被储存在叶节点。文件夹中有指向父节点和子节点的指针(在UNIX中,文件夹还包含一个指向自身的指针,这与我们上面见到的树有所区别)。在git中,也有类似的树状结构,用以表达整个文件系统的版本变化 (参考版本管理三国志)。

    文件树

    二叉搜索树的C实现

    二叉树(binary)是一种特殊的树。二叉树的每个节点最多只能有2个子节点:

    二叉树

    由于二叉树的子节点数目确定,所以可以直接采用上图方式在内存中实现。每个节点有一个左子节点(left children)和右子节点(right children)。左子节点是左子树的根节点,右子节点是右子树的根节点。

    如果我们给二叉树加一个额外的条件,就可以得到一种被称作二叉搜索树(binary search tree)的特殊二叉树。二叉搜索树要求:每个节点都不比它左子树的任意元素小,而且不比它的右子树的任意元素大。

    (如果我们假设树中没有重复的元素,那么上述要求可以写成:每个节点比它左子树的任意节点大,而且比它右子树的任意节点小)

    二叉搜索树,注意树中元素的大小

    二叉搜索树可以方便的实现搜索算法。在搜索元素x的时候,我们可以将x和根节点比较:

    1. 如果x等于根节点,那么找到x,停止搜索 (终止条件)

    2. 如果x小于根节点,那么搜索左子树

    3. 如果x大于根节点,那么搜索右子树

    二叉搜索树所需要进行的操作次数最多与树的深度相等。n个节点的二叉搜索树的深度最多为n,最少为log(n)。

    下面是用C语言实现的二叉搜索树,并有搜索,插入,删除,寻找最大最小节点的操作。每个节点中存有三个指针,一个指向父节点,一个指向左子节点,一个指向右子节点。

    (这样的实现是为了方便。节点可以只保存有指向左右子节点的两个指针,并实现上述操作。)

    删除节点相对比较复杂。删除节点后,有时需要进行一定的调整,以恢复二叉搜索树的性质(每个节点都不比它左子树的任意元素小,而且不比它的右子树的任意元素大)。

    • 叶节点可以直接删除。
    • 删除非叶节点时,比如下图中的节点8,我们可以删除左子树中最大的元素(或者右树中最大的元素),用删除的节点来补充元素8产生的空缺。但该元素可能也不是叶节点,所以它所产生的空缺需要其他元素补充…… 直到最后删除一个叶节点。上述过程可以递归实现。

    删除节点

    删除节点后的二叉搜索树

     

      1 /* By Vamei */
      2 /* binary search tree */
      3 #include <stdio.h>
      4 #include <stdlib.h>
      5 
      6 typedef struct node *position;
      7 typedef int ElementTP;
      8 
      9 struct node {
     10     position parent;
     11     ElementTP element;
     12     position lchild;
     13     position rchild;
     14 };
     15 
     16 /* pointer => root node of the tree */
     17 typedef struct node *TREE;
     18 
     19 void print_sorted_tree(TREE);
     20 position find_min(TREE);
     21 position find_max(TREE);
     22 position find_value(TREE, ElementTP);
     23 position insert_value(TREE, ElementTP);
     24 ElementTP delete_node(position);
     25 
     26 static int is_root(position);
     27 static int is_leaf(position);
     28 static ElementTP delete_leaf(position);
     29 static void insert_node_to_nonempty_tree(TREE, position);
     30 
     31 void main(void) 
     32 {
     33     TREE tr;
     34     position np;
     35     ElementTP element;
     36     tr = NULL;
     37     tr = insert_value(tr, 18);
     38     tr = insert_value(tr, 5);
     39     tr = insert_value(tr, 2); 
     40     tr = insert_value(tr, 8);
     41     tr = insert_value(tr, 81);
     42     tr = insert_value(tr, 101);
     43     printf("Original:
    ");
     44     print_sorted_tree(tr);
     45 
     46     np = find_value(tr, 8);
     47     if(np != NULL) {
     48         delete_node(np);
     49         printf("After deletion:
    ");
     50         print_sorted_tree(tr);
     51     }
     52 }
     53 
     54 
     55 /* 
     56  * print values of the tree in sorted order
     57  */
     58 void print_sorted_tree(TREE tr)
     59 {
     60     if (tr == NULL) return;
     61     print_sorted_tree(tr->lchild);
     62     printf("%d 
    ", tr->element);
     63     print_sorted_tree(tr->rchild);
     64 }
     65 
     66 /*
     67  * search for minimum value
     68  * traverse lchild
     69  */
     70 position find_min(TREE tr)
     71 {
     72     position np;
     73     np = tr;
     74     if (np == NULL) return NULL;
     75     while(np->lchild != NULL) {
     76         np = np->lchild;
     77     }
     78     return np;
     79 }
     80 
     81 /*
     82  * search for maximum value
     83  * traverse rchild
     84  */
     85 position find_max(TREE tr)
     86 {
     87     position np;
     88     np = tr;
     89     if (np == NULL) return NULL;
     90     while(np->rchild != NULL) {
     91         np = np->rchild;
     92     }
     93     return np;
     94 }
     95 
     96 /*
     97  * search for value
     98  *
     99  */
    100 position find_value(TREE tr, ElementTP value) 
    101 {
    102     if (tr == NULL) return NULL; 
    103 
    104     if (tr->element == value) {
    105         return tr;
    106     }
    107     else if (value < tr->element) {
    108         return find_value(tr->lchild, value);
    109     }
    110     else {
    111         return find_value(tr->rchild, value);
    112     }
    113 }
    114 
    115 /* 
    116  * delete node np 
    117  */
    118 ElementTP delete_node(position np) 
    119 {
    120     position replace;
    121     ElementTP element;
    122     if (is_leaf(np)) {
    123         return delete_leaf(np);
    124     }   
    125     else {
    126         /* if a node is not a leaf, then we need to find a replacement */
    127         replace = (np->lchild != NULL) ? find_max(np->lchild) : find_min(np->rchild);
    128         element = np->element;
    129         np->element = delete_node(replace);
    130         return element;
    131     }
    132 }
    133 
    134 /* 
    135  * insert a value into the tree
    136  * return root address of the tree
    137  */
    138 position insert_value(TREE tr, ElementTP value) {
    139     position np;
    140     /* prepare the node */
    141     np = (position) malloc(sizeof(struct node));
    142     np->element = value;
    143     np->parent  = NULL;
    144     np->lchild  = NULL;
    145     np->rchild  = NULL;
    146  
    147     if (tr == NULL) tr = np;
    148     else {
    149         insert_node_to_nonempty_tree(tr, np);
    150     }
    151     return tr;
    152 }
    153 
    154 
    155 //=============================================
    156 
    157 /*
    158  * np is root?
    159  */
    160 static int is_root(position np)
    161 {
    162     return (np->parent == NULL);
    163 }
    164 
    165 /*
    166  * np is leaf?
    167  */
    168 static int is_leaf(position np)
    169 {
    170     return (np->lchild == NULL && np->rchild == NULL);
    171 }
    172 
    173 /* 
    174  * if an element is a leaf, 
    175  * then it could be removed with no side effect.
    176  */
    177 static ElementTP delete_leaf(position np)
    178 {
    179     ElementTP element;
    180     position parent;
    181     element = np->element;
    182     parent  = np->parent;
    183     if(!is_root(np)) {
    184         if (parent->lchild == np) {
    185             parent->lchild = NULL;
    186         }
    187         else {
    188             parent->rchild = NULL;
    189         }
    190     }
    191     free(np);
    192     return element;
    193 }
    194 
    195 /*
    196  * insert a node to a non-empty tree
    197  * called by insert_value()
    198  */
    199 static void insert_node_to_nonempty_tree(TREE tr, position np)
    200 {
    201     /* insert the node */
    202     if(np->element <= tr->element) {
    203         if (tr->lchild == NULL) {
    204             /* then tr->lchild is the proper place */
    205             tr->lchild = np;
    206             np->parent = tr;
    207             return;
    208         }
    209         else {
    210             insert_node_to_nonempty_tree(tr->lchild, np);
    211         }
    212     }
    213     else if(np->element > tr->element) {
    214         if (tr->rchild == NULL) {
    215             tr->rchild = np;
    216             np->parent = tr;
    217             return;
    218         }
    219         else {
    220             insert_node_to_nonempty_tree(tr->rchild, np);
    221         }
    222     }
    223 }

    运行结果:

    Original:



    18 
    81 
    101 
    After deletion:


    18 
    81 
    101

    上述实现中的删除比较复杂。有一种简单的替代操作,称为懒惰删除(lazy deletion)。在懒惰删除时,我们并不真正从二叉搜索树中删除该节点,而是将该节点标记为“已删除”。这样,我们只用找到元素并标记,就可以完成删除元素了。如果有相同的元素重新插入,我们可以将该节点找到,并取消删除标记。

    懒惰删除的实现比较简单,可以尝试一下。树所占据的内存空间不会因为删除节点而减小。懒惰节点实际上是用内存空间换取操作的简便性。

  • 相关阅读:
    Abp集成Swagger的最佳实践
    ELK 集中日志分析 windows部署实战
    nuget国内镜像的解决办法
    nginx+Memcached 缓存设计
    OpenSSL windows 下编译
    HTTPS 双向认证构建移动设备安全体系
    ASP.NET vNext on CentOS 7
    XF custom render 各平台实现类
    Xamarin.Forms 简介
    【开篇】认识网络和传输,架构
  • 原文地址:https://www.cnblogs.com/iwangzhengchao/p/9794823.html
Copyright © 2020-2023  润新知