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.
LL型
RR型
RL型
LR型

输入格式

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);
}

检查是否失衡

  1. 用递归获取高度
  2. 根据两边子树高度差判断是否平衡
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;
}

插入主体过程

精妙的使用递归过程实现插入和调整的!

  1. 只要设计一个(base case)基本情况作为插入功能的实现就可以了,这里选的时p = NULL时进行插入
  2. 因为递归是先触底在一层层回来的,所以在执行完插入过程之后一层层回到根结点进行判断的时候,肯定是先接触到最小失衡树并进行调整,这样调整过之后递归外层的结点(也就是在在树中离插入结点更远的结点)肯定也已经平衡了,巧妙的利用了递归的性质
  3. 注意递归函数传入的参数,一个是结点指针,一个是关键字。在进行调整时,传入的结点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;
}

猜你喜欢

转载自www.cnblogs.com/Za-Ya-Hoo/p/12725387.html
今日推荐