数据结构---平衡二叉树的实现
数据结构—平衡二叉树的实现
一. 内容介绍
平衡二叉树是对二叉搜索树进行优化过后形成的一种更高效的搜索树,在绝大多数情况下查询效率会比二叉搜索树高,是因为当一颗二叉搜索树插入的节点在树的一边,导致形成一种一边子树节点较少一边过多的失衡状态,二叉平和树就是解决了这种失衡后形成的树形结构,接下来会详细介绍平衡二叉树的原理介绍和代码实现。
二. 二叉平衡树形成原理
为避免搜索树出现“一边倒”的情况,容易得出的改善方法就是在满足搜索树“左小右大”性质的前提下进行树节点的移位,如何更好的完成这一部分就要介绍下该树保持平衡的操作:“左旋” 和 “右旋”。顾名思义,就是通过旋转树的部分来完成树的平衡和搜索性质,一旦树失衡就根据情况选择“旋转”操作。具体步骤如下:
- 判断进行插入或删除树节点操作后该位置是否“失衡”(平衡条件为目标节点的左右子树高度之差不超过1,即 左子树高度-右子树高度的绝对值 <= 1。
- 判断失衡后,再观察失衡类型,共四种分别为:LL型,LR型,RR型,RL型。判断方式为插入节点与失衡节点的关系(失衡节点是指插入节点后,一个节点的左右子树高度差大于1的节点)。(L:left 左 ;R: right 右)
LL型:

观察图片可得插入30节点后,66节点的左子树高度为3,右子树高度为1,满足失衡条件。观察可得,66节点到30节点的路径中经过的66->60, 60->50皆是向左走,所以上图失衡类型为LL型。如果插入节点为55,也是LL型。
LR型:

判断易知失衡节点为66,66节点到61节点的路径有66->60, 60->65。方向为先向左后向右为LR型。
RR型,RL型同理可以判断得出。
关于右旋具体操作可参考下图:

首先断开失衡节点左子节点的右子节点,然后将失衡节点和其右子节点向右下移一层,失衡节点的左子树上移一层,形成左子树根节点取代原先失衡节点位置的状态,最后失衡节点指向一开始被断开的节点,完成右旋操作。
左旋同理,篇幅有限,请自行思考.
上述右旋例图同时也是LL型失衡处理示例,RR型则是用左旋操作完成处理。下面介绍LR/RL型处理方式:
以LR为例

由上图可以看出,LR型进行单一的右旋处理只能将失衡方式变换为RL型,所以对于LR型的失衡形式,我们要先将它的左子节点惊醒一次左旋,转换为LL型,再右旋完成平衡操作。示例如下:

RL型同理。
- 根据判断类型进行旋转操作
三. 代码实现
1. 初始化及建树操作
typedef int Element;
// 平衡二叉树的节点结构
typedef struct _avl_node {Element data;struct _avl_node *left, *right;int height; // 当前节点的高度(这个节点左子树的高度和右子树高度的最大值 + 1)
} AVLNode;// 平衡二叉树的树头结构
typedef struct {AVLNode *root;int count;
} AVLTree;AVLTree* createAVLTree()
{AVLTree* tree = malloc(sizeof(AVLTree));if (tree == NULL){printf("Memory allocation failed");return NULL;}tree -> count = 0;tree -> root = NULL;return tree;
}
2. 中序遍历
void visitAVLNode(const AVLNode* node)//访问节点操作,此处示例为输出节点中所存数据
{if (node) {printf("\t<%d:%d>", node->data, node->height);}
}static void inOrderNode(AVLNode *node)
{if (node){inOrderNode(node->left);visitAVLNode(node);inOrderNode(node->right);}
}void inOrderAVLTree(AVLTree* tree)
{if (tree){inOrderNode(tree->root);printf("\n");}
}
3. 左/右旋操作
static int maxNum(int a, int b)//返回两数之间较大的值
{return a > b ? a : b;
}static int h(const AVLNode *node)//返回当前节点所在高度
{if (node == NULL){return 0;}return node->height;
}/* 左旋操作* px px* | |* x y* / \ -》 / \* lx y x ry* / \ / \* ly ry lx ly * */
static AVLNode *leftRotate(AVLNode *x)
{AVLNode* y = x->right;x->right = y->left;//失衡节点右子节点的左子节点指向失衡节点y->left = x;//左旋后x为y左子节点x->height = maxNum(h(x->left),h(x->right)) + 1;y->height = maxNum(h(y->left),h(y->right)) + 1;return y;
}/* 右旋操作* py py* | |* y x* / \ -》 / \* x ry lx y * / \ / \* lx rx rx ry* */
static AVLNode *rightRotate(AVLNode *y)//右旋同理
{AVLNode* x = y->left;y->left = x->right;x->right = y;y->height = maxNum(h(y->left),h(y->right)) + 1;x->height = maxNum(h(x->left),h(x->right)) + 1;return x;
}
4. 插入操作
static int getBalance(const AVLNode *node)//返回平衡因子(判断左右子树高度差)
{if (node == NULL){return 0;}return h(node->left) - h(node->right);
}static AVLNode *createAVLNode(Element data)//创建节点并初始化
{AVLNode* node = malloc(sizeof(AVLNode));if (node == NULL){printf("Memory allocation failed");return NULL;}node->data = data;node->left = node->right = NULL;node->height = 1;return node;
}static AVLNode *insertAVLNode(AVLTree *tree, AVLNode *node, Element e)
//意为在tree这颗树中以node为根节点的子树中插入节点e
{if (node == NULL)//若插入子树为NULL,直接创建一个节点{tree->count++;return createAVLNode(e);}if (e < node->data)//根据“左小右大”规则判断e应插入位置{node->left = insertAVLNode(tree,node->left,e);}else if (e > node->data){node->right = insertAVLNode(tree,node->right,e);}else//已找到{return node;}//未找到的情况下判断插入节点是否失衡node->height = maxNum(h(node->left),h(node->right)) + 1;int balance = getBalance(node);//左子树比右子树高度之差大于1if (balance > 1){if (e > node->left->data)//LR型先通过左旋转换成LL型{node->left = leftRotate(node->left);//先左旋再把左旋后node->left位置当前节点返回赋值给node->left}return rightRotate(node);//再右旋返回继续判断是否平衡}//RR型,RL型同上if (balance < -1){if (e < node->right->data){node->right = rightRotate(node->right);}return leftRotate(node);}return node;//未失衡直接返回
}void insertAVLTree(AVLTree* tree, Element data) {if (tree) {tree->root = insertAVLNode(tree, tree->root, data);//从根节点开始插入}
}
5. 删除节点操作
static AVLNode *deleteAVLNode(AVLTree *tree, AVLNode *node, Element e)
//意为在tree这颗树中以node为根节点的子树中删除节点e
{if (node == NULL)//若该子树为NULL,删除失败{return NULL;}if (e < node->data)//根据搜索树规则判断递归待删节点位置{node->left = deleteAVLNode(tree,node->left,e);}else if (e > node->data){node->right = deleteAVLNode(tree,node->right,e);}else{AVLNode* temp;//定义备份节点用以删除节点后不丢失位置if (node->left == NULL || node->right == NULL)//若满足条件则左右子节点必有一空{temp = node->left ? node->left : node->right;//判断node节点度为1还是0if (temp == NULL)//度为0,直接释放{tree->count--;free(node);return NULL;}//度为1,将子节点整个替换掉node,类同于将node删除node->data = temp->data;node->left = temp->left;node->right = temp->right;tree->count--;free(temp);}else//度为2,寻找node前驱节点,将其替换掉node,使“左小右大”性质不发生改变{temp = node->left;while (temp->right){temp = temp->right;}node->data = temp->data;node->left = deleteAVLNode(tree,node->left,temp->data);//替换之后将用以替换的节点删除}}//完成上述删除操作后开始判断删除节点后是否导致失衡node->height = 1 + maxNum(h(node->left),h(node->right));int balance = getBalance(node);if (balance > 1)//L型{if (getBalance(node->left) < 0)//LR型(node左子节点的左右子树高度差小于零得以判断){node->left = leftRotate(node->left);}return rightRotate(node);}if (balance < -1)//R型处理方式同上{if (getBalance(node->right) > 0){node->right = rightRotate(node->right);}return leftRotate(node);}return node;//未失衡直接返回
}void deleteAVLTree(AVLTree* tree, Element e)
{if (tree){tree->root = deleteAVLNode(tree,tree->root,e);}
}
6. 释放树
static void releaseAVLNode(AVLTree* tree,AVLNode* node)
{if (node){if (node->left)//左节点存在向左递归{releaseAVLNode(tree,node->left);}if (node->right)//右节点存在向左递归{releaseAVLNode(tree,node->right);}free(node);tree->count--;}
}void releaseAVLTree(AVLTree* tree)
{if (tree){releaseAVLNode(tree,tree->root);//从根节点开始递归逐一释放}
}
四. 总结概括
本文介绍平衡二叉树的原理与实现,与普通二叉树的实现区别在于进行插入删除节点后使树结构保持平衡的操作,只要学会用以判断失衡类型并牢记各类型如何通过左旋右旋解决即可。关于保持平衡核心操作“旋转”的实现,可以自行思考具体原理。本文到此结束,有问题可以在评论中提出。
