Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >纯函数式堆(纯函数式优先级队列)part one ----二项堆

纯函数式堆(纯函数式优先级队列)part one ----二项堆

作者头像
Ldpe2G
发布于 2018-07-09 06:58:41
发布于 2018-07-09 06:58:41
67100
代码可运行
举报
运行总次数:0
代码可运行

前言:

这篇文章是基于我看过的一篇论文,主要是关于函数式数据结构,函数式堆(优先级队列),

我会以自己的理解写下来,然后论文中出现的代码将会使用scala这们语言。

论文链接:   Optimal Purely Functional Priority Queues,另外一个链接:   论文。

这里有个好网站介绍:coursera,全球在线课程,各种课程都有。

scala这们语言的一些学习资料:

scala的教程:   scala turorials(文档和更高阶的教程这个网站上都有),

这里有一个有趣的(讲的很有趣值的一看)介绍scala的视频:

Scala for the Intrigued

这里还有scala作者Martin Odersky在youtube上的一个视频,

主要是介绍scala这个语言是如何应对和处理并行计算所带来的挑战(youtube需访问外国网站):

Martin’s talk at OSCON 2011: Working Hard to Keep it Simple

正文:

好了,我们回到正文。

       我们知道堆可用来实现优先级队列,或者说堆就是一种优先级队列。论文中的优先级队列

(priority queue),其实也就是堆(heap),反正都是差不多的东西,我还是写成堆吧。

论文中的讨论的堆支持以下4个操作:

1、findMin(h)           返回堆h中最小的元素;

2、insert(x,h)        往堆h中插入元素x;

3、meld(h1,h2)     将堆h1和h2融合成一个新堆;

4、deleteMin(h)      从堆h中删除最小的元素;

        论文中首先介绍了二项堆(binomial queue),二项堆对于上述4个操作的时间复杂度是O(log n)

接着我们会用3步来优化该堆,最终的效果是,1、2、3操作的时间复杂度会变为O(1)

第一步,引入二项堆的一个变种,斜二项堆(skew binomial queue),该堆通过消除级联链接?

          (cascading links)使插入的时间复杂度为O(1)

第二步,增加一个全局root保存最小的元素,这样查找最小的元素时间复杂度变为O(1),后面会看到其实

            第三步就包含了第二步,所以可以说只要两步;

第三步,通过应用一种技术data-structural bootstrapping,通过允许堆中包含另外一个堆从而使合并                      (meld操作)两个堆的时间复杂度变为 O(1)

下面会详细解释。 

堆的抽象定义:

首先来看堆的抽象定义(引用自coursera的课程Principles of Reactive Programming的week 1的作业 ): 

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//对应论文第三页,Figure 1
trait Heap {

  type H // 代表堆的类型
  
  type A // 代表每个元素的类型
  
  def ord: Ordering[A] // 对元素的排序方式

  def empty: H // 空堆
  
  def isEmpty( h: H ): Boolean // 判断堆h是否为空

  def insert( x: A, h: H ): H // 向堆h中插入元素x
  
  def meld( h1: H, h2: H ): H // 合并堆h1和h2

  def findMin( h: H ): A // 堆h中的最小元素
  
  def deleteMin( h: H ): H // 删除堆中的最小元素
  
}

//当应用到其他元素类型时,只需要类似以下定义多一个trait
trait IntHeap extends Heap {
 
  override type A = Int
  
  override def ord = scala.math.Ordering.Int
  
}

二项堆(binomial queue):

       由于二项堆由二项树组成,所以我们先来看看二项树的定义。

二项树(binomial tree):

1、rank为0的二项树只有一个节点;

2、rank为r+1的二项树由两个rank为r的二项树链接而成,链接的方法是一个二项树作为另外一个二项树的

      最左子树。

下面用图片举例:

     以rank3的二项树为例,我们可以看到蓝色虚线框和蓝色实线框都是rank为2的二项树,其中一个

是另外一个的最左子树。然后从二项树的定义可以知道,rank为r的二项树包含2^r(2的r次方)个

节点。

二项树的另外一个等价定义: 

        一个rank为r的二项树相当于是一个节点,该节点有r个子节点t1 ... tr,对于ti(1 <= i <= r)

