树堆

树堆(Treap)

    在某些极端情况下,二叉查找树有可能退化为链表,所以前辈们想尽了各种优化策略,这就涉及到二叉树的自平衡。二叉树的自平衡方式有多种,如红黑树、AVL等,包括今天要讲的树堆(Treap)

    一、树堆的特性和原理

    树堆是一种随机化平衡二叉搜索树,结合了二叉堆和二叉查找树的特性(Treap=Tree+Heap)。

    实现原理很简单,在树中维护一个”优先级“,”优先级“采用随机数的方法生成,但是”优先级“必须满足二叉堆的性质,当然是“最大堆”或者“最小堆”都可以的。如下图:

   

   从上图可以看出:

   1)节点中的值满足二叉查找树特性 

   2)节点中的优先级满足最大堆特性

   下面归纳下树堆的一些特性:

   1)键值特性

   树堆的每个节点包含一个键值和一个随机的优先级值。键值用于节点的比较和排序,优先级值用于平衡树的结构

   2)二叉查找树性质

   树堆维护了二叉搜索树的性质,即对于任意节点,它的左子树中的所有节点的键值小于该节点的键值,右子树中的所有节点的键值大于该节点的键值。

   3)堆性质

   树堆也维护了堆的性质,即对于任意节点,它的优先级值大于其子节点的优先级值。

   4)随机化平衡

   树堆通过随机生成节点的优先级值来实现平衡。节点的优先级值和键值无关,它的随机性保证了树的平衡性质。

   5)插入操作

   当插入一个新节点时,树堆首先按照二叉查找树性质找到插入的位置,然后根据节点的优先级值,通过旋转操作保持堆性质和查找树性质。
   6)
删除操作

   删除一个节点时,树堆首先根据二叉查找树性质找到要删除的节点,然后通过旋转操作将其删除,并保持堆性质和查找树性质。

   由于采用最大和最小堆的效果一样,本文采用最大堆进行讨论。

   二、 插入节点

   当插入一个新节点时,树堆首先按照二叉查找树性质找到插入的位置,然后根据节点的优先级值,判断是否需要通过旋转操作持堆性质和查找树性质。

   说明:我们知道各个节点的优先级是采用随机数的方法,那么就存在一个问题,当我们按照二叉查找树规则插入一个节点后,优先级有可能不满足最大堆定义。显然,此时跟维护堆一样,如果当前节点的优先级比根大就旋转,如果当前节点是根的左孩子就右旋,如果当前节点是根的右孩子就左旋。如下图所示:

   上图中右旋只展示了一次,而左旋则展示了三次递归的过程。其实理解了AVL的旋转,就能很容易理解这里的旋转了。旋转代码与AVL的完全一样。

    三、 删除节点   

    跟普通的二叉查找树一样,删除结点存在三种情况。

    1.  叶子结点(待删除的节点没有子节点)

    跟普通查找树一样,直接删除本节点即可。

    2.  单孩子结点(待删除的节点有一个孩子)

    跟普通查找树一样操作:直接让其孩子节点取代被删除的节点,孩子节点以下的节点关系无须变动。

    3.  满孩子结点(待删除的节点有两个孩子)

    在treap中移除满孩子结点有两种方式。

    1) 第一种

    普通二叉查找树一样,找到左子树的最大节点或者右子树的最小节点,然后copy元素的值,但不拷贝其优先级(以免破坏堆属性),如下图所示:

    2) 第二种

    第二种是采用旋转的方法:因为Treap树满足堆性质,所以只需要把要删除的节点旋转到叶子节点上,然后直接删除就可以了。具体的方法就是每次找到优先级最大的儿子,向与其相反的方向旋转,直到那个节点被旋转到了叶节点,然后直接删除。删除最多进行O(h)次旋转,期望复杂度是 O(logN)。示意图如下所示:

  四、代码实现

  下面用代码简单实现下:

  树堆的定义:

