AVL树分析

首先说一下,这篇博客没有图解,这可能对于理解上带来一定的困难,但是当你想一个问题时不是每个时候都有图解的,所以试着在大脑中构想一 棵包含5个节点的二叉树(a为父节点,b为a的左子树,c为a的右子树,(d为b的左子树,e为b的右子树)/(d为c的左子树,e为c的右子树),!如果有难度,找一张纸画一棵5节点的二叉树,然后阅读下文,相信会有不一样的体验,go

回顾一下: 

二叉树任意个节点保存的值小于它的右子树点的值,大于它左子树保存的值

AVL树又称高度平衡二叉树,它的左子树和右子树都是AVL树且左子树 与右子树的高度差的绝对值不超过1.

AVL树的主要操作就是插入和删除,这也是难点,以下就是分析:

首先说明一下:想要允许往树里插入相同值,就在AVLNODE中添加一个频率的变量来记录出现的次数,否则就直接在插入的开始就进行判断,相同直接返回。否则会有麻烦,至少对我来说是这样(在插入三万多个数的时候就会被提示内存什么的错误),现在还没有解决。在这里我采用第二种方法,不允许插入相同的值。

对于插入操作void   insert(int value),

1.如果插入的位置是其父节点的左子树,则父节点高度差bf减一否则的话bf加一。

2.如果插入后父节点的高度差bf为0,说明插入操作没有影响以该父节点为根节点的子树的高度(插入到了较矮的子树上),可以直接返回。

3.如果bf为1或-1,说明插入操作改变了该父节点的高度,但还在允许范围内。但是这样的改变可能会导致该父节点向上直至根节点中的某个节点的高度差大于2,所以要向上回朔,重复该过程。

4.如果bf为2或-2,这时候就要调节了。对于调节可分为四种情况,由于二叉树是对称的,所以其实是两种情况。如果看书的话,就知道有这四种情况,可是不妨想一下,为什么可以把插入操作归纳为这四种情况,以前被问到这个问题,看了下书,没明白,但是现在貌似有想法了。

想一下什么情况下需要调节树的形态,当然当节点bf==2或bf==-2的时候,但什么时候bf会变成2或-2?它变化前的状态是怎样的?
1.首先bf要变成2,那么插入前bf肯定为1;同理要变成-2,插入前bf肯定为-1,这是初始状态。
2.当插入的数据在当前节点较高的子树上时,且该节点bf值为1或-1,插入后才会变成-2或2。
当我们搞清楚是什么情况下导致数的形态需要调节时,这时候我们已经成功一半了。
明白上面的东西,我们就会发现:对于某个节点bf为2或-2(鉴于2和-2的情况大致分析方法相同<看完就懂了>,这里我们以2为例)。
假设对于节点a,其bf为2,则那么它的右子树b高度比左子树高度高2,这里我们设其右子树高度为h+2,则左子树高度为h。对于左子树b可以看成是以b为根节点,以c为其左子树,以d为其右子树。

那么由二知c比d大1或,d比c大1。就这两种情况,对于bf为-2同理也有两种情况,所以总共四种情况。

1.  a->bf == 2(a左子树b的高度小于a的右子树c的高度)

(1)c->bf==1

(2)c->bf == -1

2.a->bf == -2(a左子树b的高度大于a的右子树c的高度)

(3)c->bf == 1

  (4)c->bf == -1

下面是每种情况对应的代码,实现方法很多,再次只举一例,特别要分析好旋转后个节点高度差bf的变化情况。

(1) :左单旋转

void  AVLTREE::rotatel(AVLNODE*& sub) {
AVLNODE* p = sub;
sub = sub->rchild;
p->bf = 0;
sub->bf = 0;
p->rchild = sub->lchild;
sub->lchild = p;
}

(2 ):先右后左旋转

void AVLTREE::rotaterl(AVLNODE*& sub) { AVLNODE* pt = sub, *right = sub->rchild;
sub = right->lchild;
if (sub->bf == 0) {
pt->bf = 0;
right->bf = 0;
}
else if (sub->bf == 1) {
sub->bf = 0;
pt->bf = -1;
right->bf = 0;
}
else {
right->bf = 1;
sub->bf = 0;
pt->bf = 0;
}
pt->rchild = sub->lchild;
right->lchild = sub->rchild;
sub->rchild = right;
sub->lchild = pt;
}

(3):先左后右旋转

void AVLTREE::rotatelr(AVLNODE*& sub) {
AVLNODE* pt = sub, *left = sub->lchild;
sub = left->rchild;
if (sub->bf == 0) {
pt->bf = 0;
left->bf = 0;
}
else if (sub->bf == 1) {
left->bf = -1;
sub->bf = pt->bf = 0;
}
else {
sub->bf = 0;
pt->bf = 1;
left->bf = 0;
}
pt->lchild = sub->rchild;
left->rchild = sub->lchild;
sub->rchild = pt;
sub->lchild = left;
}

(4):右单旋转

void AVLTREE::rotater(AVLNODE*& sub) {
AVLNODE* p = sub;
sub = sub->lchild;
p->bf = 0;
sub->bf = 0;
p->lchild = sub->rchild;
sub->rchild = p;
}

插入函数insert的具体实现,在此我要说一些 细节问题,也是我编写过程中遇到的。

1.指针理解错误。以前听到别人说指针和指针的指向,总以为很简单,担当用的时候,才体验到指针的困难,一个是用起来困难,二是调试起来困难。比如刚开始我在每个节点中定义了一个parent指针用来指向父节点(想着不用保存从根节点向下寻找插入节点的路径)前面提到过当插入完成后有可能向上回朔,回溯的时候就会用到,然后为了直接改变指针,我用的是二级指针,

sub = root; 这个是初始状态这里sub和tmpt都是AVLNODE**类型
tmpt = &((*sub)->parent)) 如果((*tmpt)->bf) >1 ||( *tmpt)->bf < -1) 就rotate(*tmp)

 当时想着没有问题,不知道你发现问题了没有。本想着改变指向(*sub)->parent这个节点的指针的值,但实际却改变了(*sub)->parent的值,也就是指针的值,和指针指向的值的问题。当时调试的时候看着结果很是不能理解,明明改变了指针的指向,为啥显示却没有变。现在想着确实不好理解。在这里要想改变指针的指向有两种方法,一种是保存向下寻找插入位置时经过的指针(本文就是这么做的),第二种是直接改变(*sub)->parent->parent->rchild/(*sub)->parent->parent->lchild,也就是找到其父节点,然后改变其左子树指针或右子树指针。不过这里要判断是左子树还是右子树,很麻烦。还有如果要用parent的这个变量的话,也会引入不必要的麻烦,比如用到parent的指针不能为空,这个每次用到,要根据实际情况进行判断。


