一 基础知识
没有儿子的节点是树叶(leaf);有相同父亲的节点师兄弟sibling
从节点n_1到n_i+1的路径path定义为节点n_1,n_2,,,,n_k的一个序列,n_i是n_i+1的父亲,路径长length为路径上的边的条数即k-1
n_i的深度depth是从根到n_i的唯一路径长,根的深度是0;n_i的高是从n_i到一片树叶的最长路径的长。所有树叶高是0.
树高是根高=树的深度=最深的树叶的深度
二 AVL树的插入 操作 单旋转 双旋转 先序遍历输出 后序遍历输出&仿计算文件大小
嗯,就是这样,书上介绍的太详细了,就是抄到电脑上,无脑运动(理解、记忆、消化、吸收...)
头文件
struct AvlNode;
typedef struct AvlNode *Position;
typedef struct AvlNode *AvlTree;
AvlTree MakeEmpty(AvlTree T);
Position Find(AvlTree T,int X);
Position FindMin(AvlTree T);
Position FindMax(AvlTree T);
AvlTree Insert(AvlTree T,int X);
AvlTree Delete(AvlTree T,int X);
Position SingleRotationLeft(AvlTree T);
Position SingleRotationRight(AvlTree T);
Position DoubleRotationLeft(AvlTree T);
Position DoubleRotationRight(AvlTree T);
int Retrieve(Position P);
int PHeight(Position P);
int Max(int a,int b);
void ListDirectory(AvlTree T);//先序遍历打印树
void ListDir(AvlTree T,int Depth,int Flag);
void PrintName(AvlTree T,int Depth,int Flag);
int ListSize(AvlTree T,int Depth,int Flag);//后序遍历打印大小和节点值
void PrintSize(AvlTree T,int Depth,int TotalSize,int Flag);
主函数
/************************************************************************/
/* 实现功能包括:1 对一个数组里的数插入到树中;
2分别对树里的内容实现先序遍历&后序遍历的打印功能;
3把各个儿子的数值大小看做子文件大小,计算父亲所应占的内存大小*/
/************************************************************************/
#include <stdio.h>
#include <STDLIB.H>
#include "AvlTree.h"
struct AvlNode
{
int Data;
AvlTree Left;
AvlTree Right;
int Height;//这里Height取名意,就是高度=到最远树叶的路径长度
};
AvlTree MakeEmpty(AvlTree T)
{
if (T!=NULL)
{
MakeEmpty(T->Left);//这里需要递归实现,对T树的所有节点free
MakeEmpty(T->Right);
T->Height=0;
//T=NULL;
free(T);
}
return NULL;
}
Position Find(AvlTree T,int X)
{
if (T==NULL)
{
return T;
}
else if (X<T->Data)
{
return(Find(T->Left,X));
}
else if (X>T->Data)
{
return(Find(T->Right,X));
}
else//最后一个是X==T->Data的情况,发生的概率最低,也就放在最后面
return T;
}
//用递归的方法查找AVL树的最小
Position FindMin(AvlTree T)
{
if (T==NULL||T->Left==NULL)//T==NULL时候应该返回NULL,既然T和NULL相等,返回T应该是相同的效果
{
return T;
}
else if (T->Left!=NULL)
{
T=T->Left;
return(FindMin(T));
}
}
//用循环而不是递归的方法查找AVL树的最大
Position FindMax(AvlTree T)
{
if(T!=NULL)//这里应该对T==NULL做一个判断,如果T==NULL,那就不存在T->Right,更没有NULL的判断
while(T->Right!=NULL)
T=T->Right;
return T;
}
AvlTree Insert(AvlTree T,int X)//这个程序有一个缺陷就是如果插入的X已经在T中存在这个值,
//没有办法处理。书中是讲可以在节点中另设一个变量来存储X出现的次数,也便于进行懒惰删除
//如果有时间,将完善该程序,在节点中加入这个变量并实现懒惰删除
{
// Position P;
// P=(Position)malloc(sizeof(struct AvlNode));
if (T==NULL)
{
T=(Position)malloc(sizeof(struct AvlNode));
T->Data=X;
T->Left=NULL;
T->Right=NULL;
T->Height=0;
return T;
}
else if (X<T->Data)
{
/*1*/ T->Left=Insert(T->Left,X);//这里因为T是一个树根,所以需要保证T的有效性。可以用tempT=T->Left来进行递归调用,
//但是一定是对T->Left进行Insert操作,并且返回值要赋值给T->Left
if (PHeight(T->Left)-PHeight(T->Right)==2)//新插入破坏了AVL的平衡,进行旋转
{
if (X<T->Left->Data)//左左单循环
T=SingleRotationLeft(T);//这里需要说明的是,这是一个递归调用,/*1*/行操作后T不是根,但是要在局部区域(X插入引起不平衡所涉及的最小区域)中进行旋转,需要对T子数进行旋转
else//左右双循环
T=DoubleRotationLeft(T);
}
}
else if(X>T->Data)
{
T->Right=Insert(T->Right,X);
if (PHeight(T->Right)-PHeight(T->Left)==2)
{
if (X>T->Right->Data)//右右单循环
T=SingleRotationRight(T);
else//右左双循环
T=DoubleRotationRight(T);
}
}
T->Height=Max(PHeight(T->Left),PHeight(T->Right))+1;
return T;
}
AvlTree Delete(AvlTree T,int X)
{
return T;
}
int Retrieve(Position P)
{
if (P==NULL)
return -9999;
else
return P->Data;
}
int PHeight(Position P)
{
if (P==NULL)
return -1;
else
return P->Height;
}
Position SingleRotationLeft(AvlTree K2)//我对于本书作者能以这种方式来精巧实现单旋转的敬仰犹如滔滔之江水...
{
Position K1;
K1=K2->Left;
K2->Left=K1->Right;
K1->Right=K2;
//对一个节点的信息更新,包括Left,Right,Data,Height:data不变,做完Left,Right后需要处理Height
K2->Height=Max(PHeight(K2->Left),PHeight(K2->Right))+1;//深度,在子目录的最大深度上加1
K1->Height=Max(PHeight(K1->Left),PHeight(K1->Right))+1;//先算K2,才能有K1->Right
return K1;
}
Position SingleRotationRight(AvlTree K1)
{
Position K2;
K2=K1->Right;
K1->Right=K2->Left;
K2->Left=K1;
K1->Height=Max(PHeight(K1->Left),PHeight(K1->Right))+1;
K2->Height=Max(PHeight(K2->Left),PHeight(K2->Right))+1;
return K2;
}
Position DoubleRotationLeft(AvlTree K1)
{
Position K2;
K2=K1->Left;
K1->Left=SingleRotationRight(K2);//这里如果写成K2会出现问题:因为K2只是一个指针变量,如果是对K2赋值,则只是K2指向的位置变了,K1->Left实质上没有改变,所以指针...!!!
K1=SingleRotationLeft(K1);
return K1;
}
Position DoubleRotationRight(AvlTree K1)
{
K1->Right=SingleRotationLeft(K1->Right);//这里的写法简单易懂又不会出现问题,所以指针的使用中尽量不要使用中间变量
return SingleRotationRight(K1);
}
int Max(int A,int B)
{
return A>B?A:B;
}
void ListDirectory(AvlTree T)//先序遍历打印树
{
ListDir(T,0,0);
}
void ListDir(AvlTree T,int Depth,int Flag)
{
if (T!=NULL)
{
PrintName(T,Depth,Flag);
Position T1,T2;
T1=T->Left;
ListDir(T1,++Depth,1);//这里处理深度问题
T2=T->Right;
ListDir(T2,Depth,2);
}
}
void PrintName(AvlTree T,int Depth,int Flag)
{
int i;
for (i=0;i<Depth;i++)
printf(" ");
if(Flag==1)
printf("Left:");
else if(Flag==2)
printf("Right:");
else printf("ROOT:");
printf("%d\n",T->Data);
}
int ListSize(AvlTree T,int Depth,int Flag)//后序遍历打印大小和节点值
{
if (T!=NULL)
{
int TotalSize=T->Data;
Position T1,T2;
T1=T->Left;
TotalSize+=ListSize(T1,++Depth,1);//这里处理深度问题
T2=T->Right;
TotalSize+=ListSize(T2,Depth,2);
PrintSize(T,--Depth,TotalSize,Flag);
return TotalSize;
}
return 0;
}
void PrintSize(AvlTree T,int Depth,int TotalSize,int Flag)
{
int i;
for (i=0;i<Depth;i++)
printf(" ");
if(Flag==1)
printf("Left:");
else if(Flag==2)
printf("Right:");
else printf("ROOT:");
printf("ID=%d,TotalSize=%d\n",T->Data,TotalSize);
}
int main(void)
{
AvlTree T,P1,P2;
int TData[]={3,5,13,6,7,14,10,11,4,1,2,8,9,12,15};
int n=sizeof(TData)/sizeof(int),i;
T=(AvlTree)malloc(sizeof(struct AvlNode));
//P=(AvlTree)malloc(sizeof(struct AvlNode));
T=NULL;
T=MakeEmpty(T);
for (i=0;i<n;i++)
T=Insert(T,TData[i]);
P1=Find(T,4);//这里P1只是当做指针用,指向一个返回的地址,不用分配内存空间
P2=FindMax(T);
//怎么得到depth是一个问题。可以在调用的程序中加入一个变量i用来计算深度
ListDirectory(T);
printf("\n/*******************************************/\n");
n=ListSize(T,0,0);
return 0;
}
我擦,显示出来恰好250行。TMD!!! 啊哈哈~~
输出结果
如果数组里的数是排序的,生成的树可以最大化利用空间,即层数最少,如15个升序数将只用深度为4的树,而当前的数组生成的树深度为5;