美文网首页程序员
7.二叉搜索树

7.二叉搜索树

作者: lumo的二次学习笔记 | 来源:发表于2017-12-30 21:57 被阅读0次

[TOC]

写在前面

本篇文章整理《数据结构(C++语言版)》关于二叉树这种线性结构知识点。
整个数据结构部分章节列表如下:
1 向量
2 列表
3 栈
4 队列

5 二叉树
6 图
7 二叉搜索树

7 二叉搜索树

设计初衷:高效的动态修改(如insert, remove)与高高效的静态查找(search)能否兼顾?

循关键码访问

类似于python中的dict类型,词条对象拥有关键码key与值value并通过key来寻找对应节点。

template<typename K, typename V> struct Entry {  //词条模板类
  K key; V value;
  Entry( K k = K(), V v = V() ) : key(k), value(v) {};  //默认构造函数
  Entry( Entry<K, V> const & e) : key(e.key), value(e.value) {};
// 比较器、判断器,可用词条直接代替关键码
  bool operator< ( Entry<K, V> const & e ) { return key < e.key; }
  bool operator> ( Entry<K, V> const & e ) { return key > e.key; }
  bool operator== ( Entry<K, V> const & e ) { return key == e.key; }
  bool operator!= ( Entry<K, V> const & e ) { return key != e.key; }
}

7.1 基础操作

任意一颗二叉树是二叉搜索树当且仅当其中序遍历序列单调非降。

BST与中序遍历
从上图可以看出,二叉搜索树(BST)中序遍历序列形式上与有序向量相同。
BST模板类
这里假定基本元素类型T或者直接支持比较和判别操作,或者已经重载过对应操作符。
template<typename T> class BST : public BinTree {  //由二叉树(BT)派生
public:  //以virtual修饰,强制要求派生类根据各自规则重写接口
  virtual BinNodePosi(T) & search( cosnt T & );
  virtual BinNodePosi(T) insert( const T & );
  virtual bool remove ( const T & );
protected:
  BinNodePosi(T) _hot;  //命中节点的父亲
  BinNodePosi(T) connect34(  //3+4重构
      BinNodePosi(T), BinNodePosi(T), BinNodePosi(T), 
      BinNodePosi(T), BinNodePosi(T), BinNodePosi(T), BinNodePosi(T) );  
  BinNodePosi(T) rotateAt( BinNodePosi(T) );  //旋转调整
}

关于虚函数virtual
当基类Base指针ptr指向派生类Derived的成员函数时,调用派生类对应的成员函数而不是基类成员函数(一般情况下,ptr是Base指针,其指向应为Base成员函数)
>>>
class Base {
public:
void echo1() { std::cout << "called from Base" << endl; }
virtual void echo2() { std::cout << "called from Base" << endl; }
}
class Derived : public Base {
public:
void echo1() { std::cout << "called from Derived" << endl; }
void echo2() { std::cout << "called from Derived" << endl; }
}
int main() {
Base * ptr1 = new Derived;
ptr->echo1; //调用Base成员函数,打印--called from Base
ptr->echo2; //调用Derived成员函数,打印--called from Derived
}

7.1.1 search接口

与二分查找类似,BST节点的中序遍历即为单调有序向量形式。
如下图,待查找节点e与当前节点比较,若e较大,则深入该节点右子树...依次递归。

查找算法
若查找失效,则将此空节点视作哨兵节点
这样无论是否查找成功,命中节点v都指向e应当存在的位置,_hot指向v的父亲
接口语义
template<typename T> BinNodePosi(T) & BST<T>::search(const T& e) {
  return searchIn(_root, e, _hot = NULL);
}
template<typename T>
static BinNodePosi(T) & searchIn(BinNodePosi(T) & v, const T& e, BInNodePosi(T) & hot){
  if( !v || ( e == v->data ) ) return v;  //待search值e已找到或递归节点v为空
  hot = v;  //hot指向“命中节点”的父亲
  return searchIn( ( ( e < v->data ) ? v->lc : v->rc ), e, hot);
}

7.1.2 插入操作

根据search接口,命中节点v即为待查找节点e应当存在的位置,因此只需将待插入节点作为命中节点v父亲_hot的孩子插入即可。

template<typename T> BinNodePosi(T) BST<T>::insert( const T& e) {
  if( search(e) ) return search(e);  //若待插入节点存在,则停止插入
  BinNodePosi(T) & x = new BinNode<T> (e, _hot);  //创建新节点x,以e为关键码,_hot为父
  _size++;
  updateHeightAbove(x);
  return x;
}  

7.1.3 删除操作

与插入操作类似,首先通过search接口查找需要删除节点位置。
以下是删除操作的代码框架

template<typename T> bool BST<T>::remove( cosnt T& e ) {
  BinNodePosi(T) & x = search(e);
  if(!x) return false;  //待删除元素不存在,返回
  removeAt(x, _hot);  //删除操作,需考虑两种情况。
  size--;
  updateHeightAbove(_hot);
  return true;
}