2.考虑问题不全面,就像上面提到的用的parent的指针要判空,当时没有就调试,花去了很长的时间;如果插入时树为空的处理,因为用到了parent指针,还有很多细小的问题,但是每一小的个问题都会导致程序的崩溃,

bool AVLTREE::insert(int value, AVLNODE*& ptr) { AVLNODE* p = ptr, *pr = NULL;
int d;
stack<AVLNODE*> s;         //保存经过的指针
while (p != NULL) {
if (p->data == value) //插入相同的值
return false;
pr = p;
s.push(p);
if (p->data < value) {
p = p->rchild;
}
else {
p = p->lchild;
}
}
p = new AVLNODE(value);
if (p == NULL)
return false;
if (pr == NULL) {
AVLTREE::num++;
ptr = p;
return true;
}
if (value > pr->data) //把新节点连接在树上
pr->rchild = p;
else
pr->lchild = p;
while (!s.empty()) {  //回溯用的循环
pr = s.top();
s.pop();
if (p == pr->lchild)
(pr->bf)–;
else (pr->bf)++;
if (pr->bf == 0)    //pr为p的父节点
break;
if (pr->bf == 1 || pr->bf == -1) {
p = pr;
}
else {
if (pr->bf > 1) { //pr->bf == 2
if (p->bf > 0) {
rotatel(pr);
}
else {
rotaterl(pr);
}
}
else {  //pr->bf ==  -2
if (p->bf > 0) {
rotatelr(pr);
}
else {
rotater(pr);
}
}
break;  //调节后退出(因为调节后子 //树高度没有变大也没有变小,所以没有必要再向上回溯
}
}
//一下是最后一步,中间重新连接或改变数的根节点
if (s.empty())
ptr = pr;
else {
p = s.top();
if (p->data < pr->data)
p->rchild = pr;
else
p->lchild = pr;
}
AVLTREE::num++;
return true;
}
这里说明一下:整个过程都是找到需要的节点后 ,不马上改变(改变了也没有,这不是想要改变的指针),找到其父节点,然后判断改变其左子树或右子树的值,也就是上文提到的第二种方法。所以要在最后在改变的位置重新连接

