自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Treap——堆和二叉樹的完美結(jié)合,性價(jià)比極值的搜索樹

大數(shù)據(jù) 數(shù)據(jù)分析
Treap本質(zhì)上也是一顆BST(平衡二叉搜索樹),和我們之前介紹的SBT是一樣的。但是Treap維持平衡的方法和SBT不太一樣,有些許區(qū)別,相比來說呢,Treap的原理還要再簡單一些,所以之前在競賽當(dāng)中不允許使用STL的時(shí)候,我們通常都會手寫一棵Treap來代替。

[[357058]]

大家好,今天和大家聊一個(gè)新的數(shù)據(jù)結(jié)構(gòu),叫做Treap。

Treap本質(zhì)上也是一顆BST(平衡二叉搜索樹),和我們之前介紹的SBT是一樣的。但是Treap維持平衡的方法和SBT不太一樣,有些許區(qū)別,相比來說呢,Treap的原理還要再簡單一些,所以之前在競賽當(dāng)中不允許使用STL的時(shí)候,我們通常都會手寫一棵Treap來代替。

Treap的基本原理

既然是平衡二叉搜索樹,關(guān)鍵點(diǎn)就在于平衡,那么重點(diǎn)自然是如何維護(hù)樹的平衡。

在Treap當(dāng)中,維護(hù)平衡非常簡單,只有一句話,就是通過維護(hù)小頂堆的形式來維持樹的平衡。Treap也正是因此得名,因?yàn)樗荰ree和Heap的結(jié)合體。

我們來看下Treap當(dāng)中節(jié)點(diǎn)的結(jié)構(gòu):

  1. class TreapNode(TreeNode): 
  2.     ""
  3.     TreeNode: The node class of treap tree. 
  4.     Paramters:  
  5.         key: The key of node, can be treated as the key of dictionary 
  6.         value: The value of node, can be treated as the value of dictionary 
  7.         priority: The priority of node, specially for treap structure, describe the priority of the node in the treap.  
  8.         lchild: The left child of node 
  9.         rchild: The right child of node 
  10.         father: The parent of node, incase that we need to remove or rotate the node in the treap, so we need father parameter to mark the address of the parent 
  11.     ""
  12.     def __init__(self, key=None, value=None, lchild=None, rchild=None, father=None, priority=None): 
  13.         super().__init__(key, value, lchild, rchild, father) 
  14.         self._priority = priority 
  15.  
  16.     @property 
  17.     def priority(self): 
  18.         return self._priority 
  19.  
  20.     @priority.setter 
  21.     def priority(self, priority): 
  22.         self._priority = priority 
  23.  
  24.     def __str__(self): 
  25.         return 'key={}, value={}'.format(self.key, self.value) 

這里的TreeNode是我抽象出來的樹結(jié)構(gòu)通用的Node,當(dāng)中包含key、value、lchild、rchild和father。TreapNode其實(shí)就是在此基礎(chǔ)上增加了一個(gè)priority屬性。

之所以要增加這個(gè)priority屬性是為了維護(hù)它堆的性質(zhì),通過維護(hù)這個(gè)堆的性質(zhì)來保持樹的平衡。具體的操作方法,請往下看。

Treap的增刪改查

插入

首先來講Treap的插入元素的操作,其實(shí)插入元素的操作非常簡單,就是普通BST插入元素的操作。唯一的問題是如何維持樹的平衡。

我們前文說了,我們是通過維持堆的性質(zhì)來保持平衡的,那么自然又會有一個(gè)新的問題。為什么維持堆的性質(zhì)可以保證平衡呢?

答案很簡單,因?yàn)槲覀冊诓迦氲臅r(shí)候,需要對每一個(gè)插入的Node隨機(jī)附上一個(gè)priority。堆就是用來維護(hù)這個(gè)priority的,保證樹根一定擁有最小的priority。正是由于這個(gè)priority是隨機(jī)的,我們可以保證整棵樹蛻化成線性的概率降到無窮低。

