樹(shù)的應(yīng)用4——二叉樹(shù)查找BST

方法介紹:

通過(guò)二叉查找樹(shù)保存Key湖笨,實(shí)現(xiàn)快速查找
還有散列表法(散列及解決沖突)舀寓,與有序表法(二分查找)

BST定義:

左子樹(shù)節(jié)點(diǎn)key比根節(jié)點(diǎn)來(lái)的小,右子樹(shù)節(jié)點(diǎn)key比根節(jié)點(diǎn)大杜秸。


image.png

可以看出外永,根據(jù)插入值的順序,生成的BST樹(shù)也不一樣匙铡。不是完全有序图甜。

BST方法介紹:

  1. put、get方法:
    插入BST中鳖眼,創(chuàng)建新節(jié)點(diǎn)黑毅。
    沒(méi)啥可說(shuō)的,注意遞歸具帮,遞歸結(jié)束條件為找到合適的位置博肋,put創(chuàng)建樹(shù)節(jié)點(diǎn)加入到子節(jié)點(diǎn)中去低斋;get方法將找到的節(jié)點(diǎn)return。
  2. delete方法:
    移除當(dāng)前BST的某個(gè)節(jié)點(diǎn)匪凡。
    ①先通過(guò)get方法去找key膊畴,找到需要?jiǎng)h除的節(jié)點(diǎn)。
    ②判斷節(jié)點(diǎn)是否為葉節(jié)點(diǎn)病游,若為葉節(jié)點(diǎn)唇跨,直接刪除(令葉節(jié)點(diǎn)為None)
    ③若有左右子節(jié)點(diǎn),則去找后繼節(jié)點(diǎn)
    后繼節(jié)點(diǎn)法:通過(guò)看刪除節(jié)點(diǎn)的右子節(jié)點(diǎn)中最小的那個(gè)節(jié)點(diǎn)(取最靠近待刪除節(jié)點(diǎn)的值取代刪除的節(jié)點(diǎn)值衬衬。)找到后繼節(jié)點(diǎn)后买猖,拆除待刪除節(jié)點(diǎn)并放入后繼節(jié)點(diǎn)
    拆除方法:若后繼節(jié)點(diǎn)為葉節(jié)點(diǎn)直接摘除;如果不是滋尉,由于后繼節(jié)點(diǎn)肯定是左節(jié)點(diǎn)玉控,且肯定只有右子節(jié)點(diǎn),將后繼節(jié)點(diǎn)的右節(jié)點(diǎn)放在后繼節(jié)點(diǎn)父節(jié)點(diǎn)的左節(jié)點(diǎn)狮惜。


    image.png

    ④如果只有左節(jié)點(diǎn)或者右節(jié)點(diǎn)
    一高诺、 如果被刪除的節(jié)點(diǎn)有左子節(jié)點(diǎn):若被刪除的節(jié)點(diǎn)是左子節(jié)點(diǎn),則刪除碾篡;右子也是一樣虱而;如果被刪除的節(jié)點(diǎn)是根節(jié)點(diǎn),則直接去他的左子節(jié)點(diǎn)替換开泽。
    二牡拇、 如果被刪除的節(jié)點(diǎn)有右子節(jié)點(diǎn):同上

算法分析:

查找算法復(fù)雜度:如果隨機(jī)分布,則兩邊平均分布key值大致相等
put方法的復(fù)雜度為O(log2n)

AVL樹(shù)概念:

AVL樹(shù)的定義(平衡二叉查找樹(shù)):
對(duì)每個(gè)節(jié)點(diǎn)引入平衡因子參數(shù)
平衡因子:左右子樹(shù)的高度差穆律。

AVL樹(shù)平衡因子為0惠呼,-1,1

AVL樹(shù)搜索的時(shí)間復(fù)雜度:O(logn)


image.png

AVL樹(shù)方法:

  1. 新key以葉節(jié)點(diǎn)插入BST
  2. 重新平衡众旗,并修改父節(jié)點(diǎn)的平衡因子(左重或者右重罢杉,進(jìn)行旋轉(zhuǎn)重新平衡AVL樹(shù))趟畏,
    直到兩種情況:①根節(jié)點(diǎn)②父節(jié)點(diǎn)的平衡因子被調(diào)整為0
    旋轉(zhuǎn)方法如下:
    1.左重贡歧,右旋
    2.右重,左轉(zhuǎn)赋秀,如果右子節(jié)點(diǎn)不存在左重利朵,
    則如下圖,把右子節(jié)點(diǎn)B變?yōu)楦?jié)點(diǎn)猎莲,將A作為B的左子節(jié)點(diǎn)绍弟,若原B有左子節(jié)點(diǎn),則把該節(jié)點(diǎn)掛在A的右子節(jié)點(diǎn)處著洼。
    更新父節(jié)點(diǎn)引用樟遣,并更新平衡因子