对于删除操作bool AVLTREE::remove(int value, AVLNODE*& ptr)

回想一下二叉树的删除,找到删除节点后,用其左子树的最右子树a或者右子树的最左子树a的值覆盖要删除节点的值,然后删除a。这里删除也是这种方法。

大致和插入操作的思想是相同的,这里只说不同的地方:

1.前面提到要使一个节点的bf变成2或-2,要满足期初bf为1或-1,然后插入节点在树的最高子树上。

假设对于节点a,其bf为2,则那么它的右子树b高度比左子树高度高2,这里我们设其右子树高度为h+2,则左子树高度为h。对于左子树b可以看成是以b为根节点,以c为其左子树,以d为其右子树。那么c比d大1或,d比c大1。

但是对于删除操作有一种新的情况就是d和c一样高,高度都为h+1,单输出了较矮的子树上(这里为左子树)的一个节点后导致a的bf为2,需要调解。

(要是不理解为啥会发现这种新的情况,下面分析一下,我们都知道一个节点在不需要调解的情况下bf有三种情况:0,1,-1,在插入操作中我们只说了为1和-1两种情况,因为插入前树不需要调整,如果插入一个节点后树需要调整,那么插入在了较高的子树上,这种情况下 子树只能bf只能变为1或-1,为0时不需要调解。但删除操作 是删除较矮的子树使整个树需要调整,删除前较高子树bf就可能为1, 0, -1)

2.因为删除操作是删除节点所以根据插入的分析方法,不难发现删除后:

当bf为0时,说明删除操作导致子树高度变低,所以要向上回朔

当bf为1或-1时,说明删除操作没有改变子树的高度,所以可以停止回朔

bf == 0

void AVLTREE::rotate(AVLNODE*& ptr) {
    AVLNODE *p = NULL;
    if (ptr->bf == 2) {  //节点bf为2需要调整
        p = ptr->rchild;
        if (p->bf == 1 ) {
            rotatel(ptr);
        } else if (p->bf == -1) {
            rotaterl(ptr);
        } else {/// p->bf == 0
            rotatel(ptr);
            ptr->lchild->bf = 1;
            ptr->bf = -1;
        }
    } else {  ///节点bf为-2需要调节
        p = ptr->lchild;
        if (p->bf == 1) {
            rotatelr(ptr);
        } else if (p->bf == -1) {
            rotater(ptr);
        } else {
            rotater(ptr);
            ptr->rchild->bf = -1;
            ptr->bf = 1;
        }
    }
}
着这里说一下当p->bf为0时,虽然可以通过左单旋转或右单旋转来调节,但是个节点的bf变化却不相同,所以如上代码对bf重新做了调整。

