二叉堆/二项堆/斐波那契堆

article/2025/8/29 14:23:41

二叉堆

二叉树

  • 二叉树:是树的一种,主要的特点是二叉树的所有节点最多只有两个叶节点。除此之外没有别的要求
  • 完全二叉树:就是在二叉树当中,除了最后一层之外,所有层的节点都有满的,且最后一层的节点也是从左到右的。优先填满左边的节点。
  • 满二叉树:又是一种特殊的完全二叉树,满二叉树的最后一层也是满的。也就是说,除了最后一层的节点外所有的节点都有两个子节点,满二叉树的第i层节点数量为2^(i-1)。
  • 二叉查找树(Binary Search Tree),又称为有序二叉树,排序二叉树,满足以下性质:

    1)没有键值相等的节点。

    2)若左子树不为空,左子树上节点值均小于根节点的值。

    3)若右子树不为空,右子树上节点值均大于根节点的值。
    二叉查找树中对于目标节点的查找过程类似与有序数组的二分查找,并且查找次数不会超过树的深度。设节点数目为n,树的深度为h,假设树的每层都被塞满(第L层有2^L个节点,层数从1开始),则根据等比数列公式可得h=log(n+1)。即最好的情况下,二叉查找树的查找效率为O(log n)。当二叉查找树退化为单链表时,比如,只有右子树的情况,如下图所示,此时查找效率为O(n),如下图所示:

 

二叉堆

二叉堆是完全二元树或者是近似完全二元树,按照数据的排列方式可以分为两种:最大堆和最小堆。

下面是数组实现的最大堆和最小堆的示意图:

添加

假设在最大堆[90,80,70,60,40,30,20,10,50]种添加85,需要执行的步骤如下:

 

删除

假设从最大堆[90,85,70,60,80,30,20,10,50,40]中删除90,需要执行的步骤如下:

注意:从最大堆[90,85,70,60,80,30,20,10,50,40]中删除60,执行的步骤不能单纯的用它的字节点来替换;而必须考虑到"替换后的树仍然要是最大堆"!

源码实例(最大堆)

 

/*** 二叉堆(最大堆)**/
import java.util.ArrayList;
import java.util.List;
public class MaxHeap<T extends Comparable<T>> {private List<T> mHeap;    // 队列(实际上是动态数组ArrayList的实例)public MaxHeap() {this.mHeap = new ArrayList<T>();}/* * 最大堆的向下调整算法** 注:数组实现的堆中,第N个节点的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。** 参数说明:*     start -- 被下调节点的起始位置(一般为0,表示从第1个开始)*     end   -- 截至范围(一般为数组中最后一个元素的索引)*/protected void filterdown(int start, int end) {int c = start;          // 当前(current)节点的位置int l = 2*c + 1;     // 左(left)孩子的位置T tmp = mHeap.get(c);    // 当前(current)节点的大小while(l <= end) {int cmp = mHeap.get(l).compareTo(mHeap.get(l+1));// "l"是左孩子,"l+1"是右孩子if(l < end && cmp<0)l++;        // 左右两孩子中选择较大者,即mHeap[l+1]cmp = tmp.compareTo(mHeap.get(l));if(cmp >= 0)break;        //调整结束else {mHeap.set(c, mHeap.get(l));c = l;l = 2*l + 1;   }       }   mHeap.set(c, tmp);}/** 删除最大堆中的data** 返回值:*      0,成功*     -1,失败*/public int remove(T data) {// 如果"堆"已空,则返回-1if(mHeap.isEmpty() == true)return -1;// 获取data在数组中的索引int index = mHeap.indexOf(data);if (index==-1)return -1;int size = mHeap.size();mHeap.set(index, mHeap.get(size-1));// 用最后元素填补mHeap.remove(size - 1);                // 删除最后的元素if (mHeap.size() > 1)filterdown(index, mHeap.size()-1);    // 从index号位置开始自上向下调整为最小堆return 0;}/** 最大堆的向上调整算法(从start开始向上直到0,调整堆)** 注:数组实现的堆中,第N个节点的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。** 参数说明:*     start -- 被上调节点的起始位置(一般为数组中最后一个元素的索引)*/protected void filterup(int start) {int c = start;            // 当前节点(current)的位置int p = (c-1)/2;        // 父(parent)结点的位置 T tmp = mHeap.get(c);        // 当前节点(current)的大小while(c > 0) {int cmp = mHeap.get(p).compareTo(tmp);if(cmp >= 0)break;else {mHeap.set(c, mHeap.get(p));c = p;p = (p-1)/2;   }       }mHeap.set(c, tmp);}/* * 将data插入到二叉堆中*/public void insert(T data) {int size = mHeap.size();mHeap.add(data);    // 将"数组"插在表尾filterup(size);        // 向上调整堆}@Overridepublic String toString() {StringBuilder sb = new StringBuilder();for (int i=0; i<mHeap.size(); i++)sb.append(mHeap.get(i) +" ");return sb.toString();}public static void main(String[] args) {int i;int a[] = {10, 40, 30, 60, 90, 70, 20, 50, 80};MaxHeap<Integer> tree=new MaxHeap<Integer>();System.out.printf("== 依次添加: ");for(i=0; i<a.length; i++) {System.out.printf("%d ", a[i]);tree.insert(a[i]);}System.out.printf("\n== 最 大 堆: %s", tree);i=85;tree.insert(i);System.out.printf("\n== 添加元素: %d", i);System.out.printf("\n== 最 大 堆: %s", tree);i=90;tree.remove(i);System.out.printf("\n== 删除元素: %d", i);System.out.printf("\n== 最 大 堆: %s", tree);System.out.printf("\n");}
} 

 