是一个rank为r - i的二项树。对照上图很容易明白。

        然后假设一棵二项树是堆有序的,当该树的每个节点都比自己的子节点要小。为了保存堆顺序,

链接两棵堆有序二项树时,选择根较大的二项树作为根较小的二项树的最左子树。

       然后我们回到二项堆,一个二项堆就是一个堆有序的二项树集合,该集合中每棵二项树的rank都

不一样,而且堆中的二项树以升序排列。

       由于我们知道一棵rank为r的二项树 包含2^r(2的r次方)个节点,而结合二项堆的定义,我们可以

推出,一个包含n个节点的二项堆所包含的二项树就对应n的二进制表达形式中的的1。

       比如:我们来看看21个节点的二项堆,由于21的二进制表达形式为:10101,所以该堆包含rank为

0、2、4 ,三棵二项树(对应的节点数分别为1、4、16,加起来就是21)。

      而一个节点数为n的二项堆最多包含  [ log(n + 1) ] 棵二项树。

      现在可以来描述一下二项堆的操作,因为堆中的所有二项树都是堆有序的,所以可以得出堆中的

最小元(findMin)是某棵二项树的树根。所以只要把堆中的二项树的根都遍历一遍就知道最小的元素了,

所以时间复杂度为O(log n)。对于插入一个元素(insert)的操作,首先创建一个只有一个节点的树也就是

rank为0的二项树,然后相当于向一个二进制数加一一样,如果堆中已经存在rank为0的二项树,则将这

两个二项树链接起来,然后继续将rank相同的二项树链接,相当于加一之后的进位,如果没有rank为0的

二项树,则直接将该节点放到二项树列表的头部。最差的时间复杂度为 O(log n),即是一个节点数为n的

二项堆,n = 2^k - 1,相当于二进制位全部为一,这时加一会导致k次进位,也就是要做k次链接,

这也是后面要讲到的斜二项堆会优化的方面,通过消除这种情况,使插入的时间复杂度为O(1)

       容易推出,合并(meld)两个二项堆就相当于两个二进制数相加,升序遍历两个堆的树然后将rank

相同的树链接在一起,一次链接就相当于一次进位,时间复杂度也是(log n)

 最后到deleteMin操作,首先遍历堆中的树根,找到根最小的二项树,然后删除该节点,返回该树

的子树,由于子树是以降序排列的,所以要反转顺序,然后该被删除的树的子树也组成一个二项堆,

于是剩下的操作就是将该堆和原来的堆合并,找树和合并都需要O(log n)的时间,所以总共需要

O(log n)的时间复杂度。

上图解释插入操作:

这个图解释了插入操作的过程,还有上面的说到的,k次链接问题(当n=2^k-1)。

合并两个堆和删除最小的情况参照上图读者自己能画出来。

二项堆的定义:

二项堆的定义(引用自coursera的课程Principles of Reactive Programming的week 1的作业 ): 

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 对应论文Figure 3, 第七页 7
trait BinomialHeap extends Heap {

  type Rank = Int 

  case class Node( x: A, r: Rank, c: List[Node] ) //定义节点,也就是二项树

  override type H = List[Node]    //堆的定义,是由二项树组成的列表

  protected def root( t: Node ) = t.x  //取得树根的值

  protected def rank( t: Node ) = t.r  //取得树的rank

  //链接两棵rank相同的树,树根值大的元素作为树根值小的元素的最左子树
  protected def link( t1: Node, t2: Node ): Node = // t1.r==t2.r
    if ( ord.lteq( t1.x, t2.x ) ) 
        Node( t1.x, t1.r + 1, t2 :: t1.c ) 
    else 
        Node( t2.x, t2.r + 1, t1 :: t2.c )

  //往堆中插入一棵树 
  protected def ins( t: Node, ts: H ): H = ts match {
    case Nil => List(t)  //若堆为空,直接返回只有一棵树的堆
    case tp :: ts =>     //其实认真想一下只有t.r <= tp.r的情况出现,所以如果t.r不小于tp.r,
                         //则t.r == tp.r,所以将之链接起来,然后插入到堆ts中
      if ( t.r < tp.r ) t :: tp :: ts else ins( link( t, tp ), ts )
  }