當(dāng)我們插入元素之后發(fā)現(xiàn)破壞了堆的性質(zhì),那么我們需要通過旋轉(zhuǎn)操作來維護(hù)。舉個(gè)簡單的例子,在下圖當(dāng)中,如果B節(jié)點(diǎn)的priority比D要小,為了保證堆的性質(zhì),需要將B和D進(jìn)行互換。由于直接互換會破壞BST的性質(zhì),所以我們采取旋轉(zhuǎn)的操作。

旋轉(zhuǎn)之后我們發(fā)現(xiàn)B和D互換了位置,并且旋轉(zhuǎn)之后的A和E的priority都是大于D的,所以旋轉(zhuǎn)之后我們整棵樹依然維持了性質(zhì)。

右旋的情況也是一樣的,其實(shí)我們觀察一下會發(fā)現(xiàn),要交換左孩子和父親需要右旋,如果是要交換右孩子和父親,則需要左旋。

整個(gè)插入的操作其實(shí)就是基礎(chǔ)的BST插入過程,加上旋轉(zhuǎn)的判斷。

  1. def _insert(self, node, father, new_node, left_or_right='left'): 
  2.       ""
  3.       Inside implement of insert node. 
  4.       Implement in recursion. 
  5.       Since the parameter passed in Python is reference, so when we add node, we need to assign the node to its father, otherwise the reference will lose outside the function
  6.       When we add node, we need to compare its key with its father's key to make sure it's the lchild or rchild of its father. 
  7.       ""
  8.       if node is None: 
  9.           if new_node.key < father.key
  10.               father.lchild = new_node 
  11.           else
  12.               father.rchild = new_node 
  13.           new_node.father = father 
  14.           return 
  15.       if new_node.key < node.key
  16.           self._insert(node.lchild, node, new_node, 'left'
  17.           # maintain 
  18.           if node.lchild.priority < node.priority: 
  19.               self.rotate_right(node, father, left_or_right) 
  20.       else
  21.           self._insert(node.rchild, node, new_node, 'right'
  22.           # maintain 
  23.           if node.rchild.priority < node.priority: 
  24.               self.rotate_left(node, father, left_or_right) 

前面的邏輯就是BST的插入,也就是和當(dāng)前節(jié)點(diǎn)比大小,決定插入在左邊還是右邊。注意一下,這里我們在插入完成之后,增加了maintain的邏輯,其實(shí)也就是比較一下,剛剛進(jìn)行的插入是否破壞了堆的性質(zhì)。可能有些同學(xué)要問我了,這里為什么只maintain了一次?有可能插入的priority非常小,需要一直旋轉(zhuǎn)到樹根不是嗎?

的確如此,但是不要忘了,我們這里的maintain邏輯并非只調(diào)用一次。隨著整個(gè)遞歸的回溯,在樹上的每一層它其實(shí)都會執(zhí)行一次maintain邏輯。所以是可以保證從插入的地方一直維護(hù)到樹根的。

查詢

查詢很簡單,不用多說,就是BST的查詢操作,沒有任何變化。

  1. def _query(self, node, key, backup=None): 
  2.        if node is None: 
  3.            return backup 
  4.        if key < node.key
  5.            return self._query(node.lchild, key, backup) 
  6.        elif key > node.key
  7.            return self._query(node.rchild, key, backup) 
  8.        return node 
  9.  
  10.    def query(self, key, backup=None): 
  11.        ""
  12.        Return the result of query a specific node, if not exists return None 
  13.        ""
  14.        return self._query(self.root, key, backup) 

刪除

刪除的操作稍微麻煩了一些,由于涉及到了優(yōu)先級的維護(hù),不過邏輯也不難理解,只需要牢記需要保證堆的性質(zhì)即可。

首先,有兩種情況非常簡單,一種是要?jiǎng)h除的節(jié)點(diǎn)是葉子節(jié)點(diǎn),這個(gè)都很容易想明白,刪除它不會影響任何其他節(jié)點(diǎn),直接刪除即可。第二種情況是鏈節(jié)點(diǎn),也就是說它只有一個(gè)孩子,那么刪除它也不會引起變化,只需要將它的孩子過繼給它的父親,整個(gè)堆和BST的性質(zhì)也不會受到影響。

對于這兩種情況之外,我們就沒辦法直接刪除了,因?yàn)楸厝粫绊懚训男再|(zhì)。這里有一個(gè)很巧妙的做法,就是可以先將要?jiǎng)h除的節(jié)點(diǎn)旋轉(zhuǎn),將它旋轉(zhuǎn)成葉子節(jié)點(diǎn)或者是鏈節(jié)點(diǎn),再進(jìn)行刪除。