分为两种情况考虑,待删除节点只有一个分支或有两个分支。
出现这种分歧的原因是BST是按照中序遍历序列排列的,插入或删除操作都需要维持原始BST的有序性。
对于单分支,待删除节点下只有比它大(小)的节点,将其孩子直接代替删除位置,不影响BST有序性。
对于多分支,待删除节点的左(右)孩子替换删除位置后,可能会导致新节点的左(右)子树中存在比当前节点大(小)的元素。
如下图,若直接将58代替36会导致40、46、53等比58小的节点存在右子树中。


1 单分支
这种情况下,直接删除节点,并将其孩子替换掉原在位置。
2 多分支
保持BST有序性的核心在于将距离待删除节点最近的节点代替删除位置,随后重复单分支操作情况。
综上所述,removeAt()实现代码如下。
static BinNodePosi(T) removeAt( BinNodePosi(T) & x, BinNodePosi & hot ) {
  BinNodePosi(T) w = x;
  BinNodePosi(T) succ = NULL;
  if( ! HasLChild(*x) ) succ = x = x->rc;  //直接将rc覆盖x即可删除x节点
  else if( ! HasRChild(*x) ) succ = x =x->lc;
  else {
    w = w->succ();  //通过中序遍历获得x的直接后继,即大于x的最小节点
    swap( x->data, w->data );  //只将两节点data交换,节点对应的左右孩子指向未变
    BinNodePosi(T) u = w->parent;
    ( ( u == x) ? u->rc : u->lc ) = succ = w->rc;
  }
  hot = w->parent;
  if(succ) succ->parent = hot;
  release( w->data ); release(w);
  return succ;
} 

7.2 AVL树

平衡二叉树----在节点数目固定的前提下,尽可能降低树的高度,即尽可能使兄弟子树的高度彼此接近。渐进意义下树高不超过O(log n)。
平衡因子----节点v的左右子树高度差。

balFac(v) = height(lc(v)) - height(rc(v))

AVL树----各节点平衡因子的绝对值不超过1.
接口定义----由BST派生得来。

/**********************************
#define  stature(p) ((p) ? (p)->height : -1) //节点高度(与“空树高度为-1”的约定相统一)
**********************************/
#define Balanced(x) ( stature( (x).lc ) == stature( (x).rc ) )  //理想平衡条件
#define BalFac(x) ( stature( (x).lc ) - stature( (x).rc ) )  //平衡因子
#define AvlBalanced(x) ( ( -2 < BalFac(x) ) && ( BalFac(x) < 2) )  //AVL平衡条件
template<typename T> class AVL : public BST<T> {
public:
  BinNodePosi(T) insert( const T& e);
  bool remove( const T& e);
}

7.2.1 平衡性

设高度为h的子树至少含有S(h)个节点,则根据AVL平衡条件有如下等式成立。
S(h) = S(h-1) + S(h-2) + 1

平衡性
节点删除--失衡
1.节点删除只可能会导致其父节点失衡
2.被删除节点父亲的树高未变(子树高度最大者+1),因而其祖先的平衡因子不会改变。
节点删除
节点插入--失衡
1.节点插入可能会导致其祖先发生一连串的失衡。
2.被删除节点父亲的树高可能发生变化(子树高度最大者+1),因而其祖先的平衡因子可能会打破。
节点插入

7.2.2 重平衡

插入重平衡
只需维护插入节点x导致第一个失衡祖先即可,其余失衡祖先可随之一并重平衡。
设g是首次失衡祖先,通过如下代码即可完成由g找到x的祖先p或v。

/**************************************
#define IsLChild(x) ( ! IsRoot(x) && ( & (x) == (x).parent->lc ) )
该部分在BinNode中定义
**************************************/
#define tallerChild(x) ( \
    stature( (x)->lc ) > stature( (x)->rc ) ? (x)->lc : ( /*左子树高*/ \
    stature( (x)->lc ) < stature( (x)->rc ) ? (x)->rc : ( /*右子树高*/ \
    IsLChild( * (x) ) ? (x)->lc : (x)->rc /*等高,与父亲同侧者优先*/ \
    ) \
    ) \
)

单旋----g, p, v同侧
BST中的等价变换为上下可变,左右不变

单旋操作
双旋----g, p, v不在同侧
双旋
template<typename T> BInNodePosi(T) AVL<T>::insert( const T& e ) {
  BinNodePosi(T) & x = search(e); if(x) return x;  //判断待插入节点是否存在
  BinNodePosi(T) xx = x = new BinNode<T> (e, _hot); _size++; //创建新节点
//重平衡
  for(BInNodePosi(T) g = _hot; g; g = g->parent) {  //查找首次遇到失衡祖先节点g
    if( !AvBalanced(*g) ) { // 一旦发现g失衡,采用“3+4”算法重平衡
      FromParentTo(*g) = rotateAt( tallerChild ( tallerChild(g) ) );
      break;
    } else //若未失衡,只需更新树高
      updateHeight(g);
  }
  return xx;
}

