二叉查找树BST----legend050709



二叉排序、搜索树:BST

(一) 定义 & 特点

(二) 二叉查找数的查找

(三) 二叉查找数的遍历

(四)二叉查找树的插入与创建

(五)二叉查找树的删除

(六)支持重复数据的二叉查找树

(七)二叉查找树的复杂度

(八)二叉查找树和Hash表对比

(九)二叉查找树的其他操作

(十) 平衡二叉树AVL

-----------------------------------
(一)二叉查找树的概念以及类型定义,特点:

(1)定义 
二叉查找树为空树,或者满足以下条件:
1.左子树非空,则左子树上所有记录均小于根节点的记录;
2.右子树非空,则右子树上所有记录均大于根节点的记录;
3.左右子树又是一棵二叉查找树。


(2)类型定义:
typedef struct node{
    DataType data;
    node* lchild, * rchild;
}BSTNode;


(3)特点:
中序遍历二叉查找树,得到一个递增的有序序列;

 

(二)二叉查找树的查找
(1)递归:

BSTNode* bstSearch(BSTNode* root, DataType k){
     if(root==NULL) 
          return NULL;
     if(root->data==k) 
          return root;
     else if(k<root->data) //在左子树中查找
         return bstSearch(root->lchild,k);
     else if(k>root->data)//在右子树中查找
         return bstSearch(root->rchild,k);
}
(2)非递归:

/*
在以root为根节点的二叉排序树中查值为x的节点,father保存的是所查节的父节点;
father为引用型参数
*/

BSTNode* bstSearch(BSTNode* root ,DataType x, BSTNode* & father)
{
    BSTNode* pnode=root;
    father=NULL;

    while(pnode!=NULL && x!=pnode->data){
        father=pnode;
        if(x<pnode->data)
            pnode=pnode->lchild;
        else 
            pnode=pnode->rchild;
    }//没有找到,循环结束则pnode==NULL,father为值很接近data的叶子节点;
    return pnode;
}

(三)二叉查找树的遍历:

(四)二叉查找树的插入与创建:

(1)插入:
注意:新节点都是作为叶子节点插入的,所以树是向下生长的。

/*
    目的:在以root为根节点的二叉排序树中插入一个值为x的节点,插入成功返回1,否则0;
    注意:root为引用型参数;
    思想:若原二叉查找树为空,则直接插入节点作为根节点;
    若x小于根节点的data,则插在左子树中,否则右子树中。
*/
int bstInsert(BSTNode* & root, DataType x){
     if(NULL==root) {
          root=(BSTNode* )malloc(sizeof(BSTNode));
          root->data=x;
          root->lchild=root->lchild=NULL;
          return 1;
     }
     else if(root->data == x) {//如果二叉查找树中已经存在值为x的节点,则失败。
          return 0;
     }

     else if(x<root->data) {//插入到左子树中。
          return bstInsert(root->lchild,x);
     }
     else 
          bstInsert(root->rchild,x);
}


或者:非递归方法:

int bstInsert(BSTNode* & root,DataType x){
     BSTNode* pnode,*newNode,*father;
     pnode=bstSearch(root,x,father);
     //调用上面的非递归的查找
     //如果找到则pnode非空,没有找到则pnode为空,father为值很接近x的叶子节点。

     if(pnode!=NULL )//已经存在值为x的节点
        return 0;

     newNode=(BSTNode* )malloc(sizeof(BSTNode));
     newNode->data=x;
     newNode->lchild=newNode->rchild=NULL;

     if(NULL==father) //如果原二叉查找树为空
        root=newNode;

     else if(x<father->data)
        father->lchild=newNode;
     else  
        father->rchild=newNode;
     return 1;
}


(2)二叉查找树的构造:

void createBST(BSTNode* & root ,DataType[] dataArray, int length)

{
    root=NULL;
    for(int i=0;i<length;i++)
        bstInsert(root,dataArray[i]);
}
(五)二叉查找树的删除:

二叉查找树中删除一个节点,不能把以该节点为根的子树都删除,删除二叉查找树的一个节点就相当于删除有序序列中的一个元素。

设:被删除的节点为pnode,father为其父节点,分为四种情况:
(1)pnode为叶子节点:
直接删除pnode.
(2)pnode为左单分支节点:
pnode只有左子树,而没有右子树。
将pnode的左子树直接代替pnode。
(3)pnode为右单分支节点:
将pnode的右子树直接代替pnode。
(4)pnode为双分支节点:
1)方法一:
将pnode的左子树的最右节点与pnode交换,(pnode的左子树的最右节点没有右孩子),采用情况2删除左子树的最右节点。
2)方法二:
将pnode的右子树的最左节点与pnode交换,(pnode的右子树的最左节点没有左孩子),采用情况3删除右子树的最左节点。
代码实现:
/*
目的:在以root为根节点的二叉查找树中删除值为x的节点,成功则返回1,否则返回0;
注意:root为引用型参数,因为root也可能被删除。
*/