bool AVLTREE::remove(int value, AVLNODE*& ptr) {
    AVLNODE* p = ptr, *pt = NULL, *del = NULL;
    int d = 0;
    stack<AVLNODE*> s;
    while (p != NULL) {  //寻找删除节点
        if (p->data == value)
            break;
        pt = p;
        s.push(p);
        if (p->data > value)
            p = p->lchild;
        else
            p = p->rchild;
    }
    if (p == NULL) //删除的值没有找到
        return false; 
    if (pt == NULL) { //删除节点为根节点
        delete p;
        AVLTREE::num–;
        ptr = NULL;
        return true;
    } //找最右子树的最左节点,以下是不同的情况:主要是因  //为删除节点位于其父节点的左子树或右子树时bf的变化  //不同
    if (p->rchild == NULL) { //右子树为空
        if (p == pt->rchild) {
            pt->rchild = p->lchild;
            d = 1;  ///pt->bf–;
        } else {
            pt->lchild = p->lchild;
            d =  2; ///pt->bf++;
        }
    } else { //右子树不为空,找最左子树
        s.push(p);
        pt = p;
        d = 3;
        del = p;
        p = p->rchild;
        while (p->lchild != NULL) {
            s.push(p);
            pt = p;
            d = 4;
            p = p->lchild;
        }
        del->data = p->data;
        if (pt->rchild == p) {
            pt->rchild = p->rchild;
        } else {
            pt->lchild = p->rchild;
        }
    }
    delete p; //删除尾节点
    p = NULL;
    while (s.empty() == false) {
        pt = s.top();
        s.pop();
        if (d != 0) {
            if (d == 1 || d == 3) {
                pt->bf–;
            } else {/// (d == 2 || d == 4)
                pt->bf++;
            }
            d = 0;
        } else {
            if (pt->rchild == p)
                pt->bf–;
            else
                pt->bf++;
        }
        if (pt->bf == 1 || pt->bf == -1) {
            break;
        } else if (pt->bf == 0){
                p = pt;
        } else {
            rotate(pt);
            break;
        }
    } //重新连接
    if (s.empty())
        ptr = pt;
    else {
        del = s.top();
        if (pt->data < del->data) {
            del->lchild = pt;
        } else
            del->rchild = pt;
    }
    AVLTREE::num–;
    return true;
}

对于删除操作也说一下自己犯的错误(因为有插入操作那万分坎坷,所以删除的完成还是比较顺利的):

1.没有判断如果在树中没有 找到要删除的值,结果程序崩溃了。好在分析了几组“崩溃数据”后,发现了问题及时改正了

2.如文中提到的,虽然我考虑到了bf为0的情况,但是没有重新改变调整后各节点bf的值,好在一组十个数据的分析就搞定了,呵呵

最后总结一下:

avl树确实难,但是当你掌握之后 还算可以吧。再次本人要承认,自己是很笨的,我花了连续7天时间(几乎整整三十个小时)才把avl树搞定,但是我也是很有恒心的,因为我坚持了这么长时间,有时候真的不想写了,但是还是坚持下来了,呵呵呵,现在终于弄明白了,发现自己的编程和思维能力都有所提高。如果看了上文你没有懂,没关系,不要放弃,重新找一篇博客,或者把思路重新整理一下,再看一遍,相信每次新的尝试都会有新的收获。加油!

#include <stdlib.h>
#include "AVLTREE.h"
#include <stack>
#include <iostream>
using namespace std;

unsigned int AVLTREE::num = 0;



class AVLNODE {
public:
	AVLNODE() :rchild(NULL), lchild(NULL), bf(0) {}
	AVLNODE(int value) :data(value), rchild(NULL), lchild(NULL), bf(0) {}
	int data;
	short bf;
	AVLNODE* rchild;
	AVLNODE* lchild;
};

class AVLTREE
{
public:
	AVLTREE() :root(NULL) {}
	~AVLTREE() { destroy(root); AVLTREE::num = 0;}
	bool insert(int value) { return insert(value, root); }
	bool remove(int value) { return remove(value, root);}

	void in_order() { in_order(root); }
	static unsigned int num;

protected:
	void in_order(AVLNODE*);
	void rotatel(AVLNODE*& sub);
	void rotater(AVLNODE*& sub);
	void rotaterl(AVLNODE*& sub);
	void rotatelr(AVLNODE*& sub);
	void rotate(AVLNODE*& sub);
	void destroy(AVLNODE*&);
	bool insert(int, AVLNODE*&);
	bool remove(int, AVLNODE*&);


private:
	AVLNODE* root;
};


