📜  左派树/左派堆

📅  最后修改于: 2021-10-28 01:48:12             🧑  作者: Mango

左树或左堆是使用二叉堆的变体实现的优先级队列。每个节点都有一个s 值(或等级或距离) ,它是到最近叶子的距离。与二叉堆(始终是完整的二叉树)相反,左树可能非常不平衡。

下面是 Leftist Tree / Heap 的时间复杂度。

Function       Complexity              Comparison
1) Get Min:       O(1)      [same as both Binary and Binomial]
2) Delete Min:    O(Log n)  [same as both Binary and Binomial]
3) Insert:        O(Log n)  [O(Log n) in Binary and O(1) in 
                            Binomial and O(Log n) for worst case]                                                                  
4) Merge:         O(Log n)  [O(Log n) in Binomial]

左树是具有以下特性的二叉树:

  1. 正常最小堆属性: key(i) >= key(parent(i))
  2. 左侧较重: dist(right(i)) <= dist(left(i))。这里,dist(i) 是扩展二叉树表示中从节点 i 到叶节点的最短路径上的边数(在这种表示中,空子节点被视为外部或叶节点)。到后代外部节点的最短路径是通过右孩子。每个子树也是左树并且 dist( i ) = 1 + dist( right( i ) )。

示例:下面的左树显示了使用上述过程为每个节点计算的距离。最右边的节点的等级为 0,因为该节点的右子树为空,并且其父节点的距离为 1,dist( i ) = 1 + dist( right( i ))。每个节点都遵循相同的方法,并计算它们的 s 值(或排名)。

lt1

从上面的第二个属性,我们可以得出两个结论:

  1. 从根到最右边叶子的路径是从根到叶子的最短路径。
  2. 如果到最右边叶子的路径有 x 个节点,那么左边的堆至少有 2 x – 1 个节点。这意味着对于具有 n 个节点的左侧堆,到最右侧叶子的路径长度为 O(log n)。

