算法导论第十九章 斐波那契堆

  《算法导论》第二版中在讨论斐波那契堆之前还讨论了二项堆,但是第三版中已经把这块的内容放到思考题中,究极原因我想大概是二项堆只是个引子,目的是为了引出斐波那契堆,便于理解,而且许多经典的算法实现都是基于斐波那契堆,譬如计算最小生成树问题和寻找单源最短路径问题等,此时再把二项堆单独作为一章来讲显然没有必要。类似的堆结构还有很多,如左倾堆,斜堆,二项堆等,下次我打算开一篇博客来记录下它们的异同点。

一、摊还分析(第十七章)

  这些高级的数据结构的性能分析一般是基于一个技术——摊还分析,可以理解成一种时间复杂度的分析方法。它的提出是基于这样一个事实:并不能总以最坏情况来衡量一个算法的性能,因为最坏情况并不总会发生,而且在绝大多数应用中最坏情况出现的次数是极小的。从字面意思上理解这种分析方法,就是将一系列操作所需要的时间平均分摊到每一个操作上,使得每一个操作,在最坏情况下,具有平均性能。这样的分析方法看上去有点像平均情况分析,但其实有着本质的不同,其一,摊还分析是通过求一系列操作的平均时间来评价某一操作的性能,即使其中某一操作的代价很高,都能保证它们具有较低的平均性能,而平均情况分析则是单独针对某一操作来评价;其二,摊还分析不涉及概率计算,只是对一个整体求平均值,而平均情况则不然,代价高的操作自然占总开销的概率就大。

  要理解的是,摊还分析强调的是一个“操作序列”,一般低级的数据结构操作都用不到这种分析方法,而高级的数据结构,说白了,就是结合多种低级数据结构形成的,因此在对这些数据结构进行操作时,涉及到一个序列的操作,而其中可能存在每个操作的时间复杂度都不一样,有的很高,有的又几乎可以忽略,所以采用这种摊还分析方法更加适合分析这些数据结构的操作性能。就以本文将要说的斐波那契堆来说,这种堆结构是由“堆排序”中所用到的最小堆组成,至于为什么叫这个名字,是由斐波那契堆上每个节点的度所决定的——其具有斐波那契数列的性质(具体可以看书本的推导)。另外,再来看一个书上提出的例子——栈操作:

1 Multi-Pop(S, k)
2     while not Stack-Empty(S) and k > 0
3         POP(S)
4         k = k - 1

其中,POP()操作的代价是1,假设栈的大小最大为n,一般的时间复杂度的分析方法是:Multi-Pop()操作在最坏情况下的代价为O(n),那么假设有n个这样的操作,则最坏情况下时间复杂度就为O(n^2),虽然这种分析方法是正确的,但通过单独分析每个操作的最坏情况代价得到的操作序列的最坏情况时间O(n^2)并不是一个确界。如果使用摊还分析方法(方法有三种,具体可以查看书本,此处不做详述),则可以得到更好的上界。分析如下:从整体上来看,n个Multi-Pop()序列的操作,最多进行n次,因为POP()的次数最多与栈的大小相等,有多少个元素就POP多少次,即使有n次的操作也是如此。所以,n次序列的操作,代价至多为O(n),任意一个操作的平均时间就为O(n)/n=O(1),这个就是Multi-Pop()的摊还代价。

二、斐波那契堆

1、斐波那契堆由一组最小堆序有根树组成,其中每棵树必须满足最小堆的性质;

2、每个最小堆用一个双循环链表连接起来,称为根链表;

3、斐波那契堆是一种合并堆,除了支持可合并堆的五种操作之外(Make-Heap, Insert, Minimum, Extract_min, Union),还支持Decrease_Key和Delete两种操作;

4、斐波那契堆是为了改进普通二叉堆Union操作( O(n) )而提出的一种可合并堆,除了Union操作,普通二叉堆都能在最坏情况时间为O(lgn)下完成,但斐波那契堆所有操作均能在常数摊还时间下完成,除了Extract_min和Delete操作。

5、斐波那契堆在优化加速图算法中有很大的用途。比如用于解决诸如最小生成树、寻找单源最短路径等问题的快速算法都要用到斐波那契堆。

 6、斐波那契堆具有以下属性:

1)根节点的孩子节点也组成一个双循环链表,称为孩子链表;

2)每个节点有指向父亲节点,指向某一个孩子节点,指向左兄弟节点和有兄弟节点的指针;

3)H.min指向根链表中的最小节点;

4)H.n表示节点数目

