zoukankan      html  css  js  c++  java
  • 左偏树的正确性和复杂度分析

    摘录——实现分析

    原文http://m.blog.csdn.net/article/details?id=7247454

    左偏树,也可以称之为左式堆。称其为树,是因为其存储结构通常采用二叉树,所以可以认为是一种特殊的二叉树。称其为堆,是因为在逻辑结构上,它属于可合并堆的一种。其实数据结构中最欣欣向荣的两个分支就是:平衡树 和可合并堆。高级树结构的核心都是围绕如何使树到达平衡而展开,高级堆结构的核心就是如何有效地进行合并。

    首先看左偏树的性质:

    1. 【堆性质】:任意节点的关键字大于等于其孩子节点的关键字

    2. 【左偏性质】:定义到最近的孩子的距离为节点距离dist,那么任意节点的左孩子的距离大于右孩子的距离。

    堆性质是为了让最小的结点始终在根的位置,这是所有堆都有的性质。

    而左偏性质,则是为了让树状存储的堆,树的深度不能过大,且利于合并。

    那么这个性质是怎么完成这两个功能的呢?左偏性质使树的左侧的深度始终大于等于右侧的深度,这一点从名字上就能体会到,也可以画几棵左偏的树试试。而左偏树在实现插入操作时总是从右侧插入,也就是总是让短的一侧生长,如果右侧长于了左侧,那么把左右侧交换一下,继续从短的一侧生长。其实如果不考虑具体的细节,那么这样的直观理解可以看到左偏树的一些本质内涵,一颗树有两个分支,每次要生长的时候,总是让短的一侧先生长,那么这棵树,最后是不是就能够比较对称呢?自然常识和严密的技术逻辑有时候是一致的。

    LTNode *merge(LTNode * &A, LTNode * &B) {
      if (A == NULL || B == NULL)
        return A == NULL ? B : A;
    
      if (A->data > B->data) {      /* 确保B->data >= A->data */
        swap < LTNode * >(A, B);
      }
    
      A->rchild = merge(A->rchild, B); /* 新来个左偏树始终合并到右侧 */
    
      /* 由于新结点合并到右侧,右侧结点现在一定存在了,但左侧不一定 */
      if (A->lchild == NULL ||      /* 左侧为空,一定小于右侧 */
          A->rchild->dist > A->lchild->dist) /* 右侧大于了左侧 */
        swap < LTNode * >(A->lchild, A->rchild);
    
      if (A->rchild == NULL) {
        A->dist = 0;
      } /* 右子树为空 */
      else {
        A->dist = A->rchild->dist + 1;
      }
    
      return A;
    }

    正确性分析

    前人之述备矣。然则左偏堆为何正确?下面我们证明左偏堆合并操作的正确性。考虑下面三种情况并分别证明:(下面所有分析针对小根堆)

    • 左偏堆H1合并到H2,|H1| = 1(只有一个元素),H1.root < H2.root。这种情况只把H2接在H1的左孩子上,显然可以维持左偏性和堆性质。(情况1)
    • 左偏堆H1合并到H2,|H1| = 1(只有一个元素),H1.root > H2.root,证明合并的堆符合左偏性和堆性质。这种情况又可以分两类:
      1. H1.root < H2.right;这又十分简单。根据递归操作H2->right = merge(H2->right, H1.root)可以直接归纳为情况(1)。(情况2)
      2. H1.root > H2.right;根据递归操作,要么归纳为情况1或2,要么归纳为自身,且规模更小,最终必然归纳为1或2。(情况3)
    • 左偏堆H1合并到H2,H1.root < H2.root,证明合并的堆符合左偏性和堆性质。这是要证明的最终情况。根据递归操作,要么归纳为前面的情况,要么归纳为自己,且规模更小。因此正确。

    这个证明并不是十分严谨,但是能给读者一个对于左偏堆的原理的更好理解。


    复杂度分析

    显然,合并操作与左孩子无关。那么对于总孩子数相同的左孩子越多,效率必然越高;左孩子越少,效率必然越低。那么不妨假设左孩子数的两个极端:

    • 每个左孩子比右孩子恰好多1,两颗树规模为n,m,极端下,两颗树交替成为操作中的A数,有T(n,m) = T(m,n/2) + O(1) = O(lgn + lgm)

    • 全部为左孩子,为基本情况,复杂度为O(1)。

    不难得出所有操作的复杂度:

    情况 插入 删除 合并 取最值
    最好 O(1) O(1) O(1) O(1)
    最坏 O(lgn) O(lgn) O(lgn) O(1)

    但不幸的是,左偏树的实际效率并不理想。如果插入/合并较多,pbds的thin_heap和pairing_heap都更有优势;没有合并的话,甚至std::priority_queue都能碾压左偏树。在现行OI赛制之下,左偏树的实际作用应该并不大。不过它的优雅和简单已经足以让人折服。


    手制的模板

    #include <iostream>
    using std::swap;
    
    template <typename T>
    struct LtHeapNode {
        typedef LtHeapNode<T> Node;
        T data;
        size_t dist;
        Node *left, *right;
        LtHeapNode() : dist(0), left(0), right(0) {}
        LtHeapNode(const T& _data) : dist(0), left(0), right(0)
        {
            data = _data;
        }
    };
    
    template <typename T, typename Comp>
    class LtHeap {
    
    private:
        typedef LtHeapNode<T> Node;
        typedef LtHeap<T, Comp> Heap;
        Node *root;
        Comp cmp;
    
    public:
        LtHeap():root(0) {}
        void clear(Node* &n) 
        {
            if(n) {
                clear(n->left);
                clear(n->right);
                n = 0;
            }
        }
        ~LtHeap()
        {
            clear(root);
        }
        bool empty() 
        {
            return !root;
        }
        Node* merge(Node* &A, Node* &B)
        {
            if(!A||!B) 
                return A?A:B;
            if(cmp(A->data,B->data)) 
                swap(A,B);
            A->right = merge(A->right, B);
            if(!A->left || A->left->dist < A->right->dist) 
                swap(A->left, A->right);
            A->dist = !A->right?0:A->right->dist+1;
            return A;
        }
        void push(const T& _dat) 
        {
            Node* n = new Node(_dat);
            root = merge(root, n);
        }
        void merge(Heap& _another)
        {
            root = merge(root, _another.root);
            _another.root = 0;
        }
        void pop() 
        {
            root = merge(root->left, root->right);
        }
        T top()
        {
            return root->data;
        }
    };
    
  • 相关阅读:
    诡异的楼梯 HDU1180
    selenium的那些命令:
    权限管理:
    图片裁剪:
    2-15 showapiRequest解决图片验证码识别.py
    2-14 使用pytesseract识别图片中得问题.py
    使用C#开发ActiveX控件 11
    Mybatis+mysql动态分页查询数据案例——测试类HouseDaoMybatisImplTest)
    Mybatis+mysql动态分页查询数据案例——测试类HouseDaoMybatisImplTest)
    Mybatis+mysql动态分页查询数据案例——测试类HouseDaoMybatisImplTest)
  • 原文地址:https://www.cnblogs.com/ljt12138/p/6684386.html
Copyright © 2011-2022 走看看