image.png

image.png

如果右子節(jié)點(diǎn)存在左重
則先進(jìn)行右旋而叼,在實(shí)施左旋


image.png

右旋的代碼類似左旋

總體代碼如下:

# BST樹(shù)的構(gòu)建
class BinarySearchTree:
    def __init__(self):
        # 引用根節(jié)點(diǎn)TreeNode類
        self.root = None
        self.size = 0

    def length(self):
        return self.size

    def __len__(self):
        return self.size

    # 迭代器 實(shí)現(xiàn) for key in tree
    def __iter__(self):
        return self.root.__iter__()

    # 插入key,構(gòu)造BST
    def put(self, key, val):
        if self.root:
            self._put(key, val, self.root)
        else:
            self.root = TreeNode(key, val)
        self.size += 1

    def _put(self, key, val, currentNode):
        if key < currentNode.key:
            if currentNode.hasleftchild():
                self._put(key, val, currentNode.leftchild)
            else:
                currentNode.leftchild = TreeNode(key, val, parent=currentNode)
        else:
            if currentNode.hasrightchild():
                self._put(key, val, currentNode.rightchild)
            else:
                currentNode.rightchild = TreeNode(key, val, parent=currentNode)

    # 索引賦值:實(shí)現(xiàn) mytree[key] = value
    def __setitem__(self, key, value):
        self.put(key, value)

    # 在二叉樹(shù)中找到key所在的節(jié)點(diǎn)并取值
    def get(self, key):
        if self.root:
            res = self._get(key, self.root)
            if res:
                return res.payload
            else:
                return None
        else:
            return None

    def _get(self, key, currentNode):
        if not currentNode:
            return None
        elif currentNode.key == key:
            return currentNode
        elif key < currentNode.key:
            return self._get(key, currentNode.leftchild)
        else:
            return self._get(key, currentNode.rightchild)

    # AVL樹(shù)的方法:
    '''def _put(self, key, val, currentNode):
        if key < currentNode.key:
            if currentNode.hasleftchild():
                return self._put(key, val, currentNode.leftchild)
            else:
                currentNode.leftchild = TreeNode(key, val, parent=currentNode)
                # 更新平衡因子
                self.updateBalance(currentNode.leftchild)
        else:
            if currentNode.hasrightchild():
                return self._put(key, val, currentNode.rightchild)
            else:
                currentNode.rightchild = TreeNode(key, val, parent=currentNode)
                self.updateBalance(currentNode.rightchild)

    def updateBalance(self, node):
        # 若節(jié)點(diǎn)不平衡豹悬,需要調(diào)整(旋轉(zhuǎn))
        if node.balancefactor > 1 or node.balancefactor < -1:
            self.rebalance(node)
            return
        # 如果在-1至1之間葵陵,平衡,看是否有父節(jié)點(diǎn)瞻佛,如果有脱篙,調(diào)整父節(jié)點(diǎn)
        if node.parent is not None:
            if node.isleftchild():
                node.parent.balancefactor += 1
            elif node.isrightchild():
                node.parent.balancefactor -= 1
            # 調(diào)整以后的父節(jié)點(diǎn)平衡因子不為0,則再調(diào)整父節(jié)點(diǎn)的父節(jié)點(diǎn)平衡因子
            if node.parent.balancefactor != 0:
                self.updateBalance(node.parent)

    # 左旋的方法
    def rotateLeft(self, rotRoot):
        newRoot = rotRoot.rightchild
        rotRoot.rightchild = newRoot.leftchild
        if newRoot.leftchild is not None:
            newRoot.leftchild.parent = rotRoot
        newRoot.parent = rotRoot.parent
        if rotRoot.isroot():
            self.root = newRoot
        else:
            if rotRoot.isleftchild():
                rotRoot.parent.leftchild = newRoot
            else:
                rotRoot.parent.rightchild = newRoot
        newRoot.leftchild = rotRoot
        rotRoot.parent = newRoot
        rotRoot.balancefactor = rotRoot.balancefactor + 1 - min(newRoot.balancefactor, 0)
        rotRoot.balancefactor = newRoot.balancefactor + 1 + max(rotRoot.balancefactor, 0)

    def rebalance(self, node):
        # 節(jié)點(diǎn)是不是左重
        if node.balancefactor < 0:
            # 左重的子節(jié)點(diǎn)是不是右重
            if node.rightchild.balancefactor > 0:
                self.rotateRight(node.rightchild)
                self.rotateLeft(node)
            else:
                self.rotateLeft(node)
        elif node.balancefactor > 0:
            if node.leftchild.balancefactor < 0:
                self.rotateLeft(node.leftchild)
                self.rotateRight(node)
            else:
                self.rotateRight(node)'''

    # 索引取值 實(shí)現(xiàn) value = mytree[key]
    def __getitem__(self, key):
        return self.get(key)

    # 尋找索引 實(shí)現(xiàn) key in mytree
    def __contains__(self, key):
        if self._get(key, self.root):
            return True
        else:
            return False

    # 迭代器實(shí)現(xiàn) 實(shí)現(xiàn) for key in tree
    def __iter__(self):
        if self:
            if self.hasleftchild():
                for elem in self.leftchild:
                    yield elem
            yield self.key
            if self.hasrightchild():
                for elem in self.rightchild:
                    yield elem

    # 刪除方法
    def delete(self, key):
        if self.size > 1:
            node_to_remove = self._get(key, self.root)
            if node_to_remove:
                self.remove(node_to_remove)
                self.size -= 1
            else:
                raise KeyError('error, key not in tree')
        elif self.size == 1 and self.root.key == key:
            self.root = None
            self.size -= 1
        else:
            raise KeyError('error, key not in tree')

    # 實(shí)現(xiàn)方法 del tree(key)
    def __delitem__(self, key):
        self.delete(key)

    def remove(self, currentNode):
        # 判斷節(jié)點(diǎn)是否為葉節(jié)點(diǎn)伤柄,若為葉節(jié)點(diǎn)绊困,直接刪除(令葉節(jié)點(diǎn)為None)
        if currentNode.isleaf():
            if currentNode == currentNode.parent.leftchild():
                currentNode.parentchild.leftchild = None
            else:
                currentNode.parentchild.rightchild = None
        else:
            if currentNode.hasbothchildren():
                 # 后繼節(jié)點(diǎn)法:通過(guò)看刪除節(jié)點(diǎn)的右子節(jié)點(diǎn)中最小的那個(gè)節(jié)點(diǎn)(取最靠近待刪除節(jié)點(diǎn)的值取代刪除的節(jié)點(diǎn)值。)找到后繼節(jié)點(diǎn)后适刀,拆除待刪除節(jié)點(diǎn)并放入后繼節(jié)點(diǎn)
                succ = currentNode.findsuccessor()
                succ.spliceOut()
                currentNode.key = succ.key
                currentNode.payload = succ.payload

            elif currentNode.hasleftchild():
                # 如果被刪除的節(jié)點(diǎn)有左子節(jié)點(diǎn)
                if currentNode.isleftchild():
                    currentNode.leftchild.parent = currentNode.parent
                    currentNode.parent.leftchild = currentNode.leftchild
                elif currentNode.isrightchild():
                    currentNode.leftchild.parent = currentNode.parent
                    currentNode.parent.rightchild = currentNode.leftchild
                else:
                    currentNode.replacenodedata(currentNode.leftchild.key,
                                                currentNode.leftchild.payload,
                                                currentNode.leftchild.leftchild,
                                                currentNode.leftchild.rightchild)
            else:
                # 如果被刪除的節(jié)點(diǎn)有右子節(jié)點(diǎn)
                if currentNode.isleftchild():
                    currentNode.rightchild.parent = currentNode.parent
                    currentNode.parent.leftchild = currentNode.rightchild
                elif currentNode.isrightchild():
                    currentNode.rightchild.parent = currentNode.parent
                    currentNode.parent.righchild = currentNode.rightchild
                else:
                    currentNode.replacenodedata(currentNode.rightchild.key,
                                                currentNode.rightchild.payload,
                                                currentNode.rightchild.leftchild,
                                                currentNode.rightchild.rightchild)

    # 找到后繼節(jié)點(diǎn)
    def findsuccessor(self):
        succ = None
        if self.hasrightchild():
            succ = self.rightchild().findmin()
        else:
            '''if self.parent:
                if self.isleftchild():
                    succ = self.parent
                else:
                    self.parent.rightchild = None
                    succ = self.parent.findsuccessor()
                    self.parent.rightchild = self'''
        return succ

    # 找到最小值節(jié)點(diǎn)
    def findmin(self):
        current = self
        while current.hasleftchild():
            current = current.leftchild
        return current

    # 將前面找到的節(jié)點(diǎn)摘出
    def spliceOut(self):
        # 若后繼節(jié)點(diǎn)為葉節(jié)點(diǎn)直接摘除
        if self.isleaf():
            if self.isleftchild():
                self.parent.leftchild = None
            '''else:
                self.parent.rightchild = None'''

        elif self.hasanychildren():
            if self.hasleftchild():
                '''if self.isleftchild():
                    self.parent.leftchild = self.leftchild
                else:
                    self.parent.rightchild = self.leftchild
                self.leftchild.parent = self.parent'''
        else:
            # 由于后繼節(jié)點(diǎn)肯定是左節(jié)點(diǎn)秤朗,且肯定只有右子節(jié)點(diǎn),將后繼節(jié)點(diǎn)的右節(jié)點(diǎn)放在后繼節(jié)點(diǎn)父節(jié)點(diǎn)的左節(jié)點(diǎn)笔喉。
            if self.isleftchild():
                self.parent.leftchild = self.rightchild
            else:
                '''self.parent.rightchild = self.rightchild'''
            self.rightchild.parent = self.parent