5)每个节点x有两个属性: x.degree表示节点的度; x.mark则用来标识一个非根结点是否已经失去 了一个孩子(这样的结点,不能在夺其子女了,可能要进行一些其它的特别操作),该标志主要用于删除操作。下面看一个斐波那契堆的内存结构图(引自:斐波那契堆之图文解析

 1 template<typename Data, typename Key> 
 2 class FibHeapNode
 3 {
 4 public:
 5     template<typename D, typename K> friend class FibHeap;
 6     FibHeapNode() {}
 7     FibHeapNode( Data d, Key k): 
 8         myKey( k ),
 9         myData( d ),
10         degree( 0 ),
11         marked( false ),
12         child( NULL )
13     {
14         prev = next = this; //doubly linked circular list
15     }
16 
17     Key key() const
18     {
19         return myKey;
20     }
21 
22     Data data() const 
23     {
24         return myData;
25     }
26 
28 private:
29     Key        myKey;
30     Data    myData;
31 
32     unsigned int    degree; //节点的孩子节点数
33     bool            marked;    //标识一个节点的孩子节点是否被删除过,用于decreaseKey 操作
34 
35     FibHeapNode<Data, Key>    *prev;    //双循环链表的上一个节点
36     FibHeapNode<Data, Key>    *next;    //双循环链表的下一个节点
37 
38     FibHeapNode<Data, Key>    *child;    //孩子链表中的第
39     一个节点
40     FibHeapNode<Data, Key>    *parent;//父节点
41 };

下面是斐波那契堆类的定义:

 1 template<typename Data, typename Key>
 2 class FibHeap
 3 {
 4 public:
 5     FibHeap() {}
 6     FibHeap(): rootWithMinKey( NULL ), nCount( 0 ), maxDegree( 0 ) { }
 7 
 8     ~FibHeap(){
 9 
10     }
11 
12 
13 private:
14     typedef FibHeapNode<Data, Key>* pNode;
15 
16     pNode rootWithMinKey;
17     unsigned int nCount;
18     unsigned int maxDegree;
19 };

三、斐波那契堆动态集合的操作 其中最难的,也是最麻烦的可能就是Extrac_min()和Decrease_key()操作了,书上对于这两个操作也是写得很详细,具体的我就不再赘述了,我相信认真照着书本上推导,一个下午绝对可以搞定,如果确实难以吃下,推荐一个博客:斐波那契堆(一)之 图文解析 和 C语言的实现,其图文并茂的方式可能比书上更浅显易懂一点,感谢作者。下面附上自己借鉴这个作者所写的程序:

#ifndef _FIBONACCI_HEAP_H_
#define _FIBONACCI_HEAP_H_

template<typename Data, typename Key> 
class FibHeapNode
{
public:
    template<typename D, typename K> friend class FibHeap;
    FibHeapNode() {}
    FibHeapNode( Data d, Key k): 
        myKey( k ),
        myData( d ),
        degree( 0 ),
        marked( false ),
        child( NULL )
    {
        prev = next = this; //doubly linked circular list
    }

    Key key() const
    {
        return myKey;
    }

    Data data() const 
    {
        return myData;
    }

public:
    bool isSingle() const 
    {
        return ( this == this->next );
    }
    
    //插入一个节点或节点链表
    void Insert( FibHeapNode<Data, Key> *other ) 
    {
        if ( !other )
            return;
        //for example: given 1->2->3->1, insert a->b->c->a after node 3;
        //result: 1->2->3->a->b->c->1
        this->next->prev = other->prev;
        other->prev->next = this->next;
        
        this->next = other;
        other->prev = this;
    }
    
    //删除当前节点
    void RemoveNode()
    {
        this->prev->next = this->next;
        this->next->prev = this->prev;
        this->next = this->prev = this;
    }

    //连接其他节点到当前节点
    void addChild( FibHeapNode<Data, Key> *other )
    {
        if ( !child )
            child = other;
        else 
            child->Insert( other );
        //更新相应的信息
        other->parent = this;
        other->marked = false;
        degree ++;
    }
    
    //删除孩子节点
    void removeChild( FibHeapNode<Data, Key> *other )
    {
        if ( other->parent != this )
            throw string ( "Trying to remove a child from a non-parent!");
        //只有一个节点
        if ( other->isSingle() ) {
            if ( child != other )
                throw string ("Trying to remove a non-child");
            child = NULL;
        }
        else {
            if ( child == other ) {
                child = other->next;
            }
            other->RemoveNode();
        }
        //更新
        other->parent = NULL;
        other->marked = false;
        degree --;
    }

    //<<操作符重载
    friend ostream& operator<< ( ostream& out, const FibHeapNode& n)
    {
        return ( out << n.myData << ";" << n.myKey );
    }

    //打印树
    void printTree( ostream& out ) const
    {
        out << myData << ":" << myKey << ":" << degree << ":" << marked;
        if ( child ) {
            out << "(";
            const FibHeapNode<Data, Key> *n = child;
            do {
                if ( n== this )
                    throw string ( "Illegal pointer - node is child of itself");
                n->printTree( out );
                out << " ";
                n = n->next;
            }while ( n != child );
            out << ")";
        }
    }

    void printAll( ostream& out) const
    {
        const FibHeapNode<Data, Key> *n = this;
        do {
            n->printTree( out );
            out << " ";
            n = n->next;
        }while ( n != this );
        out << endl;
    }

private:
    Key        myKey;
    Data    myData;

    unsigned int    degree; //节点的孩子节点数
    bool            marked;    //标识一个节点的孩子节点是否被删除过,用于decreaseKey 操作

    FibHeapNode<Data, Key>    *prev;    //双循环链表的上一个节点
    FibHeapNode<Data, Key>    *next;    //双循环链表的下一个节点

    FibHeapNode<Data, Key>    *child;    //孩子链表中的第
    一个节点
    FibHeapNode<Data, Key>    *parent;//父节点
};

template<typename Data, typename Key>
class FibHeap
{
public:
    FibHeap() {}
    FibHeap(): rootWithMinKey( NULL ), nCount( 0 ), maxDegree( 0 ) { }

    ~FibHeap(){

    }

    bool empty() const 
    {
        return nCount == 0;
    }

    pNode minimum() const
    {
        if ( !rootWithMinKey )
            throw string("no minimum element");
        return rootWithMinKey;
    }

    void printRoots( ostream &out ) const 
    {
        out << "maxDegree=" << maxDegree << "  count=" << count << "  roots=";
        if ( rootWithMinKey )
            rootWithMinKey->printAll( out );
        else
            out << endl;
    }

    void merge ( const FibHeap& other )  // Fibonacci-Heap-Union
    {
        rootWithMinKey->Insert( other.rootWithMinKey );
        if ( !rootWithMinKey || ( other.rootWithMinKey && other.rootWithMinKey->key() < rootWithMinKey->key() ) )
            this->rootWithMinKey = other.rootWithMinKey;
        nCount += other.nCount;
    }

    pNode insertHeap( Data d, Key k )
    {
        nCount ++;
        return insertNode( new FibHeapNode<Data, Key>(d, k));
    }
    
    void removeMinimum()    // Fibonacci-Heap-Extract-Min, CONSOLIDATE
    {
        if ( !rootWithMinKey )
            throw string( "trying to remove from an empty heap" );

        /// Phase 1: Make all the removed root's children new roots:
        // Make all children of root new roots:
        if ( rootWithMinKey->child )
        {
            pNode c = rootWithMinKey->child;
            do
            {
                c->parent = NULL;
                c = c->next;
            }
            while ( c != rootWithMinKey->child );
            rootWithMinKey->child = NULL; // removed all children
            rootWithMinKey->Insert( c );
        }

        /// Phase 2-a: handle the case where we delete the last myKey:
        if ( rootWithMinKey->next == rootWithMinKey )
        {
            if ( count != 0 )
                throw string ( "Internal error: should have 0 keys" );
            rootWithMinKey = NULL;
            return;
        }

        /// Phase 2: merge roots with the same degree:
        vector<pNode> degreeRoots ( maxDegree + 1 ); // make room for a new degree
        fill ( degreeRoots.begin(), degreeRoots.end(), ( pNode )NULL );
        maxDegree = 0;
        pNode currentPointer = rootWithMinKey->next;
        unsigned int currentDegree;
        do
        {
            currentDegree = currentPointer->degree;

            pNode current = currentPointer;
            currentPointer = currentPointer->next;
            while ( degreeRoots[currentDegree] ) // merge the two roots with the same degree:
            {
                pNode other = degreeRoots[currentDegree]; // another root with the same degree
                if ( current->key() > other->key() )
                    swap( other, current );
                // now current->key() <= other->key() - make other a child of current:
                other->RemoveNode(); // remove from list of roots
                current->addChild( other );
                
                degreeRoots[currentDegree] = NULL;
                currentDegree++;
                if ( currentDegree >= degreeRoots.size() )
                    degreeRoots.push_back( ( pNode )NULL );
            }
            // keep the current root as the first of its degree in the degrees array:
            degreeRoots[currentDegree] = current;
        }
        while ( currentPointer != rootWithMinKey );

        /// Phase 3: remove the current root, and calcualte the new rootWithMinKey:
        delete rootWithMinKey;
        rootWithMinKey = NULL;

        unsigned int newMaxDegree = 0;
        for ( unsigned int d = 0; d < degreeRoots.size(); ++d )
        {
            if ( degreeRoots[d] )
            {
                degreeRoots[d]->next = degreeRoots[d]->prev = degreeRoots[d];
                insertNode( degreeRoots[d] );
                if ( d > newMaxDegree )
                    newMaxDegree = d;
            }
        }
        maxDegree = newMaxDegree;
    }
    
    void decreaseKey( pNode node, Key newKey )
    {
        if ( newKey >= node->myKey )
            throw string( "Trying to decrease key to a greater key" );

        // Update the key and possibly the min key:
        node->myKey = newKey;

        // Check if the new key violates the heap invariant:
        pNode parent = node->parent;
        if ( !parent ) // root node - just make sure the minimum is correct
        {
            if ( newKey < rootWithMinKey->key() )
                rootWithMinKey = node;
            return; // heap invariant not violated - nothing more to do
        }
        else if ( parent->key() <= newKey )
        {
            return; // heap invariant not violated - nothing more to do
        }

        for( ;; )
        {
            parent->removeChild( node );
            insertNode( node );

            if ( !parent->parent ) // parent is a root - nothing more to do
            {
                break;
            }
            else if ( !parent->marked )    // parent is not a root and is not marked - just mark it
            {
                parent->marked = true;
                break;
            }
            else
            {
                node = parent;
                parent = parent->parent;
                continue;
            }
        }
    }

    void remove( pNode node, Key minusInfinity )
    {
        if ( minusInfinity >= minimum()->key() )
            throw string( "2nd argument to remove must be a key that is smaller than all other keys" );
        decreaseKey( node, minusInfinity );
        removeMinimum();
    }



protected:
    pNode insertNode ( pNode newNode )
    {
        if ( !rootWithMinKey )
            rootWithMinKey = newNode;
        else {
            rootWithMinKey->Insert( newNode );
            if ( newNode->key() < rootWithMinKey->key() )
                rootWithMinKey = newNode;
        }
        return newNode;
    }


private:
    typedef FibHeapNode<Data, Key>* pNode;

    pNode rootWithMinKey;
    unsigned int nCount;
    unsigned int maxDegree;
};


#endif//_FIBONACCI_HEAP_H_

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏枕边书

空间索引 - 四叉树

前言 作为程序员,应该都对二叉树都不陌生,我们都知道二叉树的变体二叉查找树,非常适合用来进行对一维数列的存储和查找,可以达到 O(logn) 的效率;我们在用二...

40410
来自专栏算法channel

玩转Pandas,让数据处理更easy系列6

玩转Pandas系列已经连续推送5篇,尽量贴近Pandas的本质原理,结合工作实践,按照使用Pandas的逻辑步骤,系统地并结合实例推送Pandas的主要常用功...

962
来自专栏小樱的经验随笔

零基础学并查集算法

并查集是我暑假从高手那里学到的一招,觉得真是太精妙的设计了。以前我无法解决的一类问题竟然可以用如此简单高效的方法搞定。不分享出来真是对不起party了。(pa...

5488
来自专栏用户2442861的专栏

2014美团网笔试题目(总结)

http://blog.csdn.net/wzy_1988/article/details/12438143

1751
来自专栏HansBug's Lab

1647: [Usaco2007 Open]Fliptile 翻格子游戏

1647: [Usaco2007 Open]Fliptile 翻格子游戏 Time Limit: 5 Sec  Memory Limit: 64 MB Subm...

2866
来自专栏PPV课数据科学社区

【学习】笨办法学R编程(二)

经历了前面两个小挑战,你应该对R有点理解了。我们继续推进,今天的问题有点点复杂,复杂的不是R,而是一个数学概念:质数和质因子。任何一个合数都可以...

3099
来自专栏人工智能

如何使用tableaux进行逻辑计算

原文作者:Miguel Diaz Kusztrich

8008
来自专栏灯塔大数据

解密 | 一文总结学习 Python 的 14 张思维导图

前言 本文主要涵盖了 Python 编程的核心知识(暂不包括标准库及第三方库,后续会发布相应专题的文章)。 首先,按顺序依次展示了以下内容的一系列思维导图:基础...

3537
来自专栏大史住在大前端

野生前端的数据结构基础练习(8)——图

图是由边的集合和点的集合组成的。如果图的边有方向(或者说图中的顶点对是有序的)则成为有向图,如果边没有方向则称为无向图。

973
来自专栏机器学习算法与Python学习

机器学习(31)之频繁集挖掘FP Tree详解

关键字全网搜索最新排名 【机器学习算法】:排名第一 【机器学习】:排名第一 【Python】:排名第三 【算法】:排名第四 明早7:22推送第2期免费送书活动 ...

4196

扫码关注云+社区

领取腾讯云代金券