void AVLTREE::destroy(AVLNODE*& sub) {
	if (sub != NULL) {
		destroy(sub->lchild);
		destroy(sub->rchild);
		delete sub;
		sub = NULL;
	}
}

void AVLTREE::in_order(AVLNODE* sub) {
	if (sub != NULL) {
		in_order(sub->lchild);
		cout << sub->data << " ";
		in_order(sub->rchild);
	}
}



void  AVLTREE::rotatel(AVLNODE*& sub) {
	AVLNODE* p = sub;
	sub = sub->rchild;
	p->bf = 0;
	sub->bf = 0;
	p->rchild = sub->lchild;
	sub->lchild = p;
}

void AVLTREE::rotater(AVLNODE*& sub) {
	AVLNODE* p = sub;
	sub = sub->lchild;
	p->bf = 0;
	sub->bf = 0;
	p->lchild = sub->rchild;
	sub->rchild = p;
}

void AVLTREE::rotatelr(AVLNODE*& sub) {
	AVLNODE* pt = sub, *left = sub->lchild;
	sub = left->rchild;
	if (sub->bf == 0) {
		pt->bf = 0;
		left->bf = 0;
	}
	else if (sub->bf == 1) {
		left->bf = -1;
		sub->bf = pt->bf = 0;
	}
	else {
		sub->bf = 0;
		pt->bf = 1;
		left->bf = 0;
	}
	pt->lchild = sub->rchild;
	left->rchild = sub->lchild;
	sub->rchild = pt;
	sub->lchild = left;
}

void AVLTREE::rotaterl(AVLNODE*& sub) {
	AVLNODE* pt = sub, *right = sub->rchild;
	sub = right->lchild;
	if (sub->bf == 0) {
		pt->bf = 0;
		right->bf = 0;
	}
	else if (sub->bf == 1) {
		sub->bf = 0;
		pt->bf = -1;
		right->bf = 0;
	}
	else {
		right->bf = 1;
		sub->bf = 0;
		pt->bf = 0;
	}
	pt->rchild = sub->lchild;
	right->lchild = sub->rchild;
	sub->rchild = right;
	sub->lchild = pt;
}


bool AVLTREE::insert(int value, AVLNODE*& ptr) {
	AVLNODE* p = ptr, *pr = NULL;
	int d;
	stack<AVLNODE*> s;
	while (p != NULL) {
		if (p->data == value)
			return false;
		pr = p;
		s.push(p);
		if (p->data < value) {
			p = p->rchild;
		}
		else {
			p = p->lchild;
		}
	}
	p = new AVLNODE(value);
	if (p == NULL)
		return false;
	if (pr == NULL) {
		AVLTREE::num++;
		ptr = p;
		return true;
	}
	if (value > pr->data)
		pr->rchild = p;
	else
		pr->lchild = p;
	while (!s.empty()) {
		pr = s.top();
		s.pop();
		if (p == pr->lchild)
			(pr->bf)--;
		else (pr->bf)++;
		if (pr->bf == 0)
			break;
		if (pr->bf == 1 || pr->bf == -1) {
			p = pr;
		}
		else {
			if (pr->bf > 1) {
				if (p->bf > 0) {
					rotatel(pr);
				}
				else {
					rotaterl(pr);
				}
			}
			else {
				if (p->bf > 0) {
					rotatelr(pr);
				}
				else {
					rotater(pr);
				}
			}
			break;
		}
	}
	if (s.empty())
		ptr = pr;
	else {
		p = s.top();
		if (p->data < pr->data)
			p->rchild = pr;
		else
			p->lchild = pr;
	}
	AVLTREE::num++;
	return true;
}

void AVLTREE::rotate(AVLNODE*& ptr) {
    AVLNODE *p = NULL;
    if (ptr->bf == 2) {
        p = ptr->rchild;
        if (p->bf == 1 ) {
            rotatel(ptr);
        } else if (p->bf == -1) {
            rotaterl(ptr);
        } else {/// p->bf == 0
            rotatel(ptr);
            ptr->lchild->bf = 1;
            ptr->bf = -1;
        }
    } else {
        p = ptr->lchild;
        if (p->bf == 1) {
            rotatelr(ptr);
        } else if (p->bf == -1) {
            rotater(ptr);
        } else {
            rotater(ptr);
            ptr->rchild->bf = -1;
            ptr->bf = 1;
        }
    }
}