  override def empty = Nil    //空堆

  override def isEmpty(ts: H) = ts.isEmpty  //判断堆是否为空

  //往堆中插入一个元素,insert函数和ins函数有点令人困惑,论文中说了,这几乎是
  //所有的二项树实现都有的问题
  override def insert( x: A, ts: H ) = ins( Node( x, 0, Nil ), ts )  

  //合并两棵树
  override def meld( ts1: H, ts2: H ) = ( ts1, ts2 ) match {
    case ( Nil, ts ) => ts
    case ( ts, Nil ) => ts
    case ( t1 :: ts1, t2 :: ts2 ) =>
      if ( t1.r < t2.r ) t1 :: meld( ts1, t2 :: ts2 )  //二进制数相加一样
      else if ( t2.r < t1.r ) t2 :: meld( t1 :: ts1, ts2 )
      else ins( link( t1, t2 ), meld( ts1, ts2 ) ) 
      //当找到两个rank相同的树,则先链接两棵树,然后
      //将链接后得到的树插入到剩下已经合并的堆中。
  }                                               

  //找最小元素
  override def findMin( ts: H ) = ts match {
    case Nil => throw new NoSuchElementException( "min of empty heap" )
    case t :: Nil => root(t)
    case t :: ts =>
      val x = findMin(ts)
      if ( ord.lteq( root(t), x ) ) root(t) else x
  }
  
  //删除最小元素
  override def deleteMin( ts: H ) = ts match {
    case Nil => throw new NoSuchElementException( "delete min of empty heap" )
    case t :: ts =>
                                    
      def getMin( t: Node, ts: H ): ( Node, H ) = ts match { 
                                    //辅助函数,找到堆中最小的树,返回值
        case Nil => ( t, Nil )      //Node代表根最小的树,H是已经删除Node
        case tp :: tsp =>          //的堆,虽然是递归看的有点头晕,但其实就是
                                   //从列表的最后一棵树开始遍历到头,相邻两
                                   //棵树互相比较,每次比较最小的树就是tq
                                    //最后回溯到头就找到根最小的树了
          val ( tq, tsq ) = getMin( tp, tsp )      
          if ( ord.lteq( root(t), root(tq) ) ) (t, ts) 
          else ( tq, t :: tsq )                    
      }
      
      val ( Node( _, _, c ), tsq ) = getMin( t, ts )
      meld( c.reverse, tsq )  //将被删除节点的子树和剩下的树合并
  }
}