二项堆

二项堆是二项树的集合。在了解二项堆之前,先对二项树进行介绍。

二项树

二项树的定义

二项树是一种递归定义的有序树。它的递归定义如下:
(01) 二项树B0只有一个结点;
(02) 二项树Bk由两棵二项树B(k-1)组成的,其中一棵树是另一棵树根的最左孩子。

上图的B0、B1、B2、B3、B4都是二项树。对比前面提到的二项树的定义:B0只有一个节点,B1由两个B0所组成,B2由两个B1所组成,B3由两个B2所组成,B4由两个B3所组成;而且,当两颗相同的二项树组成另一棵树时,其中一棵树是另一棵树的最左孩子。

 

二项树的性质

二项树有以下性质:
[性质一] Bk共有2k个节点。
               如上图所示,B0有20=1节点,B1有21=2个节点,B2有22=4个节点,...
[性质二] Bk的高度为k。
               如上图所示,B0的高度为0,B1的高度为1,B2的高度为2,...
[性质三] Bk在深度i处恰好有C(k,i)个节点,其中i=0,1,2,...,k。
              C(k,i)是高中数学中阶乘元素,例如,C(10,3)=(10*9*8) / (3*2*1)=240
              B4中深度为0的节点C(4,0)=1
              B4中深度为1的节点C(4,1)= 4 / 1 = 4
              B4中深度为2的节点C(4,2)= (4*3) / (2*1) = 6
              B4中深度为3的节点C(4,3)= (4*3*2) / (3*2*1) = 4
              B4中深度为4的节点C(4,4)= (4*3*2*1) / (4*3*2*1) = 1
             合计得到B4的节点分布是(1,4,6,4,1)。
[性质四] 根的度数为k,它大于任何其它节点的度数。
              节点的度数是该结点拥有的子树的数目。

 

二项堆

二项堆是指满足以下性质的二项树的集合:
(01) 每棵二项树都满足最小堆性质。即,父节点的关键字 <= 它的孩子的关键字。
(02) 不能有两棵或以上的二项树具有相同的度数(包括度数为0)。换句话说,具有度数k的二项树有0个或1个。

 

