树、二叉树、AVL树

树、二叉树、AVL树

                                                                                                                                                                  (注:本文转自http://www.cnblogs.com/skywang12345/p/3576969.html)

一、树

1. 树的定义

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

《树、二叉树、AVL树》

把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:

(01) 每个节点有零个或多个子节点;

(02) 没有父节点的节点称为根节点;

(03) 每一个非根节点有且只有一个父节点;

(04) 除了根节点外,每个子节点可以分为多个不相交的子树。

 

2. 树的基本术语

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

结点的度:结点拥有的子树的数目。

叶子:度为零的结点。

分支结点:度不为零的结点。

树的度:树中结点的最大的度。

层次:根结点的层次为1,其余结点的层次等于该结点的双亲结点的层次加1。

树的高度:树中结点的最大层次。

无序树:如果树中结点的各子树之间的次序是不重要的,可以交换位置。

有序树:如果树中结点的各子树之间的次序是重要的, 不可以交换位置。

森林:0个或多个不相交的树组成。对森林加上一个根,森林即成为树;删去根,树即成为森林。

 

二、二叉树

1. 二叉树的定义

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

 《树、二叉树、AVL树》

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

 

2.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.2 性质2:深度为k的二叉树至多有2{k}-1个结点(k≥1)

证明:在具有相同深度的二叉树中,当每一层都含有最大结点数时,其树中结点数最多。利用”性质1″可知,深度为k的二叉树的结点数至多为:

           20+21+…+2k-1=2k-1

故原命题得证!

 

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

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

 

2.4 性质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。原命题得证!

 

3. 满二叉树,完全二叉树和二叉查找树

3.1 满二叉树

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

 《树、二叉树、AVL树》

3.2 完全二叉树

定义:一棵二叉树中,只有最下面两层结点的度可以小于2,并且最下一层的叶结点集中在靠左的若干位置上。这样的二叉树称为完全二叉树。

特点:叶子结点只能出现在最下层和次下层,且最下层的叶子结点集中在树的左部。显然,一棵满二叉树必定是一棵完全二叉树,而完全二叉树未必是满二叉树。

 《树、二叉树、AVL树》

3.3 二叉查找树

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

《树、二叉树、AVL树》

在二叉查找树中:

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

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

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

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

在实际应用中,二叉查找树的使用比较多。下面,用C语言实现二叉查找树。

 

二叉查找树的C实现

1. 节点定义

1.1 节点定义

typedef intType;

typedef struct BSTreeNode{

    Type   key;                    // 关键字(键值)

    structBSTreeNode *left;    // 左孩子

    structBSTreeNode *right;    // 右孩子

    structBSTreeNode *parent;    // 父结点

}Node, *BSTree;

 

二叉查找树的节点包含的基本信息:

(01) key     它是关键字,是用来对二叉查找树的节点进行排序的。

(02) left     它指向当前节点的左孩子。

(03) right    — 它指向当前节点的右孩子。

(04) parent它指向当前节点的父结点。

 

1.2 创建节点

创建节点的代码

static Node* create_bstree_node(Typekey, Node *parent, Node *left, Node* right)

{

    Node* p;

    if ((p = (Node *)malloc(sizeof(Node))) == NULL)

        return NULL;

    p->key = key;

    p->left = left;

    p->right = right;

    p->parent = parent;

 

    return p;

}

 

2 遍历

这里讲解前序遍历中序遍历后序遍历3种方式。

2.1 前序遍历

若二叉树非空,则执行以下操作:

(01) 访问根结点;

(02) 先序遍历左子树;

(03) 先序遍历右子树。

前序遍历代码

voidpreorder_bstree(BSTree tree)

{

    if(tree !=NULL)

    {

        printf(“%d “, tree->key);

       preorder_bstree(tree->left);

       preorder_bstree(tree->right);

    }

}

 

2.2 中序遍历

若二叉树非空,则执行以下操作:

(01) 中序遍历左子树;

(02) 访问根结点;

(03) 中序遍历右子树。

中序遍历代码

voidinorder_bstree(BSTree tree)

{

    if(tree !=NULL)

    {

       inorder_bstree(tree->left);

        printf(“%d “, tree->key);

       inorder_bstree(tree->right);

    }

}

 

2.3 后序遍历

若二叉树非空,则执行以下操作:

(01) 后序遍历左子树;

(02) 后序遍历右子树;

(03) 访问根结点。

后序遍历代码

voidpostorder_bstree(BSTree tree)

{

    if(tree !=NULL)

    {

       postorder_bstree(tree->left);

       postorder_bstree(tree->right);

        printf(“%d “, tree->key);

    }

}

 

下面通过例子对这些遍历方式进行介绍。

《树、二叉树、AVL树》

对于上面的二叉树而言,

(01) 前序遍历结果: 3 1 2 5 4 6

(02) 中序遍历结果: 1 2 3 4 5 6 

(03) 后序遍历结果: 2 1 4 6 5 3

 

3. 查找

递归版本的代码

Node* bstree_search(BSTree x, Type key)

{

    if (x==NULL || x->key==key)

        return x;

    if (key < x->key)

        return bstree_search(x->left, key);

    else

        returnbstree_search(x->right, key);

}

 

非递归版本的代码

Node* iterative_bstree_search(BSTree x, Typekey)

{

    while ((x!=NULL) &&(x->key!=key))

    {

        if (key < x->key)

            x = x->left;

        else

            x = x->right;

    }

    return x;

}

 

4. 最大值和最小值

查找最大值的代码

Node* bstree_maximum(BSTree tree)

{

    if (tree ==NULL)

        return NULL;

    while(tree->right != NULL)

        tree = tree->right;

    return tree;

}

 

查找最小值的代码

Node* bstree_minimum(BSTree tree)

{

    if (tree ==NULL)

        return NULL;

    while(tree->left != NULL)

        tree = tree->left;

    return tree;

}

 

5. 前驱和后继

节点的前驱:是该节点的左子树中的最大节点。

节点的后继:是该节点的右子树中的最小节点。

查找前驱节点的代码

Node* bstree_predecessor(Node *x)

{

    // 如果x存在左孩子,则”x的前驱结点”为”以其左孩子为根的子树的最大结点”。

    if(x->left != NULL)

        return bstree_maximum(x->left);

 

    // 如果x没有左孩子。则x有以下两种可能:

    // (01) x是”一个右孩子”,则”x的前驱结点”为 “它的父结点”。

    // (01) x是”一个左孩子”,则查找”x的最低的父结点,并且该父结点要具有右孩子”,找到的这个”最低的父结点”就是”x的前驱结点”。

    Node* y = x->parent;

    while ((y!=NULL) && (x==y->left))

    {

        x = y;

        y = y->parent;

    }

    return y;

}

 

查找后继节点的代码

Node* bstree_successor(Node *x)

{

    // 如果x存在右孩子,则”x的后继结点”为”以其右孩子为根的子树的最小结点”。

    if(x->right != NULL)

        return bstree_minimum(x->right);

 

    // 如果x没有右孩子。则x有以下两种可能:

    // (01) x是”一个左孩子”,则”x的后继结点”为 “它的父结点”。

    // (02) x是”一个右孩子”,则查找”x的最低的父结点,并且该父结点要具有左孩子”,找到的这个”最低的父结点”就是”x的后继结点”。

    Node* y = x->parent;

    while ((y!=NULL) && (x==y->right))

    {

        x = y;

        y = y->parent;

    }

 

    return y;

}

 

6. 插入

插入节点的代码

static Node* bstree_insert(BSTree tree,Node *z)

{

    Node *y = NULL;

    Node *x = tree;

 

    // 查找z的插入位置

    while(x != NULL)

    {

        y = x;

        if (z->key < x->key)

            x = x->left;

        else

            x = x->right;

    }

 

    z->parent = y;

    if (y==NULL)

        tree = z;

    else if(z->key < y->key)

        y->left = z;

    else

        y->right = z;

 

    return tree;

}

 

Node*insert_bstree(BSTree tree, Type key)

{

    Node *z;    // 新建结点

    // 如果新建结点失败,则返回。

    if((z=create_bstree_node(key, NULL, NULL, NULL)) ==NULL)

        return tree;

 

    return bstree_insert(tree,z);

}

 

bstree_insert(tree, z)是内部函数,它的作用是:将结点(z)插入到二叉树(tree)中,并返回插入节点后的根节点。

insert_bstree(tree, key)是对外接口,它的作用是:在树中新增节点,key是节点的值;并返回插入节点后的根节点。

 

注:本文实现的二叉查找树是允许插入相同键值的节点的!若用户不希望插入相同键值的节点,将bstree_insert()修改为以下代码即可。

 

static Node* bstree_insert(BSTree tree,Node *z)

{

    Node *y = NULL;

    Node *x = tree;

 

    // 查找z的插入位置

    while(x != NULL)

    {

        y = x;

        if (z->key < x->key)

            x =x->left;

        else  if (z->key > x->key)

            x =x->right;

        else

        {

           free(z); // 释放之前分配的系统。

            returntree;

        }

    }

 

    z->parent = y;

    if (y==NULL)

        tree = z;

    else if(z->key < y->key)

        y->left = z;

    else

        y->right = z;

 

    return tree;

}

 

7. 删除

删除节点的代码

static Node* bstree_delete(BSTree tree,Node *z)

{

    Node *x=NULL;

    Node *y=NULL;

 

    if ((z->left == NULL) ||(z->right == NULL) )

        y = z;

    else

        y = bstree_successor(z);

 

    if (y->left != NULL)

        x = y->left;

    else

        x = y->right;

 

    if (x !=NULL)

        x->parent = y->parent;

 

    if (y->parent == NULL)

        tree = x;

    else if(y == y->parent->left)

        y->parent->left = x;

    else

        y->parent->right = x;

 

    if (y !=z)

        z->key = y->key;

 

    if (y!=NULL)

        free(y);

 

    return tree;

}

 

Node*delete_bstree(BSTree tree, Type key)

{

    Node *z, *node;

 

    if ((z = bstree_search(tree, key))!= NULL)

        tree = bstree_delete(tree, z);

 

    return tree;

}

 

bstree_delete(tree, z)是内部函数,它的作用是:删除二叉树(tree)中的节点(z),并返回删除节点后的根节点。
delete_bstree(tree, key)是对外接口,它的作用是:在树中查找键值为key的节点,找到的话就删除该节点;并返回删除节点后的根节点。

 

8. 打印

打印二叉树的代码

void print_bstree(BSTree tree, Typekey, int direction)

{

    if(tree !=NULL)

    {

        if(direction==0)    // tree是根节点

            printf(“%2d is root\n”, tree->key);

        else                //tree是分支节点

            printf(“%2d is %2d’s %6s child\n”, tree->key,key, direction==1?“right”: “left”);

 

       print_bstree(tree->left, tree->key, –1);

       print_bstree(tree->right,tree->key,  1);

    }

}

 

print_bstree(tree, key, direction)的作用是打印整颗二叉树(tree)。其中,tree是二叉树节点,key是二叉树的键值,而direction表示该节点的类型:

direction为 0,表示该节点是根节点;

direction为-1,表示该节点是它的父结点的左孩子;

direction为 1,表示该节点是它的父结点的右孩子。

 

9. 销毁二叉树

销毁二叉树的代码

voiddestroy_bstree(BSTree tree)

{

    if (tree==NULL)

        return ;

 

    if (tree->left != NULL)

       destroy_bstree(tree->left);

    if (tree->right != NULL)

       destroy_bstree(tree->right);

 

    free(tree);

}

 

完整的实现代码

二叉查找树的头文件(bstree.h)

#ifndef _BINARY_SEARCH_TREE_H_

#define _BINARY_SEARCH_TREE_H_

 

typedef int Type;

 

typedef struct BSTreeNode{

   Type   key;                    // 关键字(键值)

   struct BSTreeNode *left;    // 左孩子

   struct BSTreeNode *right;    // 右孩子

   struct BSTreeNode *parent;    // 父结点

}Node, *BSTree;

 

// 前序遍历”二叉树”

void preorder_bstree(BSTree tree);

// 中序遍历”二叉树”

void inorder_bstree(BSTree tree);

// 后序遍历”二叉树”

void postorder_bstree(BSTree tree);

 

// (递归实现)查找”二叉树x”中键值为key的节点

Node* bstree_search(BSTree x, Type key);

// (非递归实现)查找”二叉树x”中键值为key的节点

Node* iterative_bstree_search(BSTree x,Type key);

 

// 查找最小结点:返回tree为根结点的二叉树的最小结点。

Node* bstree_minimum(BSTree tree);

// 查找最大结点:返回tree为根结点的二叉树的最大结点。

Node* bstree_maximum(BSTree tree);

 

// 找结点(x)的后继结点。即,查找”二叉树中数据值大于该结点”的”最小结点”。

Node* bstree_successor(Node *x);

// 找结点(x)的前驱结点。即,查找”二叉树中数据值小于该结点”的”最大结点”。

Node* bstree_predecessor(Node *x);

 

// 将结点插入到二叉树中,并返回根节点

Node* insert_bstree(BSTree tree, Typekey);

 

// 删除结点(key为节点的值),并返回根节点

Node* delete_bstree(BSTree tree, Typekey);

 

// 销毁二叉树

void destroy_bstree(BSTree tree);

 

// 打印二叉树

void print_bstree(BSTree tree, Type key,int direction);

 

#endif

二叉查找树的实现文件(bstree.c)

/**

 * 二叉搜索树(C语言): C语言实现的二叉搜索树。

 *

 * @author skywang

 * @date 2013/11/07

 */

 

#include <stdio.h>

#include <stdlib.h>

#include “bstree.h”

 

 

/*

 * 前序遍历”二叉树”

 */

void preorder_bstree(BSTree tree)

{

    if(tree !=NULL)

    {

       printf(“%d “, tree->key);

       preorder_bstree(tree->left);

       preorder_bstree(tree->right);

    }

}

 

/*

 * 中序遍历”二叉树”

 */

void inorder_bstree(BSTree tree)

{

    if(tree !=NULL)

    {

       inorder_bstree(tree->left);

       printf(“%d “, tree->key);

       inorder_bstree(tree->right);

    }

}

 

/*

 * 后序遍历”二叉树”

 */

void postorder_bstree(BSTree tree)

{

    if(tree !=NULL)

    {

       postorder_bstree(tree->left);

       postorder_bstree(tree->right);

       printf(“%d “, tree->key);

    }

}

 

/*

 * (递归实现)查找”二叉树x”中键值为key的节点

 */

Node* bstree_search(BSTree x, Type key)

{

    if (x==NULL ||x->key==key)

        return x;

 

    if (key <x->key)

        returnbstree_search(x->left, key);

    else

        returnbstree_search(x->right, key);

}

 

/*

 * (非递归实现)查找”二叉树x”中键值为key的节点

 */

Node* iterative_bstree_search(BSTree x, Type key)

{

    while((x!=NULL) && (x->key!=key))

    {

        if (key< x->key)

            x =x->left;

        else

            x =x->right;

    }

 

    return x;

}

 

/*

 * 查找最小结点:返回tree为根结点的二叉树的最小结点。

 */

Node* bstree_minimum(BSTree tree)

{

    if (tree ==NULL)

        returnNULL;

 

   while(tree->left != NULL)

        tree =tree->left;

    return tree;

}

 

/*

 * 查找最大结点:返回tree为根结点的二叉树的最大结点。

 */

Node* bstree_maximum(BSTree tree)

{

    if (tree ==NULL)

        returnNULL;

 

   while(tree->right != NULL)

        tree =tree->right;

    return tree;

}

 

/*

 * 找结点(x)的后继结点。即,查找”二叉树中数据值大于该结点”的”最小结点”。

 */

Node* bstree_successor(Node *x)

{

    // 如果x存在右孩子,则”x的后继结点”为 “以其右孩子为根的子树的最小结点”。

    if (x->right!= NULL)

        returnbstree_minimum(x->right);

 

    // 如果x没有右孩子。则x有以下两种可能:

    // (01) x是”一个左孩子”,则”x的后继结点”为 “它的父结点”。

    // (02) x是”一个右孩子”,则查找”x的最低的父结点,并且该父结点要具有左孩子”,找到的这个”最低的父结点”就是”x的后继结点”。

    Node* y =x->parent;

    while((y!=NULL) && (x==y->right))

    {

        x = y;

        y =y->parent;

    }

 

    return y;

}

 

/*

 * 找结点(x)的前驱结点。即,查找”二叉树中数据值小于该结点”的”最大结点”。

 */

Node* bstree_predecessor(Node *x)

{

    // 如果x存在左孩子,则”x的前驱结点”为 “以其左孩子为根的子树的最大结点”。

    if (x->left!= NULL)

        returnbstree_maximum(x->left);

 

    // 如果x没有左孩子。则x有以下两种可能:

    // (01) x是”一个右孩子”,则”x的前驱结点”为 “它的父结点”。

    // (01) x是”一个左孩子”,则查找”x的最低的父结点,并且该父结点要具有右孩子”,找到的这个”最低的父结点”就是”x的前驱结点”。

    Node* y =x->parent;

    while((y!=NULL) && (x==y->left))

    {

        x = y;

        y =y->parent;

    }

 

    return y;

}

 

/*

 * 创建并返回二叉树结点。

 *

 * 参数说明:

 *     key 是键值。

 *     parent 是父结点。

 *     left 是左孩子。

 *     right 是右孩子。

 */

static Node* create_bstree_node(Type key, Node *parent,Node *left, Node* right)

{

    Node* p;

 

    if ((p = (Node*)malloc(sizeof(Node))) == NULL)

        returnNULL;

    p->key =key;

    p->left =left;

    p->right =right;

    p->parent =parent;

 

    return p;

}

 

/*

 * 将结点插入到二叉树中

 *

 * 参数说明:

 *     tree 二叉树的根结点

 *     z 插入的结点

 * 返回值:

 *     根节点

 */

static Node* bstree_insert(BSTree tree, Node *z)

{

    Node *y = NULL;

    Node *x = tree;

 

    // 查找z的插入位置

    while (x !=NULL)

    {

        y = x;

        if(z->key < x->key)

            x =x->left;

        else

            x =x->right;

    }

 

    z->parent =y;

    if (y==NULL)

        tree = z;

    else if(z->key < y->key)

        y->left= z;

    else

        y->right= z;

 

    return tree;

}

 

/*

 * 新建结点(key),并将其插入到二叉树中

 *

 * 参数说明:

 *     tree 二叉树的根结点

 *     key 插入结点的键值

 * 返回值:

 *     根节点

 */

Node* insert_bstree(BSTree tree, Type key)

{

    Node *z;    // 新建结点

 

    // 如果新建结点失败,则返回。

    if((z=create_bstree_node(key, NULL, NULL, NULL)) == NULL)

        returntree;

 

    returnbstree_insert(tree, z);

}

 

/*

 * 删除结点(z),并返回根节点

 *

 * 参数说明:

 *     tree 二叉树的根结点

 *     z 删除的结点

 * 返回值:

 *     根节点

 */

static Node* bstree_delete(BSTree tree, Node *z)

{

    Node *x=NULL;

    Node *y=NULL;

 

    if ((z->left== NULL) || (z->right == NULL) )

        y = z;

    else

        y =bstree_successor(z);

 

    if (y->left!= NULL)

        x =y->left;

    else

        x =y->right;

 

    if (x != NULL)

       x->parent = y->parent;

 

    if(y->parent == NULL)

        tree = x;

    else if (y ==y->parent->left)

       y->parent->left = x;

    else

       y->parent->right = x;

 

    if (y != z)

        z->key =y->key;

 

    if (y!=NULL)

        free(y);

 

    return tree;

}

 

/*

 * 删除结点(key为节点的键值),并返回根节点

 *

 * 参数说明:

 *     tree 二叉树的根结点

 *     z 删除的结点

 * 返回值:

 *     根节点

 */

Node* delete_bstree(BSTree tree, Type key)

{

    Node *z, *node;

 

    if ((z =bstree_search(tree, key)) != NULL)

        tree =bstree_delete(tree, z);

 

    return tree;

}

 

/*

 * 销毁二叉树

 */

void destroy_bstree(BSTree tree)

{

    if (tree==NULL)

        return ;

 

    if(tree->left != NULL)

       destroy_bstree(tree->left);

    if(tree->right != NULL)

       destroy_bstree(tree->right);

 

    free(tree);

}

 

/*

 * 打印”二叉树”

 *

 * tree       — 二叉树的节点

 * key        — 节点的键值

 * direction  —  0,表示该节点是根节点;

 *               -1,表示该节点是它的父结点的左孩子;

 *                1,表示该节点是它的父结点的右孩子。

 */

void print_bstree(BSTree tree, Type key, int direction)

{

    if(tree !=NULL)

    {

       if(direction==0)    // tree是根节点

           printf(“%2d is root\n”, tree->key);

        else                // tree是分支节点

           printf(“%2d is %2d’s %6s child\n”, tree->key, key, direction==1?”right”: “left”);

 

       print_bstree(tree->left, tree->key, -1);

       print_bstree(tree->right,tree->key,  1);

    }

}

二叉查找树的测试程序(btree_test.c)

/**

 * C 语言: 二叉查找树

 *

 * @author skywang

 * @date 2013/11/07

 */

 

#include <stdio.h>

#include “bstree.h”

 

static int arr[]= {1,5,4,3,2,6};

#define TBL_SIZE(a) ( (sizeof(a)) / (sizeof(a[0])) )

 

void main()

{

    int i, ilen;

    BSTreeroot=NULL;

 

    printf(“==依次添加: “);

    ilen =TBL_SIZE(arr);

    for(i=0;i<ilen; i++)

    {

       printf(“%d “, arr[i]);

        root =insert_bstree(root, arr[i]);

    }

 

   printf(“\n== 前序遍历: “);

   preorder_bstree(root);

 

   printf(“\n== 中序遍历: “);

   inorder_bstree(root);

 

   printf(“\n== 后序遍历: “);

   postorder_bstree(root);

    printf(“\n”);

 

    printf(“==最小值: %d\n”, bstree_minimum(root)->key);

    printf(“==最大值: %d\n”, bstree_maximum(root)->key);

    printf(“==树的详细信息: \n”);

   print_bstree(root, root->key, 0);

 

   printf(“\n== 删除根节点: %d”,arr[3]);

    root =delete_bstree(root, arr[3]);

 

   printf(“\n== 中序遍历: “);

   inorder_bstree(root);

   printf(“\n”);

 

    // 销毁二叉树

   destroy_bstree(root);

}

 

二叉查找树的C测试程序

上面的btree_test.c是二叉查找树的测试程序,它的运行结果如下:

== 依次添加: 15 4 3 2 6

== 前序遍历: 15 4 3 2 6

== 中序遍历: 12 3 4 5 6

== 后序遍历: 23 4 6 5 1

== 最小值: 1

== 最大值: 6

==树的详细信息:

 1 is root

 5 is  1‘s  right child

 4is  5‘s   left child

 3is  4‘s   left child

 2is  3‘s   left child

 6is  5‘s  right child

 

== 删除根节点: 3

== 中序遍历: 12 4 5 6

 

 

下面对测试程序的流程进行分析!

(01) 新建”二叉查找树”root。

(02) 向二叉查找树中依次插入1,5,4,3,2,6。如下图所示:

《树、二叉树、AVL树》

 

(03) 打印树的信息

插入1,5,4,3,2,6之后,得到的二叉查找树如下:

《树、二叉树、AVL树》

前序遍历结果: 1 5 4 3 2 6

中序遍历结果: 1 2 3 4 5 6

后序遍历结果: 2 3 4 6 5 1

最小值是1,而最大值是6。

 

(04) 删除节点3。如下图所示:

《树、二叉树、AVL树》

 

(05) 重新遍历该二叉查找树。

中序遍历结果: 1 2 4 5 6

 

三、AVL树

AVL树的介绍

AVL树是根据它的发明者G.M. Adelson-Velsky和E.M.Landis命名的。

它是最先发明的自平衡二叉查找树,也被称为高度平衡树。相比于”二叉查找树”,它的特点是:AVL树中任何节点的两个子树的高度最大差别为1。 

《树、二叉树、AVL树》

上面的两张图片,左边的是AVL树,它的任何节点的两个子树的高度差别都<=1;而右边的不是AVL树,因为7的两颗子树的高度相差为2(以2为根节点的树的高度是3,而以8为根节点的树的高度是1)。

AVL树的查找、插入和删除在平均和最坏情况下都是O(logn)

如果在AVL树中插入或删除节点后,使得高度之差大于1。此时,AVL树的平衡状态就被破坏,它就不再是一棵二叉树;为了让它重新维持在一个平衡状态,就需要对其进行旋转处理。学AVL树,重点的地方也就是它的旋转算法;在后文的介绍中,再来对它进行详细介绍。

 

AVL树的C实现

1. 节点

1.1 定义

typedef intType;

 

typedef struct AVLTreeNode{

    Type key;                    // 关键字(键值)

    int height;

    struct AVLTreeNode *left;    // 左孩子

    structAVLTreeNode *right;    // 右孩子

}Node, *AVLTree;

AVL树的节点包括的几个组成对象:
(01) key — 是关键字,是用来对AVL树的节点进行排序的。
(02) left — 是左孩子。
(03) right — 是右孩子。
(04) height — 是高度。

 

1.2 节点的创建

/*

 * 创建AVL树结点。

 *

 * 参数说明:

 *     key 是键值。

 *     left 是左孩子。

 *     right 是右孩子。

 */

static Node* avltree_create_node(Typekey, Node *left, Node* right)

{

    Node* p;

 

    if ((p = (Node *)malloc(sizeof(Node))) == NULL)

        return NULL;

    p->key = key;

    p->height = 0;

    p->left = left;

    p->right = right;

 

    return p;

}

 

1.3 树的高度

#define HEIGHT(p)    ( (p==NULL) ? 0 : (((Node*)(p))->height) )

 

/*

 * 获取AVL树的高度

 */

intavltree_height(AVLTree tree)

{

    return HEIGHT(tree);

}

关于高度,有的文章中将”空二叉树的高度定义为-1″,而本文采用维基百科上的定义:树的高度为最大层次。即空的二叉树的高度是0,非空树的高度等于它的最大层次(根的层次为1,根的子节点为第2层,依次类推)。

 

1.4 比较大小

#define MAX(a, b)    ( (a) > (b) ? (a) : (b) )

 

2. 旋转

前面说过,如果在AVL树中进行插入或删除节点后,可能导致AVL树失去平衡。这种失去平衡的可以概括为4种姿态:LL(左左),LR(左右),RR(右右)和RL(右左)。下面给出它们的示意图:

《树、二叉树、AVL树》

上图中的4棵树都是”失去平衡的AVL树”,从左往右的情况依次是:LL、LR、RL、RR。除了上面的情况之外,还有其它的失去平衡的AVL树,如下图:

《树、二叉树、AVL树》

上面的两张图都是为了便于理解,而列举的关于”失去平衡的AVL树”的例子。总的来说,AVL树失去平衡时的情况一定是LL、LR、RL、RR这4种之一,它们都由各自的定义:

(1) LL:LeftLeft,也称为”左左”。插入或删除一个节点后,根节点的左子树的左子树还有非空子节点,导致”根的左子树的高度”比”根的右子树的高度”大2,导致AVL树失去了平衡。

例如,在上面LL情况中,由于”根节点(8)的左子树(4)的左子树(2)还有非空子节点”,而”根节点(8)的右子树(12)没有子节点”;导致”根节点(8)的左子树(4)高度”比”根节点(8)的右子树(12)”高2。

(2) LR:LeftRight,也称为”左右”。插入或删除一个节点后,根节点的左子树的右子树还有非空子节点,导致”根的左子树的高度”比”根的右子树的高度”大2,导致AVL树失去了平衡。

例如,在上面LR情况中,由于”根节点(8)的左子树(4)的左子树(6)还有非空子节点”,而”根节点(8)的右子树(12)没有子节点”;导致”根节点(8)的左子树(4)高度”比”根节点(8)的右子树(12)”高2。

(3) RL:RightLeft,称为”右左”。插入或删除一个节点后,根节点的右子树的左子树还有非空子节点,导致”根的右子树的高度”比”根的左子树的高度”大2,导致AVL树失去了平衡。

例如,在上面RL情况中,由于”根节点(8)的右子树(12)的左子树(10)还有非空子节点”,而”根节点(8)的左子树(4)没有子节点”;导致”根节点(8)的右子树(12)高度”比”根节点(8)的左子树(4)”高2。

(4) RR:RightRight,称为”右右”。插入或删除一个节点后,根节点的右子树的右子树还有非空子节点,导致”根的右子树的高度”比”根的左子树的高度”大2,导致AVL树失去了平衡。
     例如,在上面RR情况中,由于”根节点(8)的右子树(12)的右子树(14)还有非空子节点”,而”根节点(8)的左子树(4)没有子节点”;导致”根节点(8)的右子树(12)高度”比”根节点(8)的左子树(4)”高2。

前面说过,如果在AVL树中进行插入或删除节点后,可能导致AVL树失去平衡。AVL失去平衡之后,可以通过旋转使其恢复平衡,下面分别介绍”LL(左左),LR(左右),RR(右右)和RL(右左)”这4种情况对应的旋转方法。

 

2.1 LL的旋转

LL失去平衡的情况,可以通过一次旋转让AVL树恢复平衡。如下图:

《树、二叉树、AVL树》

图中左边是旋转之前的树,右边是旋转之后的树。从中可以发现,旋转之后的树又变成了AVL树,而且该旋转只需要一次即可完成。
对于LL旋转,你可以这样理解为:LL旋转是围绕”失去平衡的AVL根节点”进行的,也就是节点k2;而且由于是LL情况,即左左情况,就用手抓着”左孩子,即k1″使劲摇。将k1变成根节点,k2变成k1的右子树,”k1的右子树”变成”k2的左子树”。

LL的旋转代码

/*

 * LL:左左对应的情况(左单旋转)。

 *

 * 返回值:旋转后的根节点

 */

static Node*left_left_rotation(AVLTree k2)

{

    AVLTree k1;

 

    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;

}

 

2.2 RR的旋转

理解了LL之后,RR就相当容易理解了。RR是与LL对称的情况!RR恢复平衡的旋转方法如下:

《树、二叉树、AVL树》

图中左边是旋转之前的树,右边是旋转之后的树。RR旋转也只需要一次即可完成。

 

RR的旋转代码

/*

 * RR:右右对应的情况(右单旋转)。

 *

 * 返回值:旋转后的根节点

 */

static Node*right_right_rotation(AVLTree k1)

{

    AVLTree k2;

 

    k2 = k1->right;

    k1->right = k2->left;

    k2->left = k1;

 

    k1->height = MAX( HEIGHT(k1->left), HEIGHT(k1->right)) + 1;

    k2->height = MAX( HEIGHT(k2->right), k1->height) + 1;

 

    return k2;

}

 

2.3 LR的旋转

LR失去平衡的情况,需要经过两次旋转才能让AVL树恢复平衡。如下图:

《树、二叉树、AVL树》
第一次旋转是围绕”k1″进行的”RR旋转”,第二次是围绕”k3″进行的”LL旋转”。

 

LR的旋转代码

/*

 * LR:左右对应的情况(左双旋转)。

 *

 * 返回值:旋转后的根节点

 */

static Node*left_right_rotation(AVLTree k3)

{

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

 

    returnleft_left_rotation(k3);

}

 

2.4 RL的旋转
RL是与LR的对称情况!RL恢复平衡的旋转方法如下:

《树、二叉树、AVL树》

第一次旋转是围绕”k3″进行的”LL旋转”,第二次是围绕”k1″进行的”RR旋转”。

RL的旋转代码

/*

 * RL:右左对应的情况(右双旋转)。

 *

 * 返回值:旋转后的根节点

 */

static Node*right_left_rotation(AVLTree k1)

{

    k1->right = left_left_rotation(k1->right);

 

    returnright_right_rotation(k1);

}

3. 插入
插入节点的代码

/*

 * 将结点插入到AVL树中,并返回根节点

 *

 * 参数说明:

 *     tree AVL树的根结点

 *     key 插入的结点的键值

 * 返回值:

 *     根节点

 */

Node*avltree_insert(AVLTree tree, Type key)

{

    if (tree ==NULL)

    {

        // 新建节点

        tree = avltree_create_node(key, NULL, NULL);

        if (tree==NULL)

        {

            printf(“ERROR: create avltree node failed!\n”);

            return NULL;

        }

    }

    else if(key < tree->key) // 应该将key插入到”tree的左子树”的情况

    {

        tree->left = avltree_insert(tree->left, key);

        // 插入节点后,若AVL树失去平衡,则进行相应的调节。

        if(HEIGHT(tree->left) – HEIGHT(tree->right) == 2)

        {

            if (key < tree->left->key)

               tree =left_left_rotation(tree);

            else

               tree =left_right_rotation(tree);

        }

    }

    else if(key > tree->key) // 应该将key插入到”tree的右子树”的情况

    {

        tree->right = avltree_insert(tree->right,key);

        // 插入节点后,若AVL树失去平衡,则进行相应的调节。

        if(HEIGHT(tree->right) – HEIGHT(tree->left) == 2)

        {

            if (key > tree->right->key)

               tree =right_right_rotation(tree);

            else

               tree =right_left_rotation(tree);

        }

    }

    else //key== tree->key)

    {

        printf(添加失败:不允许添加相同的节点!\n”);

    }

 

    tree->height = MAX( HEIGHT(tree->left), HEIGHT(tree->right)) + 1;

 

    return tree;

}

 

4. 删除
删除节点的代码

/*

 * 删除结点(z),返回根节点

 *

 * 参数说明:

 *     ptree AVL树的根结点

 *     z 待删除的结点

 * 返回值:

 *     根节点

 */

static Node* delete_node(AVLTree tree,Node *z)

{

    // 根为空 或者 没有要删除的节点,直接返回NULL

    if(tree==NULL || z==NULL)

        return NULL;

 

    if (z->key < tree->key)        // 待删除的节点在”tree的左子树”中

    {

        tree->left = delete_node(tree->left, z);

        // 删除节点后,若AVL树失去平衡,则进行相应的调节。

        if(HEIGHT(tree->right) – HEIGHT(tree->left) == 2)

        {

            Node *r =  tree->right;

            if (HEIGHT(r->left) >HEIGHT(r->right))

               tree =right_left_rotation(tree);

            else

               tree =right_right_rotation(tree);

        }

    }

    else if(z->key > tree->key)// 待删除的节点在”tree的右子树”中

    {

        tree->right = delete_node(tree->right, z);

        // 删除节点后,若AVL树失去平衡,则进行相应的调节。

        if(HEIGHT(tree->left) – HEIGHT(tree->right) == 2)

        {

            Node *l =  tree->left;

            if (HEIGHT(l->right) >HEIGHT(l->left))

               tree =left_right_rotation(tree);

            else

               tree =left_left_rotation(tree);

        }

    }

    else    // tree是对应要删除的节点。

    {

        // tree的左右孩子都非空

        if((tree->left) && (tree->right))

        {

            if (HEIGHT(tree->left) >HEIGHT(tree->right))

            {

                // 如果tree的左子树比右子树高;

                // 则(01)找出tree的左子树中的最大节点

               //   (02)将该最大节点的值赋值给tree

               //   (03)删除该最大节点。

                // 这类似于用”tree的左子树中最大节点”做”tree”的替身;

                // 采用这种方式的好处是:删除”tree的左子树中最大节点”之后,AVL树仍然是平衡的。

                Node *max =avltree_maximum(tree->left);

               tree->key = max->key;

               tree->left = delete_node(tree->left,max);

            }

            else

            {

                // 如果tree的左子树不比右子树高(即它们相等,或右子树比左子树高1)

                // 则(01)找出tree的右子树中的最小节点

               //   (02)将该最小节点的值赋值给tree

               //   (03)删除该最小节点。

                // 这类似于用”tree的右子树中最小节点”做”tree”的替身;

                // 采用这种方式的好处是:删除”tree的右子树中最小节点”之后,AVL树仍然是平衡的。

                Node *min =avltree_maximum(tree->right);

               tree->key = min->key;

               tree->right = delete_node(tree->right, min);

            }

        }

        else

        {

            Node *tmp = tree;

            tree = tree->left ? tree->left : tree->right;

           free(tmp);

        }

    }

 

    return tree;

}

 

/*

 * 删除结点(key是节点值),返回根节点

 *

 * 参数说明:

 *     tree AVL树的根结点

 *     key 待删除的结点的键值

 * 返回值:

 *     根节点

 */

Node*avltree_delete(AVLTree tree, Type key)

{

    Node *z;

 

    if ((z = avltree_search(tree, key))!= NULL)

        tree = delete_node(tree, z);

    return tree;

}

 

注意关于AVL树的”前序遍历”、”中序遍历”、”后序遍历”、”最大值”、”最小值”、”查找”、”打印”、”销毁”等接口与”二叉查找树基本一样,这些操作在”二叉查找树”中已经介绍过了,这里就不再单独介绍了。当然,后文给出的AVL树的完整源码中,有给出这些API的实现代码。这些接口很简单,Please RTFSC(Read The Fucking Source Code)!

 

    原文作者:AVL树
    原文地址: https://blog.csdn.net/baiqishijkh/article/details/78591890
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