7.二叉搜索樹

[TOC]

寫在前面

本篇文章整理《數(shù)據(jù)結(jié)構(gòu)(C++語言版)》關(guān)于二叉樹這種線性結(jié)構(gòu)知識點。
整個數(shù)據(jù)結(jié)構(gòu)部分章節(jié)列表如下:
1 向量
2 列表
3 棧
4 隊列

5 二叉樹
6 圖
7 二叉搜索樹

7 二叉搜索樹

設(shè)計初衷:高效的動態(tài)修改(如insert, remove)與高高效的靜態(tài)查找(search)能否兼顧?

循關(guān)鍵碼訪問

類似于python中的dict類型骑科,詞條對象擁有關(guān)鍵碼key與值value并通過key來尋找對應(yīng)節(jié)點罢屈。

template<typename K, typename V> struct Entry {  //詞條模板類
  K key; V value;
  Entry( K k = K(), V v = V() ) : key(k), value(v) {};  //默認構(gòu)造函數(shù)
  Entry( Entry<K, V> const & e) : key(e.key), value(e.value) {};
// 比較器滑绒、判斷器,可用詞條直接代替關(guān)鍵碼
  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 基礎(chǔ)操作

任意一顆二叉樹是二叉搜索樹當(dāng)且僅當(dāng)其中序遍歷序列單調(diào)非降。

BST與中序遍歷

從上圖可以看出盐捷,二叉搜索樹(BST)中序遍歷序列形式上與有序向量相同。
BST模板類
這里假定基本元素類型T或者直接支持比較和判別操作默勾,或者已經(jīng)重載過對應(yīng)操作符。

template<typename T> class BST : public BinTree {  //由二叉樹(BT)派生
public:  //以virtual修飾聚谁,強制要求派生類根據(jù)各自規(guī)則重寫接口
  virtual BinNodePosi(T) & search( cosnt T & );
  virtual BinNodePosi(T) insert( const T & );
  virtual bool remove ( const T & );
protected:
  BinNodePosi(T) _hot;  //命中節(jié)點的父親
  BinNodePosi(T) connect34(  //3+4重構(gòu)
      BinNodePosi(T), BinNodePosi(T), BinNodePosi(T), 
      BinNodePosi(T), BinNodePosi(T), BinNodePosi(T), BinNodePosi(T) );  
  BinNodePosi(T) rotateAt( BinNodePosi(T) );  //旋轉(zhuǎn)調(diào)整
}

關(guān)于虛函數(shù)virtual
當(dāng)基類Base指針ptr指向派生類Derived的成員函數(shù)時母剥,調(diào)用派生類對應(yīng)的成員函數(shù)而不是基類成員函數(shù)(一般情況下,ptr是Base指針形导,其指向應(yīng)為Base成員函數(shù))
>>>
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; //調(diào)用Base成員函數(shù)环疼,打印--called from Base
ptr->echo2; //調(diào)用Derived成員函數(shù),打印--called from Derived
}

7.1.1 search接口

與二分查找類似朵耕,BST節(jié)點的中序遍歷即為單調(diào)有序向量形式炫隶。
如下圖,待查找節(jié)點e與當(dāng)前節(jié)點比較阎曹,若e較大伪阶,則深入該節(jié)點右子樹...依次遞歸。

查找算法

若查找失效处嫌,則將此空節(jié)點視作哨兵節(jié)點栅贴。
這樣無論是否查找成功,命中節(jié)點v都指向e應(yīng)當(dāng)存在的位置熏迹,_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已找到或遞歸節(jié)點v為空
  hot = v;  //hot指向“命中節(jié)點”的父親
  return searchIn( ( ( e < v->data ) ? v->lc : v->rc ), e, hot);
}

7.1.2 插入操作

根據(jù)search接口,命中節(jié)點v即為待查找節(jié)點e應(yīng)當(dāng)存在的位置注暗,因此只需將待插入節(jié)點作為命中節(jié)點v父親_hot的孩子插入即可坛缕。

template<typename T> BinNodePosi(T) BST<T>::insert( const T& e) {
  if( search(e) ) return search(e);  //若待插入節(jié)點存在,則停止插入
  BinNodePosi(T) & x = new BinNode<T> (e, _hot);  //創(chuàng)建新節(jié)點x捆昏,以e為關(guān)鍵碼赚楚,_hot為父
  _size++;
  updateHeightAbove(x);
  return x;
}  