好,二项树就介绍完了,part two的内容是介绍斜二项堆,斜二项树(解决k次链接,使得插入操作的时间复杂度

O(log n))等内容。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
纯函数式堆(纯函数式优先级队列)part two ----斜二项堆
前言: 这篇文章是基于我看过的一篇论文,主要是关于函数式数据结构,函数式堆(优先级队列), 我会以自己的理解写下来,然后论文中出现的代码将会使用scala这们语言。 论文链接:   Optimal Purely Functional Priority Queues,另外一个链接:   论文。 正文: 紧接part one的内容,接下来进入斜二项堆。 斜二项堆(skew binomial queue):      斜二项堆支持插入操作O(1)的时间复杂度,通过借用random-access lists中的技
Ldpe2G
2018/07/09
8180
纯函数式堆(纯函数式优先级队列)part three ---- bootstrapping (自举)
前言: 这篇文章是基于我看过的一篇论文,主要是关于函数式数据结构,函数式堆(优先级队列), 我会以自己的理解写下来,然后论文中出现的代码将会使用scala这们语言。 论文链接:   Optimal Purely Functional Priority Queues,另外一个链接:   论文。 正文: 紧接patr two, 这章介绍对合并和查找操作的优化,使得最终插入,合并,查找最小的时间复杂度均为O(1)。 这里我跳过了论文中增加全局根那一节,因为bootstrap这一节包含了增加全局根的内容。 boo
Ldpe2G
2018/07/09
5450
二叉树与堆
树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。 有一个特殊的结点,称为根结点,根节点没有前驱结点 除根节点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i<= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继
绝活蛋炒饭
2024/12/16
980
二叉树与堆
Java数据结构与算法解析(十三)——优先级队列
在很多应用中,我们通常需要按照优先级情况对待处理对象进行处理,比如首先处理优先级最高的对象,然后处理次高的对象。最简单的一个例子就是,在手机上玩游戏的时候,如果有来电,那么系统应该优先处理打进来的电话。
老马的编程之旅
2022/06/22
4260
Java数据结构与算法解析(十三)——优先级队列
【java-数据结构】Java优先级队列揭秘:堆的力量让数据处理飞起来
在开发中,尤其是需要处理大量数据或者进行任务调度的场景下,如何高效地管理数据的顺序和优先级是一个至关重要的问题。Java 提供了优先级队列(PriorityQueue),它基于堆(Heap)实现,能够以高效的方式管理数据的优先级。在本文中,我们将深入探讨优先级队列的工作原理,特别是堆的作用,并通过示例代码帮助你更好地理解其应用。
学无止尽5
2025/03/05
1530
【java-数据结构】Java优先级队列揭秘:堆的力量让数据处理飞起来
文心一言 VS 讯飞星火 VS chatgpt (151)-- 算法导论12.2 7题
TREE-MINIMUM: 这个操作在二叉搜索树中找到最小元素的复杂度是 O(h),其中 h 是树的高度。因为在二叉搜索树中,最小元素总是在最左边的叶子节点,我们可以通过递归向下搜索找到它。 TREE-SUCCESSOR: 这个操作找到给定节点的后继节点的复杂度也是 O(h),因为后继节点总是在给定节点的右子树的最小节点。如果右子树为空,那么后继节点就是其父节点的右子节点。 现在,我们来考虑算法的总运行时间。首先,我们调用 TREE-MINIMUM 找到最小元素,这需要 O(h) 的时间。然后,我们需要对除最小元素外的其他 n-1 个节点调用 TREE-SUCCESSOR。由于每次调用 TREE-SUCCESSOR 都需要 O(h) 的时间,所以总共需要 O(h*(n-1)) 的时间。由于 h ≤ n(树的高度不会超过节点的数量),所以 h*(n-1) = O(n^2) ≤ O(n),因此总运行时间为 O(n)。
福大大架构师每日一题
2023/12/13
1940
文心一言 VS 讯飞星火 VS chatgpt (151)-- 算法导论12.2 7题
堆的实现与堆排序
本篇旨在介绍二叉树中特殊结构堆, 以及堆的实现与应用 更多文章 博客主页: 酷酷学!!! 点击关注 一起加油~
用户11317877
2024/10/16
900
堆的实现与堆排序
图文详解二叉堆,实现优先级队列
二叉堆(Binary Heap)没什么神秘,性质比二叉搜索树 BST 还简单。其主要操作就两个,sink(下沉)和swim(上浮),用以维护二叉堆的性质。其主要应用有两个,首先是一种排序方法「堆排序」,第二是一种很有用的数据结构「优先级队列」。
灵魂画师牧码
2019/08/02
1.7K0
优先队列(堆)
优先队列:顾名思义,这个队列中的元素是有优先级的,它和普通的先进先出(FIFO)不一样。我们在很多场合可能都需要用到这种特殊的队列(例如,操作系统的进程调度)。
zy010101
2019/05/25
3940
《Java初阶数据结构》----6.<优先级队列之PriorityQueue底层:堆>
        前面我们了解过队列是一种先进先出(FIFO)的数据结构。但有些情况下,操作的数据可能带有优先级,一般出队列时,可能需要优先级高的元素先出队列。此时普通队列就不适用了。因此我们引入优先级队列。