上图就是一棵二项堆,它由二项树B0、B2和B3组成。对比二项堆的定义:(01)二项树B0、B2、B3都是最小堆;(02)二项堆不包含相同度数的二项树。

 

         二项堆的第(01)个性质保证了二项堆的最小节点就是某个二项树的根节点,第(02)个性质则说明结点数为n的二项堆最多只有log{n} + 1棵二项树。实际上,将包含n个节点的二项堆,表示成若干个2的指数和(或者转换成二进制),则每一个2个指数都对应一棵二项树。例如,13(二进制是1101)的2个指数和为13=23 + 22+ 20, 因此具有13个节点的二项堆由度数为3, 2, 0的三棵二项树组成。

 

基本定义

BinomialNode是二项堆的节点。它包括了关键字(key),用于比较节点大小;度数(degree),用来表示当前节点的度数;左孩子(child)、父节点(parent)以及兄弟节点(next)。
BinomialHeap是二项堆对应的类,它包括了二项堆的根节点mRoot以及二项堆的基本操作的定义。

 

public class BinomialHeap<T extends Comparable<T>> {private BinomialNode<T> mRoot;    // 根结点private class BinomialNode<T extends Comparable<T>> {T key;                // 关键字(键值)int degree;            // 度数BinomialNode<T> child;    // 左孩子BinomialNode<T> parent;    // 父节点BinomialNode<T> next;    // 兄弟节点public BinomialNode(T key) {this.key = key;this.degree = 0;this.child = null;this.parent = null;this.next = null;}public String toString() {return "key:"+key;}}...
}

内存图如下图所示:

合并操作

合并操作是二项堆的重点,它的添加操作也是基于合并操作来实现的。合并两个二项堆,需要的步骤概括起来如下:
(01) 将两个二项堆的根链表合并成一个链表。合并后的新链表按照"节点的度数"单调递增排列。
(02) 将新链表中"根节点度数相同的二项树"连接起来,直到所有根节点度数都不相同。

举例如下图所示:

第1步:将两个二项堆的根链表合并成一个链表
          执行完第1步之后,得到的新链表中有许多度数相同的二项树。实际上,此时得到的是对应"Case 4"的情况,"树41"(根节点为41的二项树)和"树13"的度数相同,且"树41"的键值 > "树13"的键值。此时,将"树41"作为"树13"的左孩子。
第2步:合并"树41"和"树13"
         执行完第2步之后,得到的是对应"Case 3"的情况,"树13"和"树28"的度数相同,且"树13"的键值 < "树28"的键值。此时,将"树28"作为"树13"的左孩子。
第3步:合并"树13"和"树28"
         执行完第3步之后,得到的是对应"Case 2"的情况,"树13"、"树28"和"树7"这3棵树的度数都相同。此时,将x设为下一个节点。
第4步:将x和next_x往后移
         执行完第4步之后,得到的是对应"Case 3"的情况,"树7"和"树11"的度数相同,且"树7"的键值 < "树11"的键值。此时,将"树11"作为"树7"的左孩子。
第5步:合并"树7"和"树11"
         执行完第5步之后,得到的是对应"Case 4"的情况,"树7"和"树6"的度数相同,且"树7"的键值 > "树6"的键值。此时,将"树7"作为"树6"的左孩子。
第6步:合并"树7"和"树6"
         此时,合并操作完成!

 

插入操作

插入操作可以看作是将"要插入的节点"和当前已有的堆进行合并。

删除操作

删除二项堆中的某个节点,需要的步骤概括起来如下:
(01) 将"该节点"交换到"它所在二项树"的根节点位置。方法是,从"该节点"不断向上(即向树根方向)"遍历,不断交换父节点和子节点的数据,直到被删除的键值到达树根位置。
(02) 将"该节点所在的二项树"从二项堆中移除;将该二项堆记为heap。
(03) 将"该节点所在的二项树"进行反转。反转的意思,就是将根的所有孩子独立出来,并将这些孩子整合成二项堆,将该二项堆记为child。
(04) 将child和heap进行合并操作。

举例如下图所示:

 

更新操作

 

更新二项堆中的某个节点,就是修改节点的值。

斐波那契堆