7.1.3 刪除操作

與插入操作類似,首先通過search接口查找需要刪除節(jié)點位置屡立。
以下是刪除操作的代碼框架

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

分為兩種情況考慮勇皇,待刪除節(jié)點只有一個分支或有兩個分支。
出現(xiàn)這種分歧的原因是BST是按照中序遍歷序列排列的焚刺,插入或刪除操作都需要維持原始BST的有序性敛摘。
對于單分支,待刪除節(jié)點下只有比它大(腥橛洹)的節(jié)點兄淫,將其孩子直接代替刪除位置屯远,不影響B(tài)ST有序性。
對于多分支捕虽,待刪除節(jié)點的左(右)孩子替換刪除位置后慨丐,可能會導(dǎo)致新節(jié)點的左(右)子樹中存在比當(dāng)前節(jié)點大(小)的元素泄私。
如下圖房揭,若直接將58代替36會導(dǎo)致40、46晌端、53等比58小的節(jié)點存在右子樹中捅暴。


1 單分支
這種情況下,直接刪除節(jié)點咧纠,并將其孩子替換掉原在位置蓬痒。
2 多分支
保持BST有序性的核心在于將距離待刪除節(jié)點最近的節(jié)點代替刪除位置,隨后重復(fù)單分支操作情況漆羔。
綜上所述梧奢,removeAt()實現(xiàn)代碼如下。

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節(jié)點
  else if( ! HasRChild(*x) ) succ = x =x->lc;
  else {
    w = w->succ();  //通過中序遍歷獲得x的直接后繼钧椰,即大于x的最小節(jié)點
    swap( x->data, w->data );  //只將兩節(jié)點data交換粹断,節(jié)點對應(yīng)的左右孩子指向未變
    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樹

平衡二叉樹----在節(jié)點數(shù)目固定的前提下,盡可能降低樹的高度嫡霞,即盡可能使兄弟子樹的高度彼此接近瓶埋。漸進意義下樹高不超過O(log n)。
平衡因子----節(jié)點v的左右子樹高度差诊沪。

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

AVL樹----各節(jié)點平衡因子的絕對值不超過1.
接口定義----由BST派生得來养筒。

/**********************************
#define  stature(p) ((p) ? (p)->height : -1) //節(jié)點高度(與“空樹高度為-1”的約定相統(tǒng)一)
**********************************/
#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 平衡性

設(shè)高度為h的子樹至少含有S(h)個節(jié)點,則根據(jù)AVL平衡條件有如下等式成立端姚。
S(h) = S(h-1) + S(h-2) + 1

平衡性

節(jié)點刪除--失衡
1.節(jié)點刪除只可能會導(dǎo)致其父節(jié)點失衡
2.被刪除節(jié)點父親的樹高未變(子樹高度最大者+1)晕粪,因而其祖先的平衡因子不會改變。
節(jié)點刪除

節(jié)點插入--失衡
1.節(jié)點插入可能會導(dǎo)致其祖先發(fā)生一連串的失衡渐裸。
2.被刪除節(jié)點父親的樹高可能發(fā)生變化(子樹高度最大者+1)巫湘,因而其祖先的平衡因子可能會打破。
節(jié)點插入

7.2.2 重平衡

插入重平衡
只需維護插入節(jié)點x導(dǎo)致第一個失衡祖先即可昏鹃,其余失衡祖先可隨之一并重平衡尚氛。
設(shè)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 /*等高阅嘶,與父親同側(cè)者優(yōu)先*/ \
    ) \
    ) \
)

單旋----g, p, v同側(cè)
BST中的等價變換為上下可變,左右不變

單旋操作

雙旋----g, p, v不在同側(cè)
雙旋

template<typename T> BInNodePosi(T) AVL<T>::insert( const T& e ) {
  BinNodePosi(T) & x = search(e); if(x) return x;  //判斷待插入節(jié)點是否存在
  BinNodePosi(T) xx = x = new BinNode<T> (e, _hot); _size++; //創(chuàng)建新節(jié)點
//重平衡
  for(BInNodePosi(T) g = _hot; g; g = g->parent) {  //查找首次遇到失衡祖先節(jié)點g
    if( !AvBalanced(*g) ) { // 一旦發(fā)現(xiàn)g失衡讯柔,采用“3+4”算法重平衡
      FromParentTo(*g) = rotateAt( tallerChild ( tallerChild(g) ) );
      break;
    } else //若未失衡抡蛙,只需更新樹高
      updateHeight(g);
  }
  return xx;
}