删除重平衡
如下图操作,重平衡后子树的高度可能继续降低,从而导致失衡向上传播。
单旋

单旋
双旋
双旋
template<typename T> bool AVL<T>::remove( const T& e ) {
  BinNodePosi(T) x = search(e); if(!x) return false; //待删除节点不存在,返回
  removeAt( x, _hot); _size--;
  for(BinNodePosi(T) g = _hot; g; g = g->parent) {
    if( !AvlBalanced(*g) )
      g = FromParentTo(*g) = rotateAt( tallerChild( tallerChild(g) ) );
    updateHeight(g);
  }
  return true;
}

7.2.3 3+4算法

将操作节点x与其先失衡祖先g以及g更高一侧的子树的孩子节点p, v设置为独立的模块。
整理得到如下两类模块:
3节点模块:g, p, v
4子树模块:g不含p的子树T1, p不含v的子树T2, v的两个子树T3, T4
最终只需按着满足中序遍历方法将上述模块重组为符合BST结构即可。


3+4算法
template<typename T> BinNodePosi(T) BST<T>::connect34 (
  BinNodePosi(T) a, BinNodePosi(T) b, BinNodePosi(T) c,
  BinNodePosi(T) T0, BinNodePosi(T) T1, BinNodePosi(T) T2, BinNodePosi(T) T3
) {
  a->lc = T0; if(T0) T0->parent = a;
  a->rc = T1; if(T1) T1->parent = a;
  c->lc = T2; if(T2) T2->parent = c;
  c->rc = T3; if(T3) T3->parent = c;
  b->lc = a; a->parent = b;
  b->rc = c; c->parent = b;
  updateHeight(b);
}

继而可以将单旋、双旋等操作直接整合如下。

template<typename T> BinNodePosi(T) BST<T>::rotateAt(BinNodePosi(T) v) {
  BinNodePosi(T) p = v->parent;
  BinNodePosi(T) g = p->parent;
//视p, v, g相对位置分为四种情况
  if( IsLChild(*p) ) /*zig*/
    if( IsLChild(*v) ) {/*zig-zig*/
      p->parent = g->parent;
      return connect34(v, p, g, v->lc, v->rc, p->rc, g->rc);
    } else { /*zig-zag*/
       v->parent = g->parent;
      return connect34(p, v, g, p->lc, v->lc, v->rc, g->rc);
  } 
  else /* zag*/
    if( IsRChild(*v) ) { /*zag-zag*/
      p->parent = g->parent;
      return connect34(g, p, v, g->lc, p->lc, v->lc, v->rc);
    } else { /*zag-zig*/
      v->parent = g->parent;
      return connect34(g, v, p, g->lc, v->lc, v->rc, p->rc);
    }  
}   

相关文章

  • 数据结构与算法之二叉搜索树(八)

    目录 二叉搜索树概念二叉搜索树的接口设计,包括增,删,改,查平衡二叉搜索树 一 二叉搜索树 二叉搜索树是二叉树的一...

  • Algorithm小白入门 -- 二叉搜索树

    二叉搜索树二叉搜索树 BSTBST 的基本操作计算合法的 BST 1. 二叉搜索树 BST 二叉搜索树(Binar...

  • 二叉搜索树

    二叉搜索树 图解二叉树搜索算法图解:二叉搜索树算法二叉查找树(Binary Search Tree),(又:二叉搜...

  • 23-红黑树

    1.二叉搜索树(BST)继承二叉树(BinaryTree) 2.平衡二叉搜索树(BBST)继承二叉搜索树(BST)...

  • 二叉搜索树(Binary Search Tree)

    1. 定义 二叉搜索树(BST)又叫二叉查找树,二叉排序树。二叉搜索树就是一棵二叉树,但是它又具有搜索树的特征: ...

  • 二叉树基础

    二叉树的分类 完全二叉树与满二叉树 二叉搜索树BST 平衡二叉搜索树BBST因为二叉搜索树有可能退化为链表,降低查...

  • 数据结构 经典算法复习

    二叉搜索树, 平衡二叉树(AVL) 红黑树 B树(平衡多路搜索树) B+树(在B树上改造) 二叉搜索树...

  • Swift 验证二叉搜索树- LeetCode

    题目: 验证二叉搜索树 验证二叉搜索树给定一个二叉树,判断其是否是一个有效的二叉搜索树。 假设一个二叉搜索树具有...

  • 树,二叉树,搜索树

    树,二叉树,搜索树 资料 二叉搜索树 Demo 树的遍历 Demo 题目 ◎ 二叉树的中序遍历 ◎ 二叉树...

  • 【数据结构】红黑树

    1、什么是红黑树? 红黑树是一个要求不那么严格的平衡二叉树搜索树(平衡二叉搜索树/AVL树=平衡二叉树+二叉搜索树...

网友评论

    本文标题:7.二叉搜索树

    本文链接:https://www.haomeiwen.com/subject/mjugwxtx.html