斐波那契堆(Fibonacci heap)是一种可合并堆,可用于实现合并优先队列。它比二项堆具有更好的平摊分析性能,它的合并操作的时间复杂度是O(1)。
与二项堆一样,它也是由一组堆最小有序树组成,并且是一种可合并堆。
与二项堆不同的是,斐波那契堆中的树不一定是二项树;而且二项堆中的树是有序排列的,但是斐波那契堆中的树都是有根而无序的。

 

基本定义

FibNode是斐波那契堆的节点类,它包含的信息较多。key是用于比较节点大小的,degree是记录节点的度,left和right分别是指向节点的左右兄弟,child是节点的第一个孩子,parent是节点的父节点,marked是记录该节点是否被删除第1个孩子(marked在删除节点时有用)。
FibHeap是斐波那契堆对应的类。min是保存当前堆的最小节点,keyNum用于记录堆中节点的总数,maxDegree用于记录堆中最大度,而cons在删除节点时来暂时保存堆数据的临时空间。

 

public class BinomialHeap<T extends Comparable<T>> {private BinomialNode<T> mRoot;    // 根结点private class BinomialNode<T extends Comparable<T>> {T key;                // 关键字(键值)int degree;            // 度数BinomialNode<T> child;    // 左孩子BinomialNode<T> parent;    // 父节点BinomialNode<T> next;    // 兄弟节点public BinomialNode(T key) {this.key = key;this.degree = 0;this.child = null;this.parent = null;this.next = null;}public String toString() {return "key:"+key;}}...
}

斐波那契堆是由一组最小堆组成,这些最小堆的根节点组成了双向链表(又叫"根链表");斐波那契堆中的最小节点就是"根链表中的最小节点"!其内存图如下图所示:

插入操作

插入操作非常简单:插入一个节点到堆中,直接将该节点插入到"根链表的min节点"之前即可;若被插入节点比"min节点"小,则更新"min节点"为被插入节点。

斐波那契堆的根链表是"双向链表",这里将min节点看作双向联表的表头。在插入节点时,每次都是"将节点插入到min节点之前(即插入到双链表末尾)"。此外,对于根链表中最小堆都只有一个节点的情况,插入操作就很演化成双向链表的插入操作。

 

合并操作

合并操作和插入操作的原理非常类似:将一个堆的根链表插入到另一个堆的根链表上即可。简单来说,就是将两个双链表拼接成一个双向链表。

 

获取最值操作

获取最小结点的操作是斐波那契堆中较复杂的操作。
(1)将要抽取最小结点的子树都直接串联在根表中;
(2)合并所有degree相等的树,直到没有相等的degree的树。

 

修改节点值操作

修改节点值包括减小节点值和增大节点值操作,以减小节点为例,减少斐波那契堆中的节点的键值,这个操作的难点是:如果减少节点后破坏了"最小堆"性质,如何去维护呢?下面对一般性情况进行分析。

(1) 首先,将"被减小节点"从"它所在的最小堆"剥离出来;然后将"该节点"关联到"根链表"中。 倘若被减小的节点不是单独一个节点,而是包含子树的树根。则是将以"被减小节点"为根的子树从"最小堆"中剥离出来,然后将该树关联到根链表中。
(2) 接着,对"被减少节点"的原父节点进行"级联剪切"。所谓"级联剪切",就是在被减小节点破坏了最小堆性质,并被切下来之后;再从"它的父节点"进行递归级联剪切操作。
      而级联操作的具体动作则是:若父节点(被减小节点的父节点)的marked标记为false,则将其设为true,然后退出。
      否则,将父节点从最小堆中切下来(方式和"切被减小节点的方式"一样);然后递归对祖父节点进行"级联剪切"。
      marked标记的作用就是用来标记"该节点的子节点是否有被删除过",它的作用是来实现级联剪切。而级联剪切的真正目的是为了防止"最小堆"由二叉树演化成链表。
(3) 最后,别忘了对根链表的最小节点进行更新。

 

删除节点值操作

删除节点操作是:"取出最小节点"和"减小节点值"的组合。

(1) 先将被删除节点的键值减少。减少后的值要比"原最小节点的值"即可。
(2) 接着,取出最小节点即可。


http://chatgpt.dhexx.cn/article/6G9qangl.shtml