1 public class Node {
2     int key, priority;
3     Node left, right;
4     Node(int key) {
5         this.key = key;
6         this.priority = new Random().nextInt();
7     }
8 }

   树堆的操作:

 1 public class Treap {
 2     Node root;
 3 
 4     /**
 5      * 左旋
 6      * @param node
 7      * @return
 8      */
 9     private Node leftRotate(Node node) {
10         Node newRoot = node.right;
11         node.right = newRoot.left;
12         newRoot.left = node;
13         return newRoot;
14     }
15 
16 
17     /**
18      * 右旋
19      * @param node
20      * @return
21      */
22     private Node rightRotate(Node node) {
23         Node newRoot = node.left;
24         node.left = newRoot.right;
25         newRoot.right = node;
26         return newRoot;
27     }
28 
29     public Node insert(Node root, int key) {
30         if (root == null) {
31             return new Node(key);
32         }
33         if (key <= root.key) {
34             root.left = insert(root.left, key);
35             if (root.left.priority > root.priority) {
36                 root = rightRotate(root);
37             }
38         } else {
39             root.right = insert(root.right, key);
40             if (root.right.priority > root.priority) {
41                 root = leftRotate(root);
42             }
43         }
44         return root;
45     }
46 
47     public void insert(int key) {
48         root = insert(root, key);
49     }
50 
51     /**
52      * 中序遍历
53      * @param node
54      */
55     private void inOrderTraversal(Node node) {
56         if (node != null) {
57             inOrderTraversal(node.left);
58             System.out.print(node.key + " ");
59             inOrderTraversal(node.right);
60         }
61     }
62 
63     public void inOrderTraversal() {
64         inOrderTraversal(root);
65     }
66 
67     public static void main(String[] args) {
68         Treap treap = new Treap();
69         int[] testData = {5, 3, 7, 2, 4, 6, 8};
70         for (int key : testData) {
71             treap.insert(key);
72         }
73         treap.inOrderTraversal();
74     }
75 }

 

  五、 树堆的优缺点分析

   1.  优点

   1)简单而高效的实现

   树堆的实现相对较简单,只需要维护两个关键属性:BST的有序性和堆的优先级。这使得它易于实现和理解。

   2)动态操作高效

   树堆在频繁的插入和删除操作方面表现出色,因为它们利用了随机的优先级来维护树的平衡,通常不需要进行复杂的平衡调整。

   3)随机性能好

   在随机输入的情况下,树堆的性能通常很好,因为优先级的随机性可以保持树的平衡。

   2. 缺点

   1)不适用于特定的有序数据

   当数据的顺序是特定的,而不是随机的时候,树堆的性能可能不如其他平衡树结构。

   2)空间开销较大

   每个节点需要存储两个额外的信息:优先级和子树的大小,这可能会增加内存开销。

   六、树堆与红黑树的比较

   1.  时间复杂度

   红黑树和树堆在查找、插入和删除操作上的平均性能是相似的,都是O(logN)。这是因为它们都是自平衡的二叉搜索树,通过调整树的结构来维护平衡性,以确保这些操作的时间复杂度保持在对数级别。

   2.  空间复杂度

  树堆的空间复杂度通常较低,适用于需要高效的优先队列操作的场景。而红黑树的空间复杂度较高,但由于其自平衡性质,它在搜索、插入和删除等操作上具有良好的性能。

   3.  实现复杂度

   树堆的实现复杂度相对较低,特别是在插入和删除操作上。红黑树相对复杂一些,具有更好的平衡性,可以保证树的高度较小,从而在查找操作上表现更好。

   总体来说,如果需要频繁的插入和删除操作,并且对查找操作的性能要求不是特别高,那么树堆可能是一个更好的选择,因为它们在动态插入和删除方面更有效率。如果对查找操作的性能要求较高,希望确保树的平衡性,或者需要一种稳定的数据结构,那么红黑树可能更适合,因为它们在保持平衡和查找操作上具有更好的性能保证。

   参考链接:

   https://blog.imallen.wang/2015/11/15/2016-07-16-treapshu-ji-javashi-xian/

   https://zhuanlan.zhihu.com/p/653926922

 

posted @ 2024-04-21 11:43  欢乐豆123  阅读(3)  评论(0编辑  收藏  举报