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

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

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

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

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

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

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_
斐波那契堆-C++实现

 

目录
相关文章
|
存储 人工智能 算法
算法导论——斐波那契堆
斐波那契堆是具有最小堆序的有根树的集合,也就是集合中的每棵树都具有父结点的关键字小于或等于子结点的关键字。   对于每一个结点x,主要有以下属性: 名称 说明 记作 关键字 结点存储的值 x.
1281 0
|
6天前
|
算法
基于模糊控制算法的倒立摆控制系统matlab仿真
本项目构建了一个基于模糊控制算法的倒立摆控制系统,利用MATLAB 2022a实现了从不稳定到稳定状态的转变,并输出了相应的动画和收敛过程。模糊控制器通过对小车位置与摆的角度误差及其变化量进行模糊化处理,依据预设的模糊规则库进行模糊推理并最终去模糊化为精确的控制量,成功地使倒立摆维持在直立位置。该方法无需精确数学模型,适用于处理系统的非线性和不确定性。
基于模糊控制算法的倒立摆控制系统matlab仿真
|
1天前
|
算法 数据安全/隐私保护
基于LS算法的OFDM+QPSK系统信道估计均衡matlab性能仿真
基于MATLAB 2022a的仿真展示了OFDM+QPSK系统中最小二乘(LS)算法的信道估计与均衡效果。OFDM利用多个低速率子载波提高频谱效率,通过循环前缀克服多径衰落。LS算法依据导频符号估计信道参数,进而设计均衡器以恢复数据符号。核心程序实现了OFDM信号处理流程,包括加性高斯白噪声的加入、保护间隔去除、快速傅立叶变换及信道估计与均衡等步骤,并最终计算误码率,验证了算法的有效性。
9 2
|
1天前
|
算法
基于GA-PSO遗传粒子群混合优化算法的CVRP问题求解matlab仿真
本文介绍了一种基于GA-PSO混合优化算法求解带容量限制的车辆路径问题(CVRP)的方法。在MATLAB2022a环境下运行,通过遗传算法的全局搜索与粒子群算法的局部优化能力互补,高效寻找最优解。程序采用自然数编码策略,通过选择、交叉、变异操作及粒子速度和位置更新,不断迭代直至满足终止条件,旨在最小化总行驶距离的同时满足客户需求和车辆载重限制。
|
6天前
|
机器学习/深度学习 算法 定位技术
MATLAB - 遗传算法(GA)求解旅行商问题(TSP)
MATLAB - 遗传算法(GA)求解旅行商问题(TSP)
13 3
|
7天前
|
算法
基于多路径路由的全局感知网络流量分配优化算法matlab仿真
本文提出一种全局感知网络流量分配优化算法,针对现代网络中多路径路由的需求,旨在均衡分配流量、减轻拥塞并提升吞吐量。算法基于网络模型G(N, M),包含N节点与M连接,并考虑K种不同优先级的流量。通过迭代调整每种流量在各路径上的分配比例,依据带宽利用率um=Σ(xm,k * dk) / cm来优化网络性能,确保高优先级流量的有效传输同时最大化利用网络资源。算法设定收敛条件以避免陷入局部最优解。
|
1月前
|
传感器 算法
基于无线传感器网络的MCKP-MMF算法matlab仿真
MCKP-MMF算法是一种启发式流量估计方法,用于寻找无线传感器网络的局部最优解。它从最小配置开始,逐步优化部分解,调整访问点的状态。算法处理访问点的动态影响半径,根据带宽需求调整,以避免拥塞。在MATLAB 2022a中进行了仿真,显示了访问点半径请求变化和代价函数随时间的演变。算法分两阶段:慢启动阶段识别瓶颈并重设半径,随后进入周期性调整阶段,追求最大最小公平性。
基于无线传感器网络的MCKP-MMF算法matlab仿真
|
17天前
|
算法 5G vr&ar
基于1bitDAC的MU-MIMO的非线性预编码算法matlab性能仿真
在现代无线通信中,1-bit DAC的非线性预编码技术应用于MU-MIMO系统,旨在降低成本与能耗。本文采用MATLAB 2022a版本,深入探讨此技术,并通过算法运行效果图展示性能。核心代码支持中文注释与操作指导。理论部分包括信号量化、符号最大化准则,并对比ZF、WF、MRT及ADMM等算法,揭示了在1-bit量化条件下如何优化预编码以提升系统性能。
|
24天前
|
算法
基于kalman滤波的UAV三维轨迹跟踪算法matlab仿真
本文介绍了一种使用卡尔曼滤波(Kalman Filter)对无人飞行器(UAV)在三维空间中的运动轨迹进行预测和估计的方法。该方法通过状态预测和观测更新两个关键步骤,实时估计UAV的位置和速度,进而生成三维轨迹。在MATLAB 2022a环境下验证了算法的有效性(参见附图)。核心程序实现了状态估计和误差协方差矩阵的更新,并通过调整参数优化滤波效果。该算法有助于提高轨迹跟踪精度和稳定性,适用于多种应用场景,例如航拍和物流运输等领域。
|
15天前
|
存储 算法 调度
基于和声搜索算法(Harmony Search,HS)的机器设备工作最优调度方案求解matlab仿真
通过和声搜索算法(HS)实现多机器并行工作调度,以最小化任务完成时间。在MATLAB2022a环境下,不仅输出了工作调度甘特图,还展示了算法适应度值的收敛曲线。HS算法模拟音乐家即兴创作过程,随机生成初始解(和声库),并通过选择、微调生成新解,不断迭代直至获得最优调度方案。参数包括和声库大小、记忆考虑率、音调微调率及带宽。编码策略将任务与设备分配映射为和声,目标是最小化完成时间,同时确保满足各种约束条件。

热门文章

最新文章