二叉查找树(BST)、平衡二叉树(AVL树)

二叉查找树(BST)

  特殊的二叉树,又称为排序二叉树、二叉搜索树、二叉排序树。

  二叉查找树实际上是数据域有序的二叉树,即对树上的每个结点,都满足其左子树上所有结点的数据域均小于或等于根结点的数据域,右子树上所有结点的数据域均大于根结点的数据域。如下图所示:


二叉查找树通常包含查找、插入、建树和删除操作。

二叉查找树的创建

对于一棵二叉查找树,其创建与二叉树的创建很类似,略有不同的是,二叉查找树,为了保证整棵树都关于根结点的大小呈左小右大的特征,在创建时,需要根据当前结点的大小来判断插入位置,给出如下代码:

template<typename T>
void  BSTree<T>::createBSTreeByFile(ifstream &f){
    T e;
    queue<BSNode<T>*> q;

    while(!f.eof()){
        InputFromFile(f, e);
        Insert(root, e);
    }
}

template<typename T>
void BSTree<T>::Insert(BSNode<T>* &t, T x){//得用指针的引用,不然传参时由于形参实例化,并不能成功创建二叉树

    if(t==NULL){
        t = new BSNode<T>;
        t->data = x;
        t->lchild = t->rchild = NULL;
        return;
    }

    if(x<=t->data){
        Insert(t->lchild, x);
    }
    else{
        Insert(t->rchild, x);
    }
}

二叉查找树的查找

二叉查找树的查找有递归和非递归两种,对于递归方式,其递归边界为树的终止结点,非递归方式则采取对树中所有结点采取BFS或者DFS进行遍历的方式。

对于非递归方式,给出采取DFS的遍历方式,在这种方式中,通常采用入栈的方式,来访问每个结点,而根据访问的先后顺序,又分为,前序、中序和后序三种遍历方式。以前序遍历为例,通常以根、左、右的顺序访问遍历每个结点,而中序遍历方式,则以左、根、右的顺序遍历,后序则以左右根的顺序来访问。下面给出三种遍历方式的代码:前序遍历:

 1 template<typename T>
 2 void BSTree<T>::PreOrderTraverse(void(*visit)(BSNode<T>&))const{
 3     stack<BSNode<T>*> s;
 4     BSNode<T> *t = root;
 5     while(NULL!=t || !s.empty()){
 6         if(NULL!=t){
 7             s.push(t);
 8             visit(*t);
 9             t = t->lchild;
10         }
11         else{
12             t = s.top();
13             s.pop();
14             t = t->rchild;
15         }
16     }
17     cout<<endl;
18 }

 中序遍历:

 1 template<typename T>
 2 void BSTree<T>::InOrderTraverse(void(*visit)(BSNode<T>&))const{
 3     stack<BSNode<T>*> s;
 4     BSNode<T> *q;
 5 
 6     q = root;
 7 
 8     while(!s.empty()||q!=NULL){
 9         if(q!=NULL){
10             s.push(q);
11             q = q->lchild;
12         }
13         else{
14             q = s.top();
15             s.pop();
16             visit(*q);
17             q = q->rchild;
18         }
19     }
20     cout<<endl;
21 }

后序遍历,对于后序遍历,直接采用入栈的方式进行访问,是不行的,因为根结点被访问两次,无法保证你在弹栈后,对该结点如何操作,因此,需要另设置一个flag参数,来指明该节点是否左右子树都访问过,代码如下,我这里是令定义一个结构体,来实现:

/*结构体部分*/
enum Tags{Left, Right};

template<typename T>struct StackElem
{
    BSNode<T> *p;
    Tags flag;
};

/*后序遍历代码部分*/
template<typename T>
void BSTree<T>::PostOrderTraverse(void(*visit)(BSNode<T>&))const{
    StackElem<T> se;
    stack<StackElem<T> > s;

    BSNode<T> *t;
    t = root;

    if(t==NULL){
        return;
    }
    while(t!=NULL||!s.empty()){
        while(t!=NULL){
            se.flag = Left;
            se.p = t;
            s.push(se);
            t = t->lchild;
        }
        se = s.top();
        s.pop();
        t = se.p;
        if(se.flag==Left){
            se.flag = Right;
            s.push(se);
            t = t->rchild;
        }
        else{
            visit(*t);
            t = NULL;
        }
    }
}

以下是递归实现部分,递归实现,则是以二叉树边界为递归边界,前面已经说过了,其余逻辑与非递归一致,因为递归的过程,可以看作是一个入栈和弹栈的过程,即,在未到达边界时,通过递归,来访问下一个结点,例如左结点,当触及边界,则访问该结点,由于每次递归状态都被计算机保存,因此,在访问一个结点以后,返回上一个结点的状态,会依次访问上去。

 递归前序遍历:

 1 template<typename T>
 2 void BSTree<T>::PreTraverse(BSNode<T> *t, void(*visit)(BSNode<T>&))const{
 3     if(t==NULL){
 4         return;
 5     }
 6     else{
 7         visit(*t);
 8         PreTraverse(t->lchild, visit);
 9         PreTraverse(t->rchild, visit);
10     }
11 }

递归中序遍历:

 1 template<typename T>
 2 void BSTree<T>::InTraverse(BSNode<T> *t, void(*visit)(BSNode<T>&))const{
 3     if(t==NULL){
 4         return;
 5     }
 6     else{
 7         InTraverse(t->lchild, visit);
 8         visit(*t);
 9         InTraverse(t->rchild, visit);
10     }
11 }

递归后序遍历:

1 template<typename T>
2 void BSTree<T>::PostTraverse(BSNode<T> *t, void(*visit)(BSNode<T>&))const{
3     if(t!=NULL){
4         PostTraverse(t->lchild, visit);
5         PostTraverse(t->rchild, visit);
6         visit(*t);
7     }
8 }

 平衡二叉树(AVL树)

  平衡二叉树是由前苏联的两位数学家G.M.Adelse-Velskil和E.M.Landis提出,因此一般也称作AVL树,AVL树本质还是一棵二叉查找树,只是在其基础上增加了“平衡”的要求。所谓平衡是指,对AVL树的任意结点来说,其左子树与右子树的高度之差的绝对值不超过1,其中左子树与右子树的高度因子之差称为平衡因子。

  如下所示,就是一棵由{1,2,3,4,5,7,8}构建的AVL树:

  

AVL树的创建

  只要能随时保证每个结点平衡因子的绝对值不超过1,AVL的高度就始终能保持O(logn)级别,由于需要对每个结点都得到平衡因子,因此需要在树的结构中加入一个变量height来记录以当前结点为根结点的子树的高度:

  

猜你喜欢

转载自www.cnblogs.com/sgatbl/p/9426394.html
今日推荐