相关文章

斐波那契堆(Fibonacci heap)原理详解

前言 斐波那契堆(Fibonacci heap)是计算机科学中最小堆有序树的集合。它和二项式堆有类似的性质&#xff0c;但比二项式堆有更好的均摊时间。堆的名字来源于斐波那契数&#xff0c;它常用于分析运行时间。 堆结构介绍 基本术语介绍&#xff1a; 关键字&#xff1a;堆节点储存的…

斐波那契堆 - 解析与实现

概要 本章介绍斐波那契堆。和以往一样&#xff0c;本文会先对斐波那契堆的理论知识进行简单介绍&#xff0c;然后给出C语言的实现。后续再分别给出C和Java版本的实现&#xff1b;实现的语言虽不同&#xff0c;但是原理如出一辙&#xff0c;选择其中之一进行了解即可。若文章有…

算法导论 斐波那契堆

算法导论 斐波那契堆 定义 堆H 最小结点min&#xff1a;指向最小关键字key的根结点n表示当前堆中结点的个数 结点x 最小堆性质&#xff1a;每个结点的关键字key均大于等于父结点的关键字根链表&#xff1a;所有的根结点都通过left,right指针形成一个环形链表父类指针为p,左右兄…

斐波那契堆(Fibonacci heap)原理详解(附java代码实现)

前言 斐波那契堆(Fibonacci heap)是计算机科学中最小堆有序树的集合。它和二项式堆有类似的性质&#xff0c;但比二项式堆有更好的均摊时间。堆的名字来源于斐波那契数&#xff0c;它常用于分析运行时间。 堆结构介绍 基本术语介绍&#xff1a; 关键字&#xff1a;堆节点储存的…

斐波那契堆

斐波那契堆 作者&#xff1a; 大树先生 博客&#xff1a; http://blog.csdn.net/koala_tree GitHub&#xff1a;https://github.com/koalatree 2017 年 09 月 13 日 自《算法导论》. 斐波那契堆有两种用途&#xff1a;第一种&#xff0c;支持一系列操作&#xff0c;这些操作…

斐波那契堆(Fibonacci Heap)

也许我们每个人都知道斐波那契数列&#xff08;Fibonacci sequence&#xff09;。即这样一个数列&#xff1a;1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144...,如果我们用伪代码比表示: int FibonacciSequence(int n){if (n 1 || n 2) {return 1;}return FibonacciSequence(n …

斐波那契堆(一)之 图文解析 和 C语言的实现

出自&#xff1a;http://www.cnblogs.com/skywang12345/p/3659060.html 斐波那契堆(一)之 图文解析 和 C语言的实现 概要 本章介绍斐波那契堆。和以往一样&#xff0c;本文会先对斐波那契堆的理论知识进行简单介绍&#xff0c;然后给出C语言的实现。后续再分别给出C和Java版本…

斐波那契堆的C++实现

本文改编自《算法导论》第三版第19章&#xff1a;斐波那契堆。文中代码为书中伪代码的C实现&#xff0c;如有疏漏还请指出。 1.斐波那契堆的结构 斐波那契堆是一种可并堆&#xff0c;它支持以下操作&#xff1a; ( 1 ) (1) (1)在 O ( 1 ) O(1) O(1)时间内插入元素、获取最小…

常见电子元器件检测方法。——Arvin

电子设备中使用着大量各种类型的电子元器件&#xff0c;设备发生故障大多是由于电子元器件失效或损坏引起的。因此怎么正确检测电子元器件就显得尤其重要&#xff0c;这也是电子维修人员必须掌握的技能。我在电器维修中积累了部分常见电子元器件检测经验和技巧&#xff0c;供大…

电工电子复习题

1、应用叠加定理时&#xff0c;理想电压源不作用时视为______&#xff0c; 理想电流源不作用时视为____。 2、电容元件具有“通_____隔_____”的特性&#xff0c; 电感元件具有通___阳___ ”的特性&#xff0c;。 3、已知:“u 60sin(628t -135度)(v),则其有效值为____V,角频率为…

