平衡二叉树定义AVL树是带有平衡条件的二叉查找树。
如果向一棵树输入预先排好序的数据,那么一连串insert操作将花费二次的时间,而链表实现的代价会非常巨大,因为此时的树只由那些没有左儿子的节点组成,一种解决办法,就是要有一颗称为平衡的附加结构条件,任何节点的深度均不得过深。
于是我开始学习一种最古老的平衡查找树,即AVL树。
二叉树节点的创建
struct BinaryTree //二叉树结构体
{
void *nodeData; //数据
int index; //索引号
BinaryTree *rightTree; //右子树
BinaryTree *leftTree; //左子树
};
#define MemMode 1
#if MemMode
#define BinTree_Malloc malloc
#define BinTree_Free free
#else
#define BinTree_Malloc mymalloc0
#define BinTree_Free myfree0
#endif
BinaryTree* creatBinaryTree(BinaryTree **binTree,int index,void *nodeData) //创建
{
*binTree = (BinaryTreePtr )BinTree_Malloc(sizeof(BinaryTree)); //内存分配
if(*binTree != null)
{
(*binTree)->index = index;
(*binTree)->nodeData = nodeData;
(*binTree)->leftTree = null;
(*binTree)->rightTree = null;
}
return *binTree;
}
由于该模块用于linux或MCU的程序开发,因而使用BinTree_Malloc宏定义来选择使用malloc还是自定义的内存分配函数
节点的插入
/*********************
平衡二叉树,插入函数
参数1:树头
参数2:索引号
参数3:数据
返回:0,创建成功,-1,创建失败
**********************/
int BinaryTreeInsert(BinaryTree **binTree,int index,void *nodeData)
{
int ret = 0;
if((*binTree) == null)
{
creatBinaryTree(binTree,index,nodeData);
return ret;
}
if((*binTree)->index>index)
{
if((*binTree)->leftTree != null)
ret = BinaryTreeInsert(&(*binTree)->leftTree,index,nodeData);
else
{
creatBinaryTree(&(*binTree)->leftTree,index,nodeData);
}
}
else if((*binTree)->index<index)
{
if((*binTree)->rightTree != null)
ret = BinaryTreeInsert(&(*binTree)->rightTree,index,nodeData);
else
{
creatBinaryTree(&(*binTree)->rightTree,index,nodeData);
}
}
else
{
return -1;
}
*binTree = spin(*binTree,index); //判断是否平衡二叉树,并作出调整
return ret;
}
在节点插入的过程中,函数主要是分配内存给节点并且将它挂钩在父节点上,当函数最后一次进栈完成后,通过spin二叉树旋转函数来判断节点左右子树的高度差是否小于2,若高度差小于2则返回原节点,若高度差大于等于2则旋转树使节点的左右子树高度差小于2.节点插入后破坏节点平衡的情况大致分4种:LL、LR、RL、RR。
LL:节点6的左子树3高度比右子树7高度大2,且左子树3的左子树1高度比左子树3的右子树4高度大。
LR:节点6的左子树2高度比右子树7高度大2,且左子树2的左子树1高度比左子树2的右子树4高度小。
RL:节点2的左子树1高度比右子树5高度小2,且右子树5的左子树3高度比右子树5的右子树6高度大。
RL:节点2的左子树1高度比右子树4高度小2,且右子树4的左子树3高度比右子树4的右子树6高度小。
左旋转和右旋转
对于LL型和RR型的结构是对称的,所以LL和RR的处理方法雷同,作单旋转即可。
BinaryTree *AVL_LLHandle(BinaryTree *binTree) //左旋
{
BinaryTree *root,*L,*R;
root = binTree;
L = root->leftTree;
R = L->rightTree;
root->leftTree = R;
L->rightTree = root;
return L;
}
BinaryTree *AVL_RRHandle(BinaryTree *binTree) //右旋
{
BinaryTree *root,*L,*R;
root = binTree;
R = root->rightTree;
L = R->leftTree;
root->rightTree = L;
R->leftTree = root;
return R;
}
左右旋转和右左旋转
对于LR型和RL型这两种情况,单旋转不能使它达到一个平衡状态,要经过两次旋转。双旋转是针对于这两种情况的解决方案,同样的,这样两种情况也是对称的。LR型处理方法是,先节点的左子树右旋,再左旋。RL型则相反,先节点的右子树左旋,再右旋。
#define TREE_PRINTF_ENABLE 0
#if TREE_PRINTF_ENABLE
#define TREEPrintf printfBinTree
#else
#define TREEPrintf
#endif
/**********************************
左右处理,先左之树右旋,再根左旋
***********************************/
BinaryTree *AVL_LRHandle(BinaryTree *binTree)
{
BinaryTree *root,*L;
root = binTree;
L = root->leftTree;
root->leftTree = AVL_RRHandle(L);
DebugPrintf("root->left RR:\n");
TREEPrintf(root);
DebugPrintf("root LL:\n");
root = AVL_LLHandle(root);
TREEPrintf(root);
return root;
}
/**********************************
右左处理,先右之树左旋,再根右旋
***********************************/
BinaryTree *AVL_RLHandle(BinaryTree *binTree)
{
BinaryTree *root,*R;
root = binTree;
R = root->rightTree;
root->rightTree = AVL_LLHandle(R);
DebugPrintf("root->left LL:\n");
TREEPrintf(root);
DebugPrintf("root RR:\n");
root = AVL_RRHandle(root);
TREEPrintf(root);
return root;
}
TREEPrintf和DebugPrintf用作打印二叉树结构和输出调试信息函数。
节点插入时判断和旋转的实现方法
/***************************
判断二叉树新增一个元素后,是否平衡二叉树,若不是平衡则旋转。
参数1:二叉树指针
参数2:新增的键值
返回:若平衡,返回原树,不平衡,返回旋转后的树
***************************/
BinaryTree *spin(BinaryTree *binTree,int index)
{
if(binTree!=null)
if(getDepthDiff(binTree)>=2)
{
if(index > binTree->index )
{
if(binTree->rightTree != null)
if(index > (binTree)->rightTree->index)
binTree = AVL_RRHandle((binTree)); //右右处理
else
binTree = AVL_RLHandle((binTree)); //右左处理
}
else
{
if(binTree->leftTree != null)
if(index > (binTree)->leftTree->index)
binTree = AVL_LRHandle(binTree); //左左
else
binTree = AVL_LLHandle(binTree); //左右
}
}
return binTree;
}
在插入节点后,不断地对节点的父节点的左右子树高度差进行检测,若高度差大于等于2则进行对应的旋转操作。
打印二叉树结构函数
由于需要对二叉树操作后的结果不断的验证,所以有必要构建一个直观的二叉树结构。
因为初次接触二叉树结构,也想不到什么好办法把它在控制台直观地打印出来,所以用了最简单粗暴的方法打印数组,通过把二叉树里的节点的值按照相对位置来存放,然后打印出来,该函数只能作测试使用。
int binTreeDisArray[30][100]; //二叉树显示数组,不可复用
int depthIndex[30]; //二叉树第n层的数据的索引号,不可复用
int indexX,indexY; //辅助参数,不可复用
/*****************************
打印二叉树结构函数,只作测试使用,能显示的最大深度取决于数组的大小和显示窗口的宽度。
参数1:二叉树指针
无返回
*******************************/
void __printfBinTree(BinaryTree *binTree)
{
int i = 0,flag = 0;
depth++;
if(binTree!=null)
{
indexX = depthIndex[depth] * (1<<(maxDepth - depth +1)) + (1<<(maxDepth - depth)); //获取元素横向位置。
binTreeDisArray[depth][indexX] = binTree->index;
depthIndex[depth]++;
}
else
{
return;
}
if(binTree->leftTree!=null)
{
__printfBinTree(binTree->leftTree);
}else
{
for(i=depth+1;i<20;i++)
depthIndex[i] += 1<<(i-depth-1);
flag=1;
}
if(binTree->rightTree!=null)
{
__printfBinTree(binTree->rightTree);
}else
{
for(i=depth+1;i<20;i++)
depthIndex[i] += (1<<(i-depth-1));
}
depth--;
}
/*************************
打印二叉树结构函数,只做测试使用。
*************************/
void printfBinTree(BinaryTree *binTree)
{
int i,j;
depth = 0;
maxDepth = 0;
memset(binTreeDisArray,0,sizeof(binTreeDisArray));
memset(depthIndex,0,sizeof(depthIndex));
getBinTreeDepth(binTree);
__printfBinTree(binTree);
printf("\nBinTree:\n");
for(i=1;i<=maxDepth;i++)
{
for(j=0;j<100;j++)
{
if(binTreeDisArray[i][j] == 0)
printf(" ");
else
{
if(binTreeDisArray[i][j]>=0 && binTreeDisArray[i][j]<10)
printf(" %d",binTreeDisArray[i][j]);
else
printf("%d",binTreeDisArray[i][j]);
}
}
printf("\n");
}
}
打印示例结果图
节点的删除
在平衡二叉树中,节点删除的处理办法和插入差不多,删除右子树相当于插入左子树,删除左子树相当于插入右子树。
删除节点有3种不同的情况
1、节点是叶子,即左右子树都是空,直接删除再判断父子树是否平衡即可。
2、节点有其中一边子树不为空,将该节点指向不为空的子树,然后释放该节点的资源和判断父子树是否平衡。
3、左右节点均不为空,这种情况就复杂些,需要判断节点左右子树的高度,若左子树比右子树高,则删除左子树里值最大的节点并将最大节点的值和数据继承到需要删除的节点上面。若右子树比左子树高,则删除右子树里值最小的节点并将最小节点的值和数据继承到需要删除的节点上面。注意不能直接删除左右子树不为空的节点,删除后它的子树会找不到它的父子树是谁。
BinaryTree *removeBinTreeNode(BinaryTree *binTree,int index)
{
BinaryTree *temp;
if(binTree == null)
return null;
if(index<binTree->index) //目标在左树
{
binTree->leftTree = removeBinTreeNode(binTree->leftTree,index);
if(GET_DEPTH((binTree)->rightTree) - GET_DEPTH(binTree->leftTree) >= 2)
{
if(GET_DEPTH(binTree->rightTree->leftTree)>GET_DEPTH(binTree->rightTree->rightTree))
{
binTree = AVL_RLHandle(binTree);
}
else
{
binTree = AVL_RRHandle(binTree);
}
}
}
else if(index>binTree->index) //目标在右树
{
binTree->rightTree = removeBinTreeNode(binTree->rightTree,index);
if(GET_DEPTH(binTree->leftTree) - GET_DEPTH(binTree->rightTree) >= 2)
{
if(GET_DEPTH(binTree->leftTree->leftTree)>GET_DEPTH(binTree->leftTree->rightTree))
{
binTree = AVL_LLHandle(binTree);
}
else
{
binTree = AVL_LRHandle(binTree);
}
}
}
else //获取到目标值位置
{
if(binTree->leftTree != null && binTree->rightTree != null)
{
if(GET_DEPTH(binTree->rightTree)>GET_DEPTH(binTree->leftTree))
{
temp = binTree->rightTree;
while(temp->leftTree) //取最小值
{
temp = temp->leftTree;
}
binTree->index = temp->index;
binTree->nodeData = temp->nodeData;
binTree->rightTree = removeBinTreeNode(binTree->rightTree,temp->index); //删除右子树里最小值的节点
}
else
{
temp = binTree->leftTree;
while(temp->rightTree) //取最大值
{
temp = temp->rightTree;
}
binTree->index = temp->index;
binTree->nodeData = temp->nodeData;
binTree->leftTree = removeBinTreeNode(binTree->leftTree,temp->index); //删除左子树里最大值的节点
}
}
else
{
temp = binTree;
if(binTree->leftTree!=null)
{
binTree = binTree->leftTree;
}
else if(binTree->rightTree!=null)
{
binTree = binTree->rightTree;
}
else
{
binTree = null;
}
BinTree_Free(temp);
}
}
return binTree;
}
二叉树遍历函数(前、中、后序历遍)
对于二叉树的遍历,实际上几种方法都差不多,区别在于执行的顺序不同,前序是自顶向下的遍历,中序是值由小到大的遍历,后序是自低向上和从左到右的遍历。对于历遍算法也适用于二叉树值的查找。
void PreorderBinTree(BinaryTree *binTree) //前序遍历
{
if(binTree != null)
printf("%d\n",binTree->index);
if(binTree->leftTree!=null)
{
PreorderBinTree(binTree->leftTree);
}
if(binTree->rightTree!=null)
{
PreorderBinTree(binTree->rightTree);
}
}
void InorderBinTree(BinaryTree *binTree) //中序遍历
{
if(binTree->leftTree!=null)
{
InorderBinTree(binTree->leftTree);
}
printf("%d\n",binTree->index);
if(binTree->rightTree!=null)
{
InorderBinTree(binTree->rightTree);
}
}
void PostorderBinTree(BinaryTree *binTree) //后序遍历
{
if(binTree->leftTree!=null)
{
PostorderBinTree(binTree->leftTree);
}
if(binTree->rightTree!=null)
{
PostorderBinTree(binTree->rightTree);
}
if(binTree != null)
printf("%d\n",binTree->index);
}
/************************
二叉树查找函数
参数1:查找的树
参数2:索引值
返回:查找到的树,找不到返回null
*************************/
BinaryTree *BinTreeFind(BinaryTree *binTree,int index)
{
//BinaryTree *result = PostorderBinTreeFind(binTree,index);
BinaryTree *result = TopToDownBinTreeFind(binTree,index); //采取自顶向下的查找算法,即前序遍历查找
if(result!=null && result->index == index)
return result;
else
return null;
}
二叉树自动分配可用ID号
有序的搜索二叉树上的节点的值是不重复的,因为我们申请插入二叉树的值的时候需要找到一个二叉树内不存在的值。
实现方法:使用中序遍历的思想,将节点的值由小到大排序,并返回父子节点的差值大于1的较小的节点值,最坏情况遍历二叉树内所有的值。
#define getAbsValue(a,b) (a)>(b)?((a)-(b)):((b)-(a)) //获取绝对值
#define getMin(a,b) (a)>(b)?(b):(a) //获取最小值
#define GetIdleIdNum(a,b) (_allotIdNum(a,b)+1) //小值节点+1
int _allotIdNum(BinaryTree *binTree,int preIndex)
{
if(binTree == null)
return 0;
if(binTree->leftTree!=null)
{
preIndex = _allotIdNum(binTree->leftTree,preIndex);
}
if(getAbsValue(preIndex,binTree->index)>1)
{
preIndex = getMin(preIndex,binTree->index); //取最小值
return preIndex;
}
preIndex = binTree->index;
if(binTree->rightTree!=null)
{
preIndex = _allotIdNum(binTree->rightTree,preIndex);
}
return preIndex;
}
测试函数和测试结果
int Test(int argc, _TCHAR* argv[])
{
int i;
char *string = null ;
for(i=0;i<10;i++)
{
string = (char *)malloc(20);
sprintf(string,"num%d",0+i);
if(BinaryTreeAutoInsert(&binTreeAutoHead,0+i,string) == 0)
{
DebugPrintf("\ninsert num :%d ,result :\n",0+i); //插入成功
TREEPrintf(binTreeAutoHead); //打印树状结构
}
else
{
DebugPrintf("BinTree Insert faile,num is %d\n",0+i); //插入失败
}
}
printfBinTree(binTreeAutoHead);
printf("\nInorder : \n");
InorderBinTree(binTreeAutoHead); //中序
printf("\nPreorder : \n");
PreorderBinTree(binTreeAutoHead); //前序
printf("\nPostorder : \n");
PostorderBinTree(binTreeAutoHead); //后序
while(1)
{
printf("\nget id num %d\n",GetIdleIdNum(binTreeAutoHead,0)); //分配可用ID号
printf("input find num\n");
count = 0; //查找次数
scanf("%d",&argc); //输入需要查找的ID号
//system("cls"); //清屏
findBinTree = BinTreeFind(binTreeAutoHead,argc);
if(findBinTree!=null) //查找到ID号,删除该节点
{
printf("%s\nfind num:%d,delete it\n",findBinTree->nodeData,count);
deleteBinTreeNode(binTreeAutoHead,argc);
}
else
{
printf("not find ,count is%d,creat it\n",count); //查找不到,新建一个节点
BinaryTreeAutoInsert(&binTreeAutoHead,argc,string);
}
printfBinTree(binTreeAutoHead); //打印树状结构
}
return 0;
}
测试结果
当前可分配ID为 10
输入4,4存在,则删除4,并获取可分配ID为4
输入4,4不存在,则新建4,当前可分配ID为10
总结
在学习二叉树的过程中,可以更深刻地理解函数的递归调用并得到灵活的运用。接下来就是利用已有代码,在一些项目中应用了。如有不足之处,希望大家能学习交流一下。