int bstDelete(BSTNode* &root , DataType x ){
     BSTNode* father,*node_x;

     node_x=bstSearch(root,x,father);   //调用上面的非递归的search
     if(node_x==NULL)
        return 0;//没有该节点。

     BSTNode* temp_nodeX=node_x;    //保存node_x的值;
     if(NULL==node_x->rchild)   //右孩子为空,为左单分支节点
     {
        node_x=node_x->lchild;
     }

     else if(NULL==node_x->lchild)  //左孩子为空,为右单分支节点
     {
         node_x=node_x->rchild;
         delete temp_nodeX;
         return 1;
     }

     else { //node_x为双分支节点
          BSTNode* pnode=node_x->lchild;
          father=node_x;
          //寻找node_x的左子树的最右节点,father为其父节点;
          while(pnode->rchild!=NULL)
          {
              father=pnode;
              pnode=pnode->rchild;
          }

          node_x->data=pnode->data;//将node_x的左子树的最右节点的值保存在node_x中
          //删除node_x的左子树的最右节点
          if(father==node_x)
              father->lchild=pnode->lchild;
          else
              father->rchild=pnode->lchild;

          delete pnode;
          return 1;
     }

}

------

 (六) 支持重复数据的二叉查找树

前面讲二叉查找树的时候,我们默认树中节点存储的都是数字。很多时候,在实际的软件开发中,我们在二叉查找树中存储的是一个包含很多字段的对象。我们利用对象的某个字段作为键值(key)来构建二叉查找树。

前面我们讲的二叉查找树的操作,针对的都是不存在键值相同的情况。那如果存储的两个对象键值相同,这种情况该怎么处理呢?这里有两种解决方法。

重复数据的二叉查找数的处理方法:

(6.1)二叉查找树的每个节点为数组或者链表:

二叉查找树中每一个节点不仅会存储一个数据,因此我们通过链表和支持动态扩容的数组等数据结构,把值相同的数据存储在同一个节点上。

(6.2)树中存在和要插入数据key相同的数据时,将这个要插入的数据放到这个节点的右子树中;

所以:当要查找、删除数据时,遇到值相同的节点,并不能停止查找,而是继续在右子树中查找,直到遇到叶子节点。

------

(七)二叉查找树的复杂度

最糟糕的情况,  当二叉查找树根节点的左右子树极度不平衡时,就退化成了链表,所以查找时间复杂度就变成了 O(n)。

最理想的情况,二叉查找树是一棵完全二叉树,这个时候插入、删除、查找的时间复杂度是 O(height),差不多O(lgn)。

 

显然,极度不平衡的二叉查找树,它的查找性能肯定不能满足我们的需求。我们需要构建一种不管怎么删除、插入数据,在任何时候,都能保持任意节点左右子树都比较平衡的二叉查找树,这就是我们下一节课要详细讲的,一种特殊的二叉查找树,平衡二叉查找树。平衡二叉查找树的高度接近 logn,所以插入、删除、查找操作的时间复杂度也比较稳定,是 O(logn)。

-----

(八)二叉查找树和Hash表对比

散列表那节中讲过,散列表的插入、删除、查找操作的时间复杂度可以做到常量级的 O(1),非常高效。

而二叉查找树在比较平衡的情况下,插入、删除、查找操作时间复杂度才是 O(logn),相对散列表,好像并没有什么优势,那我们为什么还要用二叉查找树呢?我认为有下面几个原因:

(1)数据有序性

散列表中的数据是无序存储的,如果要输出有序的数据,需要先进行排序。而对于二叉查找树来说,我们只需要中序遍历,就可以在 O(n) 的时间复杂度内,输出有序的数据序列。

(2)性能稳定性

散列表扩容耗时很多,而且当遇到散列冲突时,性能不稳定,尽管二叉查找树的性能不稳定,但是在工程中,我们最常用的平衡二叉查找树的性能非常稳定,时间复杂度稳定在 O(logn)。

所以:

散列表的构造比二叉查找树要复杂,需要考虑的东西很多。比如散列函数的设计、冲突解决办法、扩容、缩容等。平衡二叉查找树只需要考虑平衡性这一个问题,而且这个问题的解决方案比较成熟、固定。

综合这几点,平衡二叉查找树在某些方面还是优于散列表的,所以,这两者的存在并不冲突。我们在实际的开发过程中,需要结合具体的需求来选择使用哪一个。

------

(九)二叉查找树的其他操作
http://blog.csdn.net/legend050709/article/details/38847103

-----

(十)二叉平衡树:AVL
(1)定义:
高度平衡的二叉查找树为AVL树,即左右子树高度差的绝对值小于等于1的二叉查找树。
(2)节点的平衡因子:bf
节点 的平衡因子为该节点的左子树的高度减去右子树的高度,所以AVL树的所有节点的平衡因子为-1,0,或1.
(3)AVL树的节点的类型定义:

typedef struct AVLNode{
     DataType data;
     AVLNode* lchild, *rchild;
     int bf;//平衡因子
}

(4)完全二叉树是高度平衡的,但是它不一定满足二叉查找树的定义。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值