树、二叉树、AVL树
(注:本文转自http://www.cnblogs.com/skywang12345/p/3576969.html)
一、树
1. 树的定义
树是一种数据结构,它是由n(n>=1)个有限节点组成一个具有层次关系的集合。
把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:
(01) 每个节点有零个或多个子节点;
(02) 没有父节点的节点称为根节点;
(03) 每一个非根节点有且只有一个父节点;
(04) 除了根节点外,每个子节点可以分为多个不相交的子树。
2. 树的基本术语
若一个结点有子树,那么该结点称为子树根的”双亲”,子树的根是该结点的”孩子”。有相同双亲的结点互为”兄弟”。一个结点的所有子树上的任何结点都是该结点的后裔。从根结点到某个结点的路径上的所有结点都是该结点的祖先。
结点的度:结点拥有的子树的数目。
叶子:度为零的结点。
分支结点:度不为零的结点。
树的度:树中结点的最大的度。
层次:根结点的层次为1,其余结点的层次等于该结点的双亲结点的层次加1。
树的高度:树中结点的最大层次。
无序树:如果树中结点的各子树之间的次序是不重要的,可以交换位置。
有序树:如果树中结点的各子树之间的次序是重要的, 不可以交换位置。
森林:0个或多个不相交的树组成。对森林加上一个根,森林即成为树;删去根,树即成为森林。
二、二叉树
1. 二叉树的定义
二叉树是每个节点最多有两个子树的树结构。它有五种基本形态:二叉树可以是空集;根可以有空的左子树或右子树;或者左、右子树皆为空。
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个结点的二叉树,被称为满二叉树。
3.2 完全二叉树
定义:一棵二叉树中,只有最下面两层结点的度可以小于2,并且最下一层的叶结点集中在靠左的若干位置上。这样的二叉树称为完全二叉树。
特点:叶子结点只能出现在最下层和次下层,且最下层的叶子结点集中在树的左部。显然,一棵满二叉树必定是一棵完全二叉树,而完全二叉树未必是满二叉树。
3.3 二叉查找树
定义:二叉查找树(Binary Search Tree),又被称为二叉搜索树。设x为二叉查找树中的一个结点,x节点包含关键字key,节点x的key值记为key[x]。如果y是x的左子树中的一个结点,则key[y] <= key[x];如果y是x的右子树的一个结点,则key[y] >= key[x]。
在二叉查找树中:
(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);
}
}
下面通过例子对这些遍历方式进行介绍。
对于上面的二叉树而言,
(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);
}
上面的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。如下图所示:
(03) 打印树的信息
插入1,5,4,3,2,6之后,得到的二叉查找树如下:
前序遍历结果: 1 5 4 3 2 6
中序遍历结果: 1 2 3 4 5 6
后序遍历结果: 2 3 4 6 5 1
最小值是1,而最大值是6。
(04) 删除节点3。如下图所示:
(05) 重新遍历该二叉查找树。
中序遍历结果: 1 2 4 5 6
三、AVL树
AVL树的介绍
AVL树是根据它的发明者G.M. Adelson-Velsky和E.M.Landis命名的。
它是最先发明的自平衡二叉查找树,也被称为高度平衡树。相比于”二叉查找树”,它的特点是:AVL树中任何节点的两个子树的高度最大差别为1。
上面的两张图片,左边的是AVL树,它的任何节点的两个子树的高度差别都<=1;而右边的不是AVL树,因为7的两颗子树的高度相差为2(以2为根节点的树的高度是3,而以8为根节点的树的高度是1)。
AVL树的查找、插入和删除在平均和最坏情况下都是O(logn)。
如果在AVL树中插入或删除节点后,使得高度之差大于1。此时,AVL树的平衡状态就被破坏,它就不再是一棵二叉树;为了让它重新维持在一个平衡状态,就需要对其进行旋转处理。学AVL树,重点的地方也就是它的旋转算法;在后文的介绍中,再来对它进行详细介绍。
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(右左)。下面给出它们的示意图:
上图中的4棵树都是”失去平衡的AVL树”,从左往右的情况依次是:LL、LR、RL、RR。除了上面的情况之外,还有其它的失去平衡的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树,而且该旋转只需要一次即可完成。
对于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恢复平衡的旋转方法如下:
图中左边是旋转之前的树,右边是旋转之后的树。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树恢复平衡。如下图:
第一次旋转是围绕”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恢复平衡的旋转方法如下:
第一次旋转是围绕”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)!