Root of AVL Tree(二叉平衡树的建立)
Posted za-ya-hoo
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Root of AVL Tree(二叉平衡树的建立)相关的知识,希望对你有一定的参考价值。
前言
根据插入序列建立二叉平衡树并输出根结点,其实就是考察能否掌握建立二叉平衡树的过程。这题去年有写过,但是一直卡住了,这次终于写出来了,而且真的见识到了一些很精妙的操作,不管是调整还是插入过程。
题目描述
An AVL tree is a self-balancing binary search tree. In an AVL tree, the heights of the two child subtrees of any node differ by at most one; if at any time they differ by more than one, rebalancing is done to restore this property. Figures 1-4 illustrate the rotation rules.
Now given a sequence of insertions, you are supposed to tell the root of the resulting AVL tree.
输入格式
Each input file contains one test case. For each case, the first line contains a positive integer N (≤20) which is the total number of keys to be inserted. Then N distinct integer keys are given in the next line. All the numbers in a line are separated by a space.
输出格式
For each test case, print the root of the resulting AVL tree in one line.
样例
输入样例1
5
88 70 61 96 120
输出样例1
70
输入样例2
7
88 70 61 96 120 90 65
输出样例2
88
思路
emmmm...简单介绍一下平衡二叉树,它是这样一种二叉搜索树,任何一个结点的左右两个子树的高度差不超过1。平衡二叉树的目的是为了能达到最高的搜索效率。
调整过程就不介绍了...
然后就是建立平衡二叉树的过程。
建立平衡二叉树的过程主要以插入为主,插入过程和搜索二叉树的过程一致,但是在每次插入之后,都要加一个调整的步骤,因为每一次插入都可能使其失衡。建立完成后,输出根结点即可。
精妙的在代码部分。
实现
完整代码
#include<iostream>
using namespace std;
typedef struct Node{
int data;
struct Node* lchild;
struct Node* rchild;
} Node;
int myHeight(Node * t) {
if (t == NULL)
return 0;
else {
int l = myHeight(t->lchild);
int r = myHeight(t->rchild);
return (l > r ? l : r) + 1; //一定要加括号,注意优先级的问题
}
}
bool myIsBalance(Node *t) {
int l = myHeight(t->lchild);
int r = myHeight(t->rchild);
if (l - r <= 1 && l - r >= -1)
return true;
else
return false;
}
Node* LL(Node* root) {
Node *p = root->lchild;
root->lchild = p->rchild;
p->rchild = root;
return p; //返回新的根结点
}
Node* RR(Node* root) {
Node* p = root->rchild;
root->rchild = p->lchild;
p->lchild = root;
return p;
}
Node* LR(Node* root) {
root->lchild = RR(root->lchild);
return LL(root);
}
Node* RL(Node* root) {
root->rchild = LL(root->rchild);
return RR(root);
}
Node* myNewNode(int key) {
Node *p = new Node;
p->lchild = NULL;
p->rchild = NULL;
p->data = key;
return p;
}
void myInsert(Node *&t, int key) {
if (t == NULL)
t = myNewNode(key);
else {
if (key > t->data) {
myInsert(t->rchild, key);
if (myIsBalance(t) == false) {
if (key > t->rchild->data)
t = RR(t);
else
t = RL(t);
}
}
else {
myInsert(t->lchild, key);
if (myIsBalance(t) == false) {
if (key < t->lchild->data)
t = LL(t);
else
t = LR(t);
}
}
}
return;
}
int main() { //主函数
Node* tree = NULL;
int num;
cin >> num;
for (int i = 0; i < num; i++) { //逐个插入即可
int temp;
cin >> temp;
myInsert(tree, temp); //插入函数(包括调整)
}
cout << tree->data;
return 0;
}
存储结构
最普通的链式存储
typedef struct Node{
int data;
struct Node* lchild;
struct Node* rchild;
} Node;
调整过程
分别对应四种失衡情况:LL、LR、RR、RL。注意这里需要调整的是里插入结点最近的失衡结点,因为调整完这个之后,更远处失衡的结点也就变的平衡了。
实现过程有个小技巧,LL、RR是基本情况,而LR、RL则可以通过两种基本操作的组合实现,比如,对于LR情况,就可以先对失衡结点的左结点进行RR操作,再对失衡结点进行LL操作。
还有个更棒的小技巧,注意这里的调整过程,输入是待调整的结点的地址,返回值则是调整过后根结点地址,这样在执行调整过程时可以省下很多操作,避免指针错指带来的一些问题。
Node* LL(Node* root) {
Node *p = root->lchild;
root->lchild = p->rchild;
p->rchild = root;
return p; //返回新的根结点
}
Node* RR(Node* root) {
Node* p = root->rchild;
root->rchild = p->lchild;
p->lchild = root;
return p;
}
Node* LR(Node* root) {
root->lchild = RR(root->lchild);
return LL(root);
}
Node* RL(Node* root) {
root->rchild = LL(root->rchild);
return RR(root);
}
检查是否失衡
- 用递归获取高度
- 根据两边子树高度差判断是否平衡
int myHeight(Node * t) {
if (t == NULL)
return 0;
else {
int l = myHeight(t->lchild);
int r = myHeight(t->rchild);
return (l > r ? l : r) + 1; //一定要加括号,注意优先级的问题
}
}
bool myIsBalance(Node *t) {
int l = myHeight(t->lchild);
int r = myHeight(t->rchild);
if (l - r <= 1 && l - r >= -1)
return true;
else
return false;
}
插入主体过程
精妙的使用递归过程实现插入和调整的!
- 只要设计一个(base case)基本情况作为插入功能的实现就可以了,这里选的时p = NULL时进行插入
- 因为递归是先触底在一层层回来的,所以在执行完插入过程之后一层层回到根结点进行判断的时候,肯定是先接触到最小失衡树并进行调整,这样调整过之后递归外层的结点(也就是在在树中离插入结点更远的结点)肯定也已经平衡了,巧妙的利用了递归的性质
- 注意递归函数传入的参数,一个是结点指针,一个是关键字。在进行调整时,传入的结点t就是待调整的指针,这时需要对t的内容进行更改,这里的t要使用引用,因为t在这里其实是上一层结点的子结点,所以必须使用引用,来确保能改变,而不是只是传值进来,那样就是无意义的了,这时再结合调整函数,就能达到更改结点指针的目的了。(很混乱...
注:其实可以不用引用,只需要返回值也是指针就行了,这样就能实现不用引用更改作为实参传入函数的指针的指向了。有空更新一下这个版本的。
Node* myNewNode(int key) {
Node *p = new Node;
p->lchild = NULL;
p->rchild = NULL;
p->data = key;
return p;
}
void myInsert(Node *&t, int key) { //未曾设想的道路,精妙!
if (t == NULL) //基本情况(base case)
t = myNewNode(key);
else {
if (key > t->data) { //默认插在了右边,所以如果不平衡只需要考虑右边的情况即可,即RL、RR,下同
myInsert(t->rchild, key); //精妙的是,因为是递归插入的,所以最先被检查到的肯定是最靠近插入结点的,即最近失衡树
if (myIsBalance(t) == false) { //检查插入之后是否失衡
if (key > t->rchild->data)
//要么就是在右边的右边,不能用key == rchild->rchild->data判断,因为哪怕是最近失衡结点,也有可能离插入结点超过两层,这里选择使用大小判断正是利用了二叉搜索树的性质
t = RR(t); //因为这个赋值语句直接改变了t的指向,所以在传参时,必须要使用引用才行
else
t = RL(t);
}
}
else {
myInsert(t->lchild, key);
if (myIsBalance(t) == false) {
if (key < t->lchild->data)
t = LL(t);
else
t = LR(t);
}
}
}
return;
}
以上是关于Root of AVL Tree(二叉平衡树的建立)的主要内容,如果未能解决你的问题,请参考以下文章
PAT A1066 Root of AVL Tree [平衡二叉树]