在這個(gè)過程當(dāng)中,我們需要比較一下它兩個(gè)孩子的優(yōu)先級,確保堆的性質(zhì)不會受到破壞。

  1. def _delete_node(self, node, father, key, child='left'): 
  2.         ""
  3.         Implement function of delete node. 
  4.         Defined as a private function that only can be called inside. 
  5.         ""
  6.         if node is None: 
  7.             return 
  8.         if key < node.key
  9.             self._delete_node(node.lchild, node, key
  10.         elif key > node.key
  11.             self._delete_node(node.rchild, node, key'right'
  12.         else
  13.             # 如果是鏈節(jié)點(diǎn),葉子節(jié)點(diǎn)的情況也包括了 
  14.             if node.lchild is None: 
  15.                 self.reset_child(father, node.rchild, child) 
  16.             elif node.rchild is None: 
  17.                 self.reset_child(father, node.lchild, child) 
  18.             else
  19.                 # 根據(jù)兩個(gè)孩子的priority決定是左旋還是右旋 
  20.                 if node.lchild.priority < node.rchild.priority: 
  21.                     node = self.rotate_right(node, father, child) 
  22.                     self._delete_node(node.rchild, node, key'right'
  23.                 else
  24.                     node = self.rotate_left(node, father, child) 
  25.                     self._delete_node(node.lchild, node, key
  26.  
  27.                      
  28.     def delete(self, key): 
  29.         ""
  30.         Interface of delete method face outside. 
  31.         ""
  32.         self._delete_node(self.root, None, key'left'

修改

修改的操作也非常簡單,我們直接查找到對應(yīng)的節(jié)點(diǎn),修改它的value即可。

旋轉(zhuǎn)

我們也貼一下旋轉(zhuǎn)操作的代碼,其實(shí)這里的邏輯和之前SBT當(dāng)中介紹的旋轉(zhuǎn)操作是一樣的,代碼也基本相同:

  1. def reset_child(self, node, child, left_or_right='left'): 
  2.        ""
  3.        Reset the child of father, since in Python all the instances passed by reference, so we need to set the node as a child of its father node. 
  4.        ""
  5.        if node is None: 
  6.            self.root = child 
  7.            self.root.father = None 
  8.            return 
  9.        if left_or_right == 'left'
  10.            node.lchild = child 
  11.        else
  12.            node.rchild = child 
  13.        if child is not None: 
  14.            child.father = node 
  15.  
  16.  
  17. def rotate_left(self, node, father, left_or_right): 
  18.        ""
  19.        Left rotate operation of Treap. 
  20.        Example:  
  21.  
  22.                D 
  23.              /   \ 
  24.             A      B 
  25.                   / \ 
  26.                  E   C 
  27.  
  28.        After rotate: 
  29.  
  30.                B 
  31.               / \ 
  32.              D   C 
  33.             / \ 
  34.            A   E  
  35.        ""
  36.        rchild = node.rchild 
  37.        node.rchild = rchild.lchild 
  38.        if rchild.lchild is not None: 
  39.            rchild.lchild.father = node 
  40.        rchild.lchild = node 
  41.        node.father = rchild 
  42.        self.reset_child(father, rchild, left_or_right) 
  43.        return rchild 
  44.  
  45.    def rotate_right(self, node, father, left_or_right): 
  46.        ""
  47.        Right rotate operation of Treap. 
  48.        Example:  
  49.  
  50.                D 
  51.              /   \ 
  52.             A     B 
  53.            / \ 
  54.           E   C 
  55.  
  56.        After rotate: 
  57.  
  58.                A 
  59.               / \ 
  60.              E   D 
  61.                 / \ 
  62.                C   B  
  63.        ""
  64.        lchild = node.lchild 
  65.        node.lchild = lchild.rchild 
  66.        if lchild.rchild is not None: 
  67.            lchild.rchild.father = node 
  68.        lchild.rchild = node 
  69.        node.father = lchild 
  70.        self.reset_child(father, lchild, left_or_right) 
  71.        return lchild 

這里唯一要注意的是,由于Python當(dāng)中存儲的都是引用,所以我們在旋轉(zhuǎn)操作之后必須要重新覆蓋一下父節(jié)點(diǎn)當(dāng)中當(dāng)中的值才會生效。負(fù)責(zé)我們修改了node的引用,但是father當(dāng)中還是存儲的舊的地址,一樣沒有生效。

后記

基本上到這里整個(gè)Treap的原理就介紹完了,當(dāng)然除了我們剛才介紹的基本操作之外,Treap還有一些其他的操作。比如可以split成兩個(gè)Treap,也可以由兩個(gè)Treap合并成一個(gè)。還可以查找第K大的元素,等等。這些額外的操作,我用得也不多,就不多介紹了,大家感興趣可以去了解一下。

Treap這個(gè)數(shù)據(jù)結(jié)構(gòu)在實(shí)際當(dāng)中幾乎沒有用到過,一般還是以競賽場景為主,我們學(xué)習(xí)它主要就是為了提升和鍛煉我們的數(shù)據(jù)結(jié)構(gòu)能力以及代碼實(shí)現(xiàn)能力。Treap它的最大優(yōu)點(diǎn)就是實(shí)現(xiàn)簡單,沒有太多復(fù)雜的操作,但是我們前面也說了,它是通過隨機(jī)的priority來控制樹的平衡的,那么它顯然無法做到完美平衡,只能做到不落入最壞的情況,但是無法保證可以進(jìn)入最好的情況。不過對于二叉樹來說,樹深的一點(diǎn)差距相差并不大。所以Treap的性能倒也沒有那么差勁,屬于一個(gè)性價(jià)比非常高的數(shù)據(jù)結(jié)構(gòu)。

最后,還是老規(guī)矩,我把完整的代碼放在了paste當(dāng)中,大家感興趣可以點(diǎn)擊閱讀原文查看,代碼里都有詳細(xì)的注釋,大家應(yīng)該都能看明白。

本文轉(zhuǎn)載自微信公眾號「 TechFlow」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系 TechFlow公眾號。

 

責(zé)任編輯:武曉燕 來源: TechFlow
相關(guān)推薦

2020-04-27 07:05:58

二叉樹左子樹右子樹

2021-04-19 07:47:42

數(shù)據(jù)結(jié)構(gòu)二叉樹Tree

2021-04-20 08:37:14

數(shù)據(jù)結(jié)構(gòu)二叉樹

2021-04-28 20:12:27

數(shù)據(jù)結(jié)構(gòu)創(chuàng)建

2021-08-27 11:36:44

二叉樹回溯節(jié)點(diǎn)

2022-10-26 23:58:02

二叉樹數(shù)組算法

2020-09-23 18:25:40

算法二叉樹多叉樹

2019-08-22 09:22:44

數(shù)據(jù)結(jié)構(gòu)二叉搜索樹

2021-09-29 10:19:00

算法平衡二叉樹

2021-08-31 11:35:24

二叉搜索樹迭代法公共祖先

2021-03-17 08:19:22

二叉樹LeetCode

2013-07-15 16:35:55

二叉樹迭代器

2021-09-15 07:56:32

二叉樹層次遍歷

2021-10-12 09:25:11

二叉樹樹形結(jié)構(gòu)

2018-03-15 08:31:57

二叉樹存儲結(jié)構(gòu)

2021-01-13 10:03:36

二叉樹層序遍歷層次遍歷

2022-12-26 00:51:33

雙向鏈表二叉搜索樹

2022-01-11 10:01:25

二叉搜索樹數(shù)量

2021-09-03 08:58:00

二叉搜索樹節(jié)點(diǎn)

2021-09-02 11:31:28

二叉搜索樹迭代法公共祖先
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號