操作:

  1. 主要操作是merge()。
  2. deleteMin() (或 extractMin() 可以通过删除根并为左右子树调用 merge() 来完成。
  3. insert() 可以通过创建一个带有单个键(要插入的键)的左树并为给定的树和带有单个节点的树调用 merge() 来完成。

合并背后的想法:
由于右子树较小,因此想法是将一棵树的右子树与其他树合并。下面是抽象步骤。

  1. 将具有较小值的根作为新根。
  2. 将其左子树挂在左侧。
  3. 递归合并它的右子树和另一棵树。
  4. 从递归返回之前:
    – 更新合并根的 dist()。
    – 如果需要,交换根正下方的左右子树,以保持合并的左属性
    结果

来源:http://courses.cs.washington.edu/courses/cse326/08sp/lectures/05-leftist-heaps.pdf

合并的详细步骤:

  1. 比较两个堆的根。
  2. 将较小的键推入空堆栈,然后移动到较小键的右子节点。
  3. 递归地比较两个键并继续将较小的键推入堆栈并移动到其右子键。
  4. 重复直到到达一个空节点。
  5. 取最后一个处理的节点,使其成为栈顶节点的右子节点,如果违反左堆的属性,则将其转换为左堆。
  6. 递归地继续从堆栈中弹出元素并使它们成为新堆栈顶部的右孩子。

例子:
考虑下面给出的两个左派堆:
2

将它们合并成一个左派堆

3

节点 7 处的子树违反了左堆的性质,因此我们将其与左孩子交换并保留左堆的性质。
4

转换为左派堆。重复这个过程

5
6

该算法的最坏情况时间复杂度在最坏情况下为 O(log n),其中 n 是左堆中的节点数。

另一个合并两个leftist heap的例子:
lt9

左派树/左派堆的实现:

//C++ program for leftist heap / leftist tree
#include 
using namespace std;
  
// Node Class Declaration
class LeftistNode
{
public:
    int element;
    LeftistNode *left;
    LeftistNode *right;
    int dist;
    LeftistNode(int & element, LeftistNode *lt = NULL,
                LeftistNode *rt = NULL, int np = 0)
    {
        this->element = element;
        right = rt;
        left = lt,
        dist = np;
    }
};
  
//Class Declaration
class LeftistHeap
{
public:
    LeftistHeap();
    LeftistHeap(LeftistHeap &rhs);
    ~LeftistHeap();
    bool isEmpty();
    bool isFull();
    int &findMin();
    void Insert(int &x);
    void deleteMin();
    void deleteMin(int &minItem);
    void makeEmpty();
    void Merge(LeftistHeap &rhs);
    LeftistHeap & operator =(LeftistHeap &rhs);
private:
    LeftistNode *root;
    LeftistNode *Merge(LeftistNode *h1,
                       LeftistNode *h2);
    LeftistNode *Merge1(LeftistNode *h1,
                        LeftistNode *h2);
    void swapChildren(LeftistNode * t);
    void reclaimMemory(LeftistNode * t);
    LeftistNode *clone(LeftistNode *t);
};
  
// Construct the leftist heap
LeftistHeap::LeftistHeap()
{
    root = NULL;
}
  
// Copy constructor.
LeftistHeap::LeftistHeap(LeftistHeap &rhs)
{
    root = NULL;
    *this = rhs;
}
  
// Destruct the leftist heap
LeftistHeap::~LeftistHeap()
{
    makeEmpty( );
}
  
/* Merge rhs into the priority queue.
rhs becomes empty. rhs must be different
from this.*/
void LeftistHeap::Merge(LeftistHeap &rhs)
{
    if (this == &rhs)
        return;
    root = Merge(root, rhs.root);
    rhs.root = NULL;
}
  
/* Internal method to merge two roots.
 Deals with deviant cases and calls recursive Merge1.*/
LeftistNode *LeftistHeap::Merge(LeftistNode * h1,
                                LeftistNode * h2)
{
    if (h1 == NULL)
        return h2;
    if (h2 == NULL)
        return h1;
    if (h1->element < h2->element)
        return Merge1(h1, h2);
    else
        return Merge1(h2, h1);
}
  
/* Internal method to merge two roots.
 Assumes trees are not empty, and h1's root contains
  smallest item.*/
LeftistNode *LeftistHeap::Merge1(LeftistNode * h1,
                                 LeftistNode * h2)
{
    if (h1->left == NULL)
        h1->left = h2;
    else
    {
        h1->right = Merge(h1->right, h2);
        if (h1->left->dist < h1->right->dist)
            swapChildren(h1);
        h1->dist = h1->right->dist + 1;
    }
    return h1;
}
  
// Swaps t's two children.
void LeftistHeap::swapChildren(LeftistNode * t)
{
    LeftistNode *tmp = t->left;
    t->left = t->right;
    t->right = tmp;
}
  
/* Insert item x into the priority queue, maintaining
  heap order.*/
void LeftistHeap::Insert(int &x)
{
    root = Merge(new LeftistNode(x), root);
}
  
/* Find the smallest item in the priority queue.
Return the smallest item, or throw Underflow if empty.*/
int &LeftistHeap::findMin()
{
    return root->element;
}
  
/* Remove the smallest item from the priority queue.
Throws Underflow if empty.*/
void LeftistHeap::deleteMin()
{
    LeftistNode *oldRoot = root;
    root = Merge(root->left, root->right);
    delete oldRoot;
}
  
/* Remove the smallest item from the priority queue.
Pass back the smallest item, or throw Underflow if empty.*/
void LeftistHeap::deleteMin(int &minItem)
{
    if (isEmpty())
    {
        cout<<"Heap is Empty"<left);
        reclaimMemory(t->right);
        delete t;
    }
}
  
// Internal method to clone subtree.
LeftistNode *LeftistHeap::clone(LeftistNode * t)
{
    if (t == NULL)
        return NULL;
    else
        return new LeftistNode(t->element, clone(t->left),
                               clone(t->right), t->dist);
}
  
//Driver program
int main()
{
    LeftistHeap h;
    LeftistHeap h1;
    LeftistHeap h2;
    int x;
    int arr[]= {1, 5, 7, 10, 15};
    int arr1[]= {22, 75};
  
    h.Insert(arr[0]);
    h.Insert(arr[1]);
    h.Insert(arr[2]);
    h.Insert(arr[3]);
    h.Insert(arr[4]);
    h1.Insert(arr1[0]);
    h1.Insert(arr1[1]);
  
    h.deleteMin(x);
    cout<< x <

输出:

1
22
5

参考:
维基百科-左派树
CSC378:左派树

如果您希望与专家一起参加现场课程,请参阅DSA 现场工作专业课程学生竞争性编程现场课程。