# 樹(shù)節(jié)點(diǎn)定義川梅,BST需要引用樹(shù)節(jié)點(diǎn)類
class TreeNode:
    def __init__(self, key, val, left=None, right=None, parent=None):
        self.key = key
        # key值對(duì)應(yīng)存儲(chǔ)的實(shí)際值
        self.payload = val
        self.leftchild = left
        self.rightchild = right
        self.parent = parent

    def hasleftchild(self):
        return self.leftchild

    def hasrightchild(self):
        return self.rightchild

    def isleftchild(self):
        return self.parent and self.parent.leftchild == self

    def isrightchild(self):
        return self.parent and self.parent.rightchild == self

    def isroot(self):
        return not self.parent

    def isleaf(self):
        return not (self.rightchild or self.leftchild)

    def hasanychildren(self):
        return self.rightchild or self.leftchild

    def hasbothchildren(self):
        return self.rightchild and self.leftchild

    def replacenodedata(self, key, val, lc, rc):
        self.key = key
        self.payload = val
        self.leftchild = lc
        self.rightchild = rc
        if self.hasleftchild():
            self.leftchild.parent = self
        if self.hasrightchild():
            self.rightchild.parent = self

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市然遏,隨后出現(xiàn)的幾起案子贫途,更是在濱河造成了極大的恐慌,老刑警劉巖待侵,帶你破解...
    沈念sama閱讀 221,273評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件丢早,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡秧倾,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門那先,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)农猬,“玉大人,你說(shuō)我怎么就攤上這事售淡〗锎校” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 167,709評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵揖闸,是天一觀的道長(zhǎng)揍堕。 經(jīng)常有香客問(wèn)我,道長(zhǎng)汤纸,這世上最難降的妖魔是什么衩茸? 我笑而不...
    開(kāi)封第一講書人閱讀 59,520評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮贮泞,結(jié)果婚禮上楞慈,老公的妹妹穿的比我還像新娘幔烛。我一直安慰自己,他們只是感情好囊蓝,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布说贝。 她就那樣靜靜地躺著,像睡著了一般慎颗。 火紅的嫁衣襯著肌膚如雪乡恕。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 52,158評(píng)論 1 308
  • 那天俯萎,我揣著相機(jī)與錄音傲宜,去河邊找鬼。 笑死夫啊,一個(gè)胖子當(dāng)著我的面吹牛函卒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播撇眯,決...
    沈念sama閱讀 40,755評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼报嵌,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了熊榛?” 一聲冷哼從身側(cè)響起源武,我...
    開(kāi)封第一講書人閱讀 39,660評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤末秃,失蹤者是張志新(化名)和其女友劉穎顶瞒,沒(méi)想到半個(gè)月后害幅,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,203評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡煎楣,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評(píng)論 3 340
  • 正文 我和宋清朗相戀三年豺总,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片择懂。...
    茶點(diǎn)故事閱讀 40,427評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡喻喳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出困曙,到底是詐尸還是另有隱情表伦,我是刑警寧澤,帶...
    沈念sama閱讀 36,122評(píng)論 5 349
  • 正文 年R本政府宣布赂弓,位于F島的核電站绑榴,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏盈魁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評(píng)論 3 333
  • 文/蒙蒙 一窃诉、第九天 我趴在偏房一處隱蔽的房頂上張望杨耙。 院中可真熱鬧赤套,春花似錦、人聲如沸珊膜。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,272評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)车柠。三九已至剔氏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間竹祷,已是汗流浹背谈跛。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,393評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留塑陵,地道東北人感憾。 一個(gè)月前我還...
    沈念sama閱讀 48,808評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像令花,于是被迫代替她去往敵國(guó)和親阻桅。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評(píng)論 2 359