树的介绍部分摘取自博文二叉查找树

1.1 树的定义

  树是一种数据结构,它是由n(n>=1)个有限节点组成一个具有层次关系的集合。

  "《算法导论》之‘树’":二叉查找树

  把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:
  (1) 每个节点有零个或多个子节点;
  (2) 没有父节点的节点称为根节点;
  (3) 每一个非根节点有且只有一个父节点;
  (4) 除了根节点外,每个子节点可以分为多个不相交的子树。

1.2 树的基本术语

  若一个结点有子树,那么该结点称为子树根的"双亲",子树的根是该结点的"孩子"。有相同双亲的结点互为"兄弟"。一个结点的所有子树上的任何结点都是该结点的后裔。从根结点到某个结点的路径上的所有结点都是该结点的祖先。

  结点的度:结点拥有的子树的数目。
  叶子:度为零的结点。
  分支结点:度不为零的结点。
  树的度:树中结点的最大的度。

  层次:根结点的层次为1,其余结点的层次等于该结点的双亲结点的层次加1。
  树的高度:树中结点的最大层次。
  无序树:如果树中结点的各子树之间的次序是不重要的,可以交换位置。
  有序树:如果树中结点的各子树之间的次序是重要的, 不可以交换位置。
  森林:0个或多个不相交的树组成。对森林加上一个根,森林即成为树;删去根,树即成为森林。

2. 二叉树介绍

2.1 二叉树的定义

  二叉树是每个节点最多有两个子树的树结构。它有五种基本形态:二叉树可以是空集;根可以有空的左子树或右子树;或者左、右子树皆为空。

  "《算法导论》之‘树’":二叉查找树

2.2 二叉树的性质

  二叉树有以下几个性质:TODO(上标和下标)
  性质1:二叉树第i层上的结点数目最多为 2{i-1} (i≥1)。
  性质2:深度为k的二叉树至多有2{k}-1个结点(k≥1)。
  性质3:包含n个结点的二叉树的高度至少为log2 (n+1)
  性质4:在任意一棵二叉树中,若终端结点的个数为n0,度为2的结点数为n2,则n0=n2+1

  性质1:二叉树第i层上的结点数目最多为 2{i-1} (i≥1)

  证明:下面用"数学归纳法"进行证明。
        (01) 当i=1时,第i层的节点数目为2{i-1}=2{0}=1。因为第1层上只有一个根结点,所以命题成立。
        (02) 假设当i>1,第i层的节点数目为2{i-1}。这个是根据(01)推断出来的!
               下面根据这个假设,推断出"第(i+1)层的节点数目为2{i}"即可。
               由于二叉树的每个结点至多有两个孩子,故"第(i+1)层上的结点数目" 最多是 "第i层的结点数目的2倍"。即,第(i+1)层上的结点数目最大值=2×2{i-1}=2{i}
                故假设成立,原命题得证!

  性质2:深度为k的二叉树至多有2{k}-1个结点(k≥1)

  证明:在具有相同深度的二叉树中,当每一层都含有最大结点数时,其树中结点数最多。利用"性质1"可知,深度为k的二叉树的结点数至多为:
           20+21+…+2k-1=2k-1
           故原命题得证!

  性质3:包含n个结点的二叉树的高度至少为log2 (n+1)

  证明:根据"性质2"可知,高度为h的二叉树最多有2{h}–1个结点。反之,对于包含n个节点的二叉树的高度至少为log2(n+1)。

  性质4:在任意一棵二叉树中,若终端结点的个数为n0,度为2的结点数为n2,则n0=n2+1

  证明:因为二叉树中所有结点的度数均不大于2,所以结点总数(记为n)="0度结点数(n0)" + "1度结点数(n1)" + "2度结点数(n2)"。由此,得到等式一。
         (等式一) n=n0+n1+n2
      另一方面,0度结点没有孩子,1度结点有一个孩子,2度结点有两个孩子,故二叉树中孩子结点总数是:n1+2n2。此外,只有根不是任何结点的孩子。故二叉树中的结点总数又可表示为等式二。
         (等式二) n=n1+2n2+1
        由(等式一)和(等式二)计算得到:n0=n2+1。原命题得证!

2.3 满二叉树,完全二叉树和二叉查找树

    (1)满二叉树

  定义:高度为h,并且由2{h} –1个结点的二叉树,被称为满二叉树。

  "《算法导论》之‘树’":二叉查找树

 

    (2)完全二叉树

  定义:一棵二叉树中,只有最下面两层结点的度可以小于2,并且最下一层的叶结点集中在靠左的若干位置上。这样的二叉树称为完全二叉树。
  特点:叶子结点只能出现在最下层和次下层,且最下层的叶子结点集中在树的左部。显然,一棵满二叉树必定是一棵完全二叉树,而完全二叉树未必是满二叉树。

  "《算法导论》之‘树’":二叉查找树

   (3)二叉查找树

  定义:二叉查找树(Binary Search Tree),又被称为二叉搜索树。设x为二叉查找树中的一个结点,x节点包含关键字key,节点x的key值记为key[x]。如果y是x的左子树中的一个结点,则key[y] < key[x];如果y是x的右子树的一个结点,则key[y] > key[x]。

  "《算法导论》之‘树’":二叉查找树

    

  在二叉查找树中:

  1)若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;

  2)任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;

  3)任意节点的左、右子树也分别为二叉查找树。

  4)没有键值相等的节点(no duplicate nodes)。

3. 二叉查找树操作