用户11288958
2024/09/24
1110
《Java初阶数据结构》----6.<优先级队列之PriorityQueue底层:堆>
二项队列
二项队列是 堆序 的集合,也叫 森林。其中每一种形式都有约束。 二项树Bk由一个带有儿子的B0,B1,B2...组成,高度为k的二项树 恰好有2^k个结点。每一种高度只能出现一次...因此,只有1,2,4,8...等结点数目的二项树 deleteMin操作需要快速的找出跟的所有子树的能力,因此需要一般树的表示方法: 每个结点的儿子都在一个链表中,而且每个结点都有一个指向它的第一个儿子的指针。 二项树的每一个结点包括:数据,第一个儿子,以及右兄弟 下面是二项队列类构架及结点定义: 1 template <t
用户1154259
2018/01/17
6860
学习算法 – 优先级队列二叉堆实现
优先队列最少有两个操作:插入(Insert)和删除最小者(DeleteMin)。
全栈程序员站长
2022/07/06
3580
学习算法 – 优先级队列二叉堆实现
用Python实现数据结构之优先级队列
最简单的优先级队列可能就是一堆不同大小的数组成的队列,每次需要取出其中最小或最大的数,这是我们可以把这些数本身的大小叫做他们的优先级。
py3study
2020/01/17
8000
基本2D优先堆基本优先队列二叉堆实现优先队列代码实现
基本优先队列 考虑一种队列:每次取出的数据是队列中最小的元素。这种队列可用于程序调度,作业分配等领域,这种队列被称为优先队列,核心的方法有: Insert()方法:将数据插入优先队列 DeleteMin()方法:将队列中的数据中最小的输出并删除 优先队列可以使用堆这一数据结构实现 二叉堆实现优先队列 二叉堆 二叉堆是除了底层外被完全填满的二叉树,最底层的数据也是从左到右填入(完全二叉树)。因为其填满的特性,可以直接使用数组实现该树型结构:一个位于数组i位置的节点的子节点分别是2*i和2*i+1 优先队列实现
月见樽
2018/04/27
6590
基本2D优先堆基本优先队列二叉堆实现优先队列代码实现
手撸优先队列——二叉堆
在数据结构中,队列可以对应我们生活中的排队现象,像买早点排队,上公共汽车排队等。但是有一些情况,我们要优先某些元素,比如:上公共汽车时,虽然在排队,还是要优先老幼病残孕先上车;当有多个电脑向打印机发送打印请求时,一台电脑要打印100页,而其他电脑都是单页打印,此时更合理的做法时,优先打印单页的请求,最后再打印100页的请求。虽然我们都向往公平,在排队时也讲究先来后到,但是在某些特殊的情况下,还是要允许加塞现象的。这就是今天要给大家讲的——优先队列。
小忽悠
2024/10/28
1290
手撸优先队列——二叉堆
二叉树、堆的结构与相关问题
        树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。         有一个特殊的结点,称为根结点,根节点没有前驱结点 除根节点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i<= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继         因此,树是递归定义的。
比特大冒险
2023/04/16
4240
二叉树、堆的结构与相关问题
对优先级队列(堆)的理解
先将元素放入到底层空间中(注意:空间不够时需要扩容) 将最后新插入的节点向上调整,直到满足堆的性质
用户11305962
2024/10/09
1290
对优先级队列(堆)的理解
数据结构基础知识: 表 栈 队列 树 散列 堆
表,栈和队列是计算机科学中最简单和最基本的三种底层数据结构。事实上,每一个有意义的程序都将明晰地至少使用一种这样的数据结构,而栈则在程序中总是要间接地用到,不管你在程序中是否做了声明。
我是一条小青蛇
2019/11/21
1.2K0
数据结构-6.优先级队列
本篇博客给大家带来的是 优先级队列(堆) 的知识点, 其中包括 建堆时间复杂度的求解, 堆的向下调整算法, 向上调整算法, 堆的插入,删除操作 ...... 至于 PriorityQueue的扩容,  面试题 top-k问题 以及 堆排序 在后序会专门出一篇文章.
用户11369350
2024/11/19
760
数据结构-6.优先级队列
Java集合与数据结构——优先级队列(堆)
  我们在说 大小根堆 时,只说了 根节点比孩子节点大,没有说 左右孩子节点谁比谁大、谁比谁小.
RAIN7
2021/08/16
4960
推荐阅读
相关推荐
纯函数式堆(纯函数式优先级队列)part two ----斜二项堆
更多 >
领券
💥开发者 MCP广场重磅上线!
精选全网热门MCP server,让你的AI更好用 🚀
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验