刪除重平衡
如下圖操作,重平衡后子樹的高度可能繼續(xù)降低魂迄,從而導(dǎo)致失衡向上傳播粗截。
單旋

單旋

雙旋
雙旋

template<typename T> bool AVL<T>::remove( const T& e ) {
  BinNodePosi(T) x = search(e); if(!x) return false; //待刪除節(jié)點不存在,返回
  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算法

將操作節(jié)點x與其先失衡祖先g以及g更高一側(cè)的子樹的孩子節(jié)點p, v設(shè)置為獨立的模塊捣炬。
整理得到如下兩類模塊:
3節(jié)點模塊:g, p, v
4子樹模塊:g不含p的子樹T1, p不含v的子樹T2, v的兩個子樹T3, T4
最終只需按著滿足中序遍歷方法將上述模塊重組為符合BST結(jié)構(gòu)即可慈格。


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);
    }  
}   
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蒜田,隨后出現(xiàn)的幾起案子罗捎,更是在濱河造成了極大的恐慌骗露,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異凿掂,居然都是意外死亡,警方通過查閱死者的電腦和手機笛厦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進店門铆铆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人傀顾,你說我怎么就攤上這事襟铭。” “怎么了短曾?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵寒砖,是天一觀的道長。 經(jīng)常有香客問我嫉拐,道長哩都,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任婉徘,我火速辦了婚禮漠嵌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘盖呼。我一直安慰自己儒鹿,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布塌计。 她就那樣靜靜地躺著挺身,像睡著了一般。 火紅的嫁衣襯著肌膚如雪锌仅。 梳的紋絲不亂的頭發(fā)上章钾,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天墙贱,我揣著相機與錄音,去河邊找鬼贱傀。 笑死惨撇,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的府寒。 我是一名探鬼主播魁衙,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼株搔!你這毒婦竟也來了剖淀?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤纤房,失蹤者是張志新(化名)和其女友劉穎纵隔,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體炮姨,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡捌刮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了舒岸。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绅作。...
    茶點故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蛾派,靈堂內(nèi)的尸體忽然破棺而出俄认,到底是詐尸還是另有隱情,我是刑警寧澤洪乍,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布梭依,位于F島的核電站,受9級特大地震影響典尾,放射性物質(zhì)發(fā)生泄漏役拴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一钾埂、第九天 我趴在偏房一處隱蔽的房頂上張望河闰。 院中可真熱鬧,春花似錦褥紫、人聲如沸姜性。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽部念。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間儡炼,已是汗流浹背妓湘。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留乌询,地道東北人榜贴。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像妹田,于是被迫代替她去往敵國和親唬党。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,612評論 2 350

推薦閱讀更多精彩內(nèi)容

  • 樹的概述 樹是一種非常常用的數(shù)據(jù)結(jié)構(gòu)鬼佣,樹與前面介紹的線性表驶拱,棧,隊列等線性結(jié)構(gòu)不同晶衷,樹是一種非線性結(jié)構(gòu) 1.樹的定...
    Jack921閱讀 4,445評論 1 31
  • 紅黑樹是平衡二叉查找樹的一種屯烦。為了深入理解紅黑樹,我們需要從二叉查找樹開始講起房铭。 BST 二叉查找樹(Binary...
    kanehe閱讀 1,371評論 0 8
  • -二叉搜索樹 查找問題:靜態(tài)查找和動態(tài)查找缸匪,靜態(tài)查找可以用二分查找-判定樹,那么針對動態(tài)查找數(shù)據(jù)如何組織类溢?(樹的動...
    Spicy_Crayfish閱讀 1,346評論 0 2
  • 1 概述 二叉搜索樹凌蔬,顧名思義,其主要目的用于搜索闯冷,它是二叉樹結(jié)構(gòu)中最基本的一種數(shù)據(jù)結(jié)構(gòu)砂心,是后續(xù)理解B樹、B+樹蛇耀、...
    CodingTech閱讀 3,129評論 5 35
  • 端午放假幾天辩诞,有好幾件事都挺觸動。 重來沒有用過簡書纺涤,突然想用簡書發(fā)表出來译暂,不是作為什么寫手,作為作家撩炊,自認不到這...
    博佳_閱讀 202評論 0 0