3.1 查找

  在二叉查找树中查找一个给定的关键字k的过程与二分查找很类似,根据二叉查找树在的关键字存放的特征,很容易得出查找过程:首先是关键字k与树根的关键字进行比较,如果k大比根的关键字大,则在根的右子树中查找,否则在根的左子树中查找,重复此过程,直到找到与遇到空结点为止。例如下图所示的查找关键字13的过程:(查找过程每次在左右子树中做出选择,减少一半的工作量)

  "《算法导论》之‘树’":二叉查找树

  《算法导论》中给出的递归和非递归的伪代码如下:

1 TREE_SEARCH(x,k)
2   if x=NULL or k=key[x]
3       then return x
4   if(k<key[x])
5       then return TREE_SEARCH(left[x],k)
6   else
7       then return TREE_SEARCH(right[x],k)
1 ITERATIVE_TREE_SEARCH(x,k)
2   while x!=NULL and k!=key[x]
3       do if k<key[x]
4               then x=left[x]
5            else
6               then x=right[x]
7    return x

3.2 查找最大关键字和最小关键字

  根据二叉查找树的特征,很容易查找出最大和最小关键字。查找二叉树中的最小关键字:从根结点开始,沿着各个节点的left指针查找下去,直到遇到NULL时结束。如果一个结点x无左子树,则以x为根的子树中,最小关键字就是key[x]。查找二叉树中的最大关键字:从根结点开始,沿着各个结点的right指针查找下去,直到遇到NULL时结束。书中给出了查找最大最小关键字的伪代码:

  《算法导论》中给出的伪代码如下:

1 TREE_MINMUM(x)
2     while left[x] != NULL
3         do x=left[x]
4     return x 
1 TREE_MAXMUM(x)
2     while right[x] != NULL
3         do x= right[x]
4     return x

3.3 前驱和后继

  给定一个二叉查找树中的结点,找出在中序遍历顺序下某个节点的前驱和后继。如果树中所有关键字都不相同,则某一结点x的前驱就是小于key[x]的所有关键字中最大的那个结点,后继即是大于key[x]中的所有关键字中最小的那个结点。根据二叉查找树的结构和性质,不用对关键字做任何比较,就可以找到某个结点的前驱和后继。

  查找前驱步骤:先判断x是否有左子树,如果有则在left[x]中查找关键字最大的结点,即是x的前驱。如果没有左子树,则从x继续向上执行此操作,直到遇到某个结点是其父节点的右孩子结点。例如下图查找结点7的前驱结点6过程:

  "《算法导论》之‘树’":二叉查找树

  查找后继步骤:先判断x是否有右子树,如果有则在right[x]中查找关键字最小的结点,即使x的后继。如果没有右子树,则从x的父节点开始向上查找,直到遇到某个结点是其父结点的左儿子的结点时为止。例如下图查找结点13的后继结点15的过程:

  "《算法导论》之‘树’":二叉查找树

  《算法导论》中中给出了求x结点后继结点的伪代码:

1 TREE_PROCESSOR(x)
2     if right[x] != NULL
3         then return TREE_MINMUM(right(x))
4     y=parent[x]
5     while y!= NULL and x ==right[y]
6            do x = y
7                y=parent[y]
8     return y

    定理:对一棵高度为h的二叉查找,动态集合操作SEARCH、MINMUM、MAXMUM、SUCCESSOR、PROCESSOR等的运行时间均为O(h)。

3.4 插入和删除

  插入和删除会引起二叉查找表示的动态集合的变化,难点在在插入和删除的过程中要保持二叉查找树的性质。插入过程相当来说要简单一些,删除结点比较复杂。

  (1)插入

  插入结点的位置对应着查找过程中查找不成功时候的结点位置,因此需要从根结点开始查找带插入结点位置,找到位置后插入即可。下图所示插入结点过程:

  "《算法导论》之‘树’":二叉查找树

  《算法导论》中给出了插入过程的伪代码:

 1 TREE_INSERT(T, z)
 2     y = NULL;
 3     x = root[T]
 4     while x != NULL
 5         do y = x
 6     if key[z] < key[x]
 7         then x = left[x]
 8     else  
 9         x = right[x]
10     parent[z] = y
11     if y = NULL
12         then root[T] = z
13     else if key[z]>key[y]
14         then  keft[y] = z
15     else   right[y] = z

  插入过程运行时间为O(h),h为树的高度。

  (2)删除

  从二叉查找树中删除给定的结点z,分三种情况讨论:

  <1>结点z没有左右子树,则修改其父节点p[z],使其为NULL。删除过程如下图所示:

  "《算法导论》之‘树’":二叉查找树

  <2>如果结点z只有一个子树(左子树或者右子树),通过在其子结点与父节点建立一条链来删除z。删除过程如下图所示:

  "《算法导论》之‘树’":二叉查找树

  <3>如果z有两个子女,那么找z的后继y(一定在z的右子树中),并让y占据树中z的位置。z的原来右子树部分成为y的新的右子树,并且z的左子树成为y的新的左子树。

  "《算法导论》之‘树’":二叉查找树

  《算法导论》中给出了删除过程的伪代码:

 1 TREE_DELETE(T, z)
 2     if left[z] == NULL or right[z] == NULL
 3         then y = z
 4     else  y = TREE_SUCCESSOR(z)
 5 
 6     if left[y] != NULL
 7         then x = left[y]
 8     else  x = right[y]
 9 
10     if x != NULL
11         then parent[x] = parent[y]
12     if p[y] == NULL
13         then root[T] = x
14     else if y = left[[prarnt[y]]
15         then left[parent[y]] = x
16     else  right[parent[y]] = x
17 
18     if y != z
19         then key[z] = key[y]
20     copy y's data into z
21     return y
View Code

相关文章: