第四章 树(一)

    在这章,我们主要学习二叉查找树这个概念。还包括set和map类的实现和使用。

4.3 查找树ADT——二叉查找树

     二叉查找树的概念:使二叉树成为二叉查找树的性质,对于树中的每个节点X,它的左子树中所有项的值小于X中的项,而它的右子树中所有的项大于X中的项。

      二叉树查找树类的框架如下所示:

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
template< typename Comparable>
class BinarySearchTree
{
public:
    BinarySearchTree();   //构造函数
    BinarySearchTree( const BinarySearchTree &rhs);  //复制构造函数
    ~BinarySearchTree();   //析构函数

     const Comparable &findMin()  const;    // 查找最小的数据
     const Comparable &findMax()  const;   //查找最大的数据
     bool contain( const Comparable &x)  const// 包含
     bool isEmpty()  const;   //  空函数
     void printTree()  const;   //打印树

     void makeEmpty();   // 空
     void insert( const Comparable &x);  //插入
     void remove( const Comparable &x);   //删除

     const BinarySearchTree & operator=( const BinarySearchTree &rhs);   // operator=函数

private:    //私有
     struct BinaryNode   //内部类
    {
        Comparable element;
        BinaryNode *left;
        BinaryNode *right;
        BinaryNode( const Comparable &theElment, BinaryNode *lt, BinaryNode *rt):
            element(theElement), left(lt), right(rt)
        {}
    };
    BinaryNode *root;

     void insert( const Comparable &x, BinaryNode*&t)  const;
     void remove( const Comparable &x, BinaryNode*&t)  const;
    BinaryNode *findMin(BinaryNode *t)  const;
    BinaryNode *findMax(BinaryNode *t)  const;
     bool contains( const Comparable &x, BinaryNode *t)  const;

     void makeEmpty(BinaryNode*&t);
     void printTree(BinaryNode *t)  const;
    BinaryNode *clone(BinaryNode *t)  const;
};
      我们对二叉查找树的一些常规操作函数进行分析。其实二叉树的操作,大都基于递归的实现,巧妙的利用递归能够很好的实现功能。

    二叉树的contains操作,查找时间复杂度为0(logN)。

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
template< typename Comparable>
bool BinarySearchTree::contains( const Comparable &x, BinaryNode *t)  const  //使用递归实现查找包含
{
     if(t ==  NULL)
         return  false;
     else  if(x > t->element)  //在右边查找
    {
         return contains(x, t->right);
    }
     else  if(x < t->element)
    {
         return contains(x, t->left);  //在左边查找
    }
     else
    {
         return  true;
    }
}
      在很多时候,比较函数不能单单只用大于号小于号来表示,二叉查找树的数据类型有可能是字符串等,这个时候需要采用函数对象来实现比较函数。

   

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

template < typename Object,  typename Comparator = less<Object> >
class BinarySearchTree
{
public:
     //same method,with Object replacing Comparable
private:
    BinaryNode *root;
    Comparator isLessThan;
     bool contains( const Comparable &x, BinaryNode *t)  const  //使用递归实现查找包含
    {
         if(t ==  NULL)
             return  false;
         else  if(isLessThan(x, t->element))  //在右边查找
        {
             return contains(x, t->left);
        }
         else  if(isLessThan(t->element, x))
        {
             return contains(x, t->right);  //在左边查找
        }
         else
        {
             return  true;
        }
    }
};

    findMin和findMax分别返回指向树中包含最小元素和最大元素的节点指针。还是采用递归的方式实现

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
BinaryNode *findMin(BinaryNode *t)  const   //使用递归实现查找最小函数。
{
     if(t ==  NULL)
         return  NULL;
     if(t->left =  NULL)
         return t;
     return findMin(t->left);
}

BinaryNode *findMax(BinaryNode *t)  const   //使用非递归的方式查找最大函数
{
     if(t ==  NULL)
         return  NULL;
     while(t->right !=  NULL)
    {
        t = t->right;
    }
     return t;
}

    插入函数,同样采用递归来实现。

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
void insert( const Comparable &x, BinaryNode *&t)  //采用递归插入新的数据
{
     if(t ==  NULL)
        t =  new BinaryNode(x,NULL,NULL)
         if(x < t->element)
            insert(x, t->left);
         else  if(x > t->element)
            insert(x, t->right)
             else
                ; //重复
}

      二叉查找树的一个难点是删除元素。一旦发现要被删除节点,就需要考虑几种可能的情况。

    1、如果节点是一个叶子节点,那么它可以被立即删除。

    2、如果节点有一个儿子,则该节点可以在其父节点调整它的链以绕过该节点后被删除。如下图所示:

                             

     3、复杂的情况是处理具有两个儿子的节点。一般的删除策略是用其右子树的最小数据代替该节点的数据并递归地删除那个节点。因为右子树中的最小节点不可能有左子树,所以第二次remove就很容易了。下图显示了一颗初始的数及其中一个节点被删除后的结果。要被删除的节点是根的左儿子,其键值为2.它被右子树中的最小键值3所代替,然后原节点如前例那样被删除。(惰性删除)

                     

代码实现如下:

     

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void remove( const Comparable &x, BinaryNode *t)
{
     if(t ==  NULL)
         return ;
     if(x < t->element)  //查找
    {
        remove(x, t->left);
    }
     else  if(x > t->element)
    {
        remove(x, t->right);
    }
     else  if(t->left !=  NULL && t->right !=  NULL//双节点
    {
        t->element = findMin(t->right)->element;
        remove(t->element, t->right);
    }
     else  //单节点
    {
        BinaryNode *old = t;
        t = (t->left !=  NULL) ? t->left : t->right;
         delete old;
    }


}


      析构函数和复制赋值操作符

     与往常一样,析构函数调用makeEmpty。公有的makeEmpty则简单地调用私有递归版本的makeEmpty函数。在递归处理t的子树之后,对t调用delete。这样一来,所有的节点就都递归地回收了。注意,最后,t就改为指向NULL,复制赋值操作符,则首先调用makeEmpty来回收内存,然后进行rhs复制,我们使用clone很老套的递归函数来处理这些啰嗦工作。

    

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
~BinarySearch()   //析构
{
    makeEmpty();
}
void makeEmpty(BinaryNode*&t)    //递归空
{
     if(t !=  NULL)
    {
        makeEmpty(t->left);
        makeEmpty(t->right);
         delete t;
    }
    t-> NULL;
}

const BinarySearchTree & operator=( const BinarySearchTree &rhs)   //复制赋值操作符
{
     if( this != &rhs)
    {
        makeEmpty();
        root = clone(rhs.root);
    }
     return * this;
}

BinaryNode *clone(BinaryNode *t)  const   //递归复制
{
     if(t ==  NULL)
         return  NULL;
     return  new BinaryNode(t->element, clone(t->left), clone(t->right));
}


4.4 AVL树

    AVL树是带有平衡条件的二叉查找树。一颗AVL树是每个节点的左子树和右子树的高度最多差1的二叉查找树。在插入节点后,如何保证二叉树的平衡是一个本节的一个难点。

      我们把必须重新平衡的节点叫做a,由于任意节点最多有两个儿子,因此高度不平衡时,a点的两棵子树的高度差2.容易看出,这种平衡可能出现在下面4个情况:

      (1)对a的左儿子的左子树进行一次插入。

      (2)对a的左儿子的右子树进行一次插入。

      (3)对a的右儿子的左子树进行一次插入。

      (4)对a的右儿子的右子树进行一次插入。

      第一种情况是插入发生在“外边”的情形(即左-左,右-右的情况),该情况通过对树的一次单旋转而完成调整。第二种是插入发生在“内部”的情形(即左-右,右-左的情况),该情况采用较为复杂的双旋转来实现。


1、首先我们来讨论一下单旋转的情况。

      先看图吧。下图是RR的情况。

                           

改动其实很简单,先把Y解绑当k2的左孩子,接着k2成为k1的右孩子,代码如下:

        

 C++ Code 
1
2
3
4
5
6
7
8
9
10
/* return pointer to the new root */
static AVLNodePtr singlerotateLL(AVLNodePtr k2)
{
    AVLNodePtr k1 = k2->left;
    k2->left = k1->right;
    k1->right = k2;
    k2->height = Max(height(k2->left), height(k2->right)) +  1;
    k1->height = Max(height(k1->left), k2->height) +  1;
     return k1;
}

      

接着是doublerotateRL()的实现,看下图:

                      

很明显B或者C的插入使得k3(A)不平衡了,那么首先应该是k1和k2逆时针旋转,所以调用

k3->left = singlerotateRR(k3->left);

接着是k3和new child 顺时针旋转,调用singlerotateLL(k3);

so easy, 代码如下:

 C++ Code 
1
2
3
4
5
6

static AVLNodePtr doublerotateRL(AVLNodePtr k3)
{
    k3->left = singlerotateRR(k3->left);
     return singlerotateLL(k3);
}
完整的测试代码如下:

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#include <stdio.h>
#include <stdlib.h>

struct AVLNode;
typedef  struct AVLNode *AVLNodePtr;

struct AVLNode
{
     int element;
    AVLNodePtr left;
    AVLNodePtr right;
     int height;
};

void makeempty(AVLNodePtr T)
{
     if (T ==  NULL)
         return;
     else
    {
        makeempty(T->left);
        makeempty(T->right);
        free(T);
    }
}

static  int height(AVLNodePtr p)
{
     if (p ==  NULL)
         return - 1;
     else
         return p->height;
}

static  int Max( int ln,  int rn)
{
     return ln > rn ? ln : rn;
}

/* return pointer to the new root */
static AVLNodePtr singlerotateLL(AVLNodePtr k2)
{
    AVLNodePtr k1 = k2->left;
    k2->left = k1->right;
    k1->right = k2;
    k2->height = Max(height(k2->left), height(k2->right)) +  1;
    k1->height = Max(height(k1->left), k2->height) +  1;
     return k1;
}

static AVLNodePtr singlerotateRR(AVLNodePtr k1)
{
    AVLNodePtr k2 = k1->right;
    k1->right = k2->left;
    k2->left = k1;
    k1->height = Max(height(k1->left), height(k1->right)) +  1;
    k2->height = Max(k1->height, height(k2->right)) +  1;
     return k2;
}

static AVLNodePtr doublerotateRL(AVLNodePtr k3)
{
    k3->left = singlerotateRR(k3->left);
     return singlerotateLL(k3);
}

static AVLNodePtr doublerotateLR(AVLNodePtr k3)
{
    k3->right = singlerotateLL(k3->right);
     return singlerotateRR(k3);
}


AVLNodePtr insert( int X, AVLNodePtr T)
{
     if (T ==  NULL)
    {
         /* create and return a one-node tree */
        T = (AVLNodePtr)malloc( sizeof( struct AVLNode));
         if (T ==  NULL)
        {
            printf( "out of space!");
            exit( 1);
        }
         else
        {
            T->element = X;
            T->height =  0;
            T->left = T->right =  NULL;
        }
    }

     else  if (X < T->element)
    {
        T->left = insert(X, T->left);
         if (height(T->left) - height(T->right) ==  2)
        {
             if (X < T->left->element)
                T = singlerotateLL(T);
             else
                T = doublerotateRL(T);
        }
    }

     else  if (X > T->element)
    {
        T->right = insert(X, T->right);
         if (height(T->right) - height(T->left) ==  2)
        {
             if (X > T->right->element)
                T = singlerotateRR(T);
             else
                T = doublerotateLR(T);
        }
    }
     /* else X is in the tree already; we'll do nothing */
    T->height = Max(height(T->left), height(T->right)) +  1;
     return T;
}

void inorder(AVLNodePtr T)
{
     if (T ==  NULL)
         return;
     else
    {
        inorder(T->left);
        printf( "%d ", T->element);
        inorder(T->right);
    }
}

int main( void)
{
     int arr[] = { 32145671615141312111089};
    AVLNodePtr T =  NULL;
     for ( int i =  0; i <  sizeof(arr) /  sizeof(arr[ 0]); i++)
        T = insert(arr[i], T);

    inorder(T);
    makeempty(T);
     return  0;
}

代码将数组元素插入后,中序遍历后输出,即1~16的顺序排列。

注意:输入数组元素就不要搞成有序的了,如果代码中没有调整的实现,整个树就是个右斜树,但即使实现了调整,也会使得每插入一次就调整一次,何必内耗啊。

 很显然,平衡二叉树的优势在于不会出现普通二叉查找树的最差情况。其查找的时间复杂度为O(logN)。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值