bool AVLTREE::remove(int value, AVLNODE*& ptr) {
    AVLNODE* p = ptr, *pt = NULL, *del = NULL;
    int d = 0;
    stack<AVLNODE*> s;
    while (p != NULL) {
        if (p->data == value)
            break;
        pt = p;
        s.push(p);
        if (p->data > value)
            p = p->lchild;
        else
            p = p->rchild;
    }
    if (p == NULL)
        return false;
    if (pt == NULL) {
        delete p;
        AVLTREE::num--;
        ptr = NULL;
        return true;
    }
    if (p->rchild == NULL) {
        if (p == pt->rchild) {
            pt->rchild = p->lchild;
            d = 1;  ///pt->bf--;
        } else {
            pt->lchild = p->lchild;
            d =  2; ///pt->bf++;
        }
    } else {
        s.push(p);
        pt = p;
        d = 3;
        del = p;
        p = p->rchild;
        while (p->lchild != NULL) {
            s.push(p);
            pt = p;
            d = 4;
            p = p->lchild;
        }
        del->data = p->data;
        if (pt->rchild == p) {
            pt->rchild = p->rchild;
        } else {
            pt->lchild = p->rchild;
        }
    }
    delete p;
    p = NULL;
    while (s.empty() == false) {
        pt = s.top();
        s.pop();
        if (d != 0) {
            if (d == 1 || d == 3) {
                pt->bf--;
            } else {/// (d == 2 || d == 4)
                pt->bf++;
            }
            d = 0;
        } else {
            if (pt->rchild == p)
                pt->bf--;
            else
                pt->bf++;
        }
        if (pt->bf == 1 || pt->bf == -1) {
            break;
        } else if (pt->bf == 0){
                p = pt;
        } else {
            rotate(pt);
            break;
        }
    }
    if (s.empty())
        ptr = pt;
    else {
        del = s.top();
        if (pt->data < del->data) {
            del->lchild = pt;
        } else
            del->rchild = pt;
    }
    AVLTREE::num--;
    return true;
}



#include <iostream>
#include "AVLTREE.h"
#include <ctime>
#include <iostream>
#include <stdio.h>
using namespace std;

void g(int a[]) {
    AVLTREE avl;
    for (int i=0; i<10; i++) {
        avl.insert(a[i]);
    }
    avl.in_order();
    cout << endl;
    for (int i=9; i>=0; i--) {
        //cout << a[i] << "  ";
        avl.remove(a[i]);
    }
    avl.in_order();
    cout << endl << "Ok  " << AVLTREE::num << endl;

}


void f() {
    int tmpt;
    int a[10];
    while (1) {
        for (int i=0; i<10; i++) {
            a[i] = rand()%100;
            //cout << a[i] << "  ";
        }
        cout << endl;
        g(a);
    }
}

const unsigned int size = 200000;
int b[size];

void t() {
    AVLTREE avl;

    unsigned int tmpt, i=0;
    while (i != size) {
        b[i] = rand();
        //cout << i << "  ";
        avl.insert(b[i]);
        i++;
    }
    i = 0;
    cout << "hello";
    avl.in_order();
    getchar();
    cout << endl << endl;
    while (i != size) {
        avl.remove(b[i]);
        //cout << i << "  ";
        i++;
    }
    avl.in_order();
}

void test() {
    AVLTREE avl;
    int c[10] = {65, 27, 14, 6, 51, 42, 79, 90, 83, 19};
    for (int i=0; i<10; i++)
        avl.insert(c[i]);

    for (int i=9; i>=0; i--) {
        cout << c[i] << "  ";
        avl.remove(c[i]);
    }
    avl.in_order();
}

int main() {
	srand(time(NULL));
    //f();
    t();
    //test();

	return 0;
}

    原文作者:AVL树
    原文地址: https://blog.csdn.net/u012510204/article/details/45112515
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