我的元器件入门

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 元器件 前言一、电阻器1、什么是电阻器2、电阻的标示方法和测量方法3、电阻的特性4、基本参数5、电阻的功能 二、电容器1、什么是电容&#xff1f;组成&#xff0c;基本参数&…

2023年天津中德应用技术大学专升本机械电子工程专业考试大纲

天津中德应用技术大学 机械电子工程专业&#xff08;高职升本科&#xff09; 2023年专业基础考试大纲 备注&#xff1a;考试题型&#xff1a;填空题、选择题、判断题、问答题、分析题、设计与计算题 《机械设计基础》主要知识点 第一部分常用机构 一、平面机构运动分析 知…

2021中级维修电工证考试题库2021职业技能鉴定

题库来源:特种作业模考题库小程序 1.三相负载作Y接时,无论负载对称与否,线电流总等于相电流。 √ 2.双向晶闸管是( )半导体结构。 B A.四层 B.五层 C.三层 D.二层 3.符合有“0”得“0”,全“1”得“1”的逻辑关系的逻辑门是( )。 B A.或门 B.与门 C.非门 D.或非门 4.示波…

2018第八届至2022年第十三届蓝桥杯单片机开放与设计省赛客观题及简解整理

前言&#xff1a; 由于本人马上要参加第十四届蓝桥杯单片机设计与开发的省赛了&#xff0c;在对客观题复习两轮后&#xff0c;发现效率是比较低的&#xff0c;因此整理了2018至2022年的省赛客观题&#xff0c;将大概的考点划分三部分&#xff0c;这样可以更加系统的复习其内容。…

晶体管频率特性——高频等效模型、频率特性、π模型的单向化

晶体管高频等效模型 通过之前的定性分析得出在高频情况下晶体管结电容将对信号传输带来较大影响。之前的 h 参数等效模型没有考虑结电容的影响&#xff0c;因此不再适用&#xff0c;此时要用新的模型来反映晶体管的结电容&#xff0c;这就是高频等效模型。 此时从晶体管的实际…

[渝粤教育] 郑州航空工业管理学院 电工电子技术基础 参考 资料

教育 -电工电子技术基础-章节资料考试资料-郑州航空工业管理学院【】 小节测试 1、【判断题】任何一个完整的电路都必须有电源、负载和中间环节三个基本部分组成。 A、正确 B、错误 参考资料【 】 2、【判断题】电路的作用是对电能进行传输、分配和转换&#xff1b;而对电信号进…

服务于期末考试的计算机硬件基础资料

电路的基本概念和基本定律 电流和电路模型 理想元件、理想电路、集总参数元件、集总参数电路 集总元件&#xff1a; 当电路器件的尺寸远小于电路最高工作频率所对应的波长时&#xff0c;可以认为元件的参数“集总”于一个点上&#xff0c;形成所谓的集总参数元件。 理想元件&am…

您需要了解的跨阻放大器——第1部分

跨阻放大器(TIA)是光学传感器(如光电二极管)的前端放大器,用于将传感器的输出电流转换为电压。跨阻放大器的概念很简单,即运算放大器(op amp)两端的反馈电阻(RF)使用欧姆定律VOUT= I RF 将电流(I)转换为电压(VOUT)。在这一系列博文中,我将介绍如何补偿TIA,及如…

小白的模拟电路初步学习20日打卡(11)

放大电路分析 一.基本共射放大电路的波形分析 输入电压。并且Ic等于βIb&#xff0c;所以本图可以表示Ic动态信号驮载在静态工作点上变化方向与之前的IbIc不同&#xff0c;但是是有关联的&#xff0c;即与其反向。 而这同时也是电压输出波形&#xff0c;所以可知共射放大电路…

8.1 正弦波振荡电路(1)

正弦波振荡电路是在没有外加输入信号的情况下&#xff0c;依靠电路自激振荡而产生正弦波输出电压的电路。它广泛地应用于测量、遥控、通讯、自动控制、热处理和超声波电焊等加工设备之中&#xff0c;也作为模拟电子电路的测试信号。 一、概述、 1、产生正弦波振荡的条件 在负…