左式堆
堆结构及堆排序详解

堆结构及堆排序详解⼀、物理结构和概念结构 学习堆必须明确,堆有两个结构,⼀个是真实存在的物理结构,⼀个是有助于理解的概念结构。
1. 堆⼀般由数组实现,但是我们平时在理解堆的时候,会把他构建成⼀个完全⼆叉树结构。
堆分为⼤根堆和⼩根堆:⼤根堆,就是这颗树⾥的每⼀个结点都是以它为根结点的树中的最⼤值;⼩根堆则与之相反。
(注意⼀定要是完全⼆叉树) 2. 物理结构:从 0 开始的数组。
怎么将数组和⼆叉树联系起来呢? 当⼀个结点在数组中的下标为 index,那么这个结点对应的⽗节点的下标为( index-1 ) / 2,左孩⼦的下标为 2 * index +1 ,右孩⼦的下标为 2 * index +2 。
上⾯是以 0 开始的数组中各结点对应的关系,数组也可以以 1 开始,此时⽗节点下标为 index / 2,左孩⼦下标为 2 * index,右孩⼦下标为2 * index + 1。
有⼀个物理数组下标从 0 - 8 树结构为:⼆、heapInsert 当数组中 0 ~ index -1 的位置已经是⼤根堆,现在添加⼀个元素到下标为 index ,需要怎么做才能继续保持⼤根堆的结构呢? 1. 将新增元素index 与⽗节点 ( index-1 ) / 2 ⽐较,若⽐⽗节点⼤,则与⽗节点交换位置; 2. 交换位置后,新增元素下标变为⽗节点的下标,再与现在这个节点的⽗节点⽐较,周⽽复始; 3. 直⾄新增节点不再⽐⽗节点⼤或者已经到达了根结点,则新增节点的插⼊位置确定 例⼦:现在有⼀个已经在 0 ~ 7 形成⼤根堆的数组 [ 24, 18, 20, 10, 9, 17, 8, 5 ] ,在下标为 8 的位置插⼊元素 22. JAVA 实现:public static void heapInsert(int[] arr, int index) {// 停⽌条件1:新增结点不再⽐⽗节点⼤// 停⽌条件2:已经到达了整棵树的根结点 0 ,当 index = 0,( 0-1)/2 =0,所以arr[index] 和 arr[(index - 1) / 2] 相等while (arr[index] > arr[(index - 1) / 2]) {swap(arr, index, (index - 1) / 2);index = (index - 1) / 2;}}public static void swap(int[] arr, int i, int j) {int temp = arr[i];arr[i] = arr[j];arr[j] = temp;}三、heapify 当将最⼤值 pop 出去之后,需要对这个堆进⾏调整,最常⽤的就是,将堆结构中最后⼀个的数提到 0 下标,然后将这个数从 0 开始下沉。
堆数据结构的特点及适用场景

堆数据结构的特点及适用场景堆(Heap)是一种特殊的树形数据结构,常被用来实现优先队列。
堆分为最大堆和最小堆两种类型,最大堆中父节点的值大于或等于任何一个子节点的值,最小堆中父节点的值小于或等于任何一个子节点的值。
堆的特点包括以下几个方面:1. **完全二叉树结构**:堆通常是一棵完全二叉树,即除了最底层,其他层的节点都被填满,最底层的节点都尽量靠左排列。
2. **堆序性质**:在堆中,父节点的值要么大于等于(最大堆)或小于等于(最小堆)子节点的值,这种性质保证了堆顶元素是整个堆中的最大或最小值。
3. **高效的插入和删除操作**:堆的插入和删除操作的时间复杂度为O(log n),其中n为堆中元素的个数。
这是由于堆的结构特点和堆序性质的保证。
4. **不支持快速查找**:堆并不支持快速查找操作,如果需要查找特定元素,需要遍历整个堆,时间复杂度为O(n)。
堆适用于以下场景:1. **优先队列**:堆常被用来实现优先队列,可以快速找到最大或最小值的元素。
在任务调度、事件处理等场景中,优先队列的应用十分广泛。
2. **堆排序**:堆排序是一种高效的排序算法,利用堆的特性进行排序,时间复杂度为O(n log n),且是原地排序算法。
3. **求Top K 问题**:在海量数据中,需要找到最大或最小的K 个元素时,可以使用堆来解决。
维护一个大小为K的堆,遍历数据并将元素与堆顶比较,保证堆中的元素始终是Top K。
4. **Dijkstra算法**:Dijkstra算法是一种用于计算图中单源最短路径的算法,堆可以用来实现Dijkstra算法中的优先队列,保证每次选择距离最短的节点进行扩展。
总之,堆数据结构由于其高效的插入和删除操作以及适用于优先队列、堆排序、Top K 问题和Dijkstra算法等场景,被广泛应用于计算机科学领域中。
熟练掌握堆的特点和应用场景,有助于提高算法设计和实现的效率,是每位计算机专业人士值得深入学习和掌握的数据结构之一。
堆数据结构说明

堆数据结构说明数据结构是计算机科学中非常重要的概念,它用于组织和存储数据,以便能够高效地对其进行操作和访问。
堆(Heap)是一种常用的数据结构,主要用于实现优先队列和排序算法。
本文将详细介绍堆的定义、性质以及常见操作等内容。
一、堆的定义和性质堆是一种完全二叉树,其每个节点的值都大于等于(或小于等于)其子节点的值。
在堆中,根节点存放着堆中的最大值(或最小值),并且每个子树都是一个堆。
堆可以分为两种类型:最大堆和最小堆。
最大堆中,堆中的任意节点的值都大于等于其子节点的值;而在最小堆中,任意节点的值都小于等于其子节点的值。
二、堆的实现方式在计算机中,我们通常使用数组或者链表来实现堆。
这里以数组实现最大堆为例进行说明。
1. 堆的表示我们可以使用一个数组来表示堆。
对于一个节点的索引 i,其父节点的索引为 i/2,左子节点的索引为 2i,右子节点的索引为 2i+1。
2. 堆的初始化初始化堆时,我们需要给定一个数组,并从数组的最后一个非叶子节点开始进行调整。
调整的过程包括比较节点与其子节点的大小关系,如果不满足堆的性质,则进行交换。
3. 插入操作节点的插入是将新节点添加到堆的末尾,然后根据堆的性质,逐级将新节点与其父节点进行比较和交换,直到满足堆的性质为止。
4. 删除操作节点的删除操作通常是删除堆中的根节点,然后将堆的最后一个节点放到根节点的位置,再根据堆的性质,逐级将新的根节点与其子节点进行比较和交换,直到满足堆的性质为止。
三、堆的应用堆作为一种重要的数据结构,具有广泛的应用。
1. 优先队列堆可以用来实现优先队列,其中每个元素都有一个与之关联的优先级。
在优先队列中,我们可以高效地插入新元素和删除当前优先级最高的元素。
2. 排序算法堆排序是一种常用的排序算法,它利用最大堆进行排序。
堆排序的基本思想是将待排序序列构建成最大堆,然后将堆顶元素与末尾元素交换,并调整堆,重复这个过程,直到整个序列有序。
3. 数据流的中位数在处理数据流时,我们经常需要快速获取中位数。
左倾堆(对两个优先队列合并)

左倾堆(对两个优先队列合并)⼀、左倾堆的介绍左倾堆(leftist tree 或 leftist heap),⼜被成为左偏树、左偏堆,最左堆等。
它和⼆叉堆⼀样,都是优先队列实现⽅式。
当优先队列中涉及到"对两个优先队列进⾏合并"的问题时,⼆叉堆的效率就⽆法令⼈满意了,⽽本⽂介绍的左倾堆,则可以很好地解决这类问题。
左倾堆的定义上图是⼀颗左倾树,它的节点除了和⼆叉树的节点⼀样具有左右⼦树指针外,还有两个属性:键值和零距离。
(01) 键值的作⽤是来⽐较节点的⼤⼩,从⽽对节点进⾏排序。
(02) 零距离(英⽂名NPL,即Null Path Length)则是从⼀个节点到⼀个"最近的不满节点"的路径长度。
不满节点是指该该节点的左右孩⼦⾄少有有⼀个为NULL。
叶节点的NPL为0,NULL节点的NPL为-1。
左倾堆有以下⼏个基本性质:[性质1] 节点的键值⼩于或等于它的左右⼦节点的键值。
[性质2] 节点的左孩⼦的NPL >= 右孩⼦的NPL。
[性质3] 节点的NPL = 它的右孩⼦的NPL + 1。
⼆、左倾堆的图⽂解析合并操作是左倾堆的重点。
合并两个左倾堆的基本思想如下:(01) 如果⼀个空左倾堆与⼀个⾮空左倾堆合并,返回⾮空左倾堆。
(02) 如果两个左倾堆都⾮空,那么⽐较两个根节点,取较⼩堆的根节点为新的根节点。
将"较⼩堆的根节点的右孩⼦"和"较⼤堆"进⾏合并。
(03) 如果新堆的右孩⼦的NPL > 左孩⼦的NPL,则交换左右孩⼦。
(04) 设置新堆的根节点的NPL = 右⼦堆NPL + 1下⾯通过图⽂演⽰合并以下两个堆的过程。
提⽰:这两个堆的合并过程和测试程序相对应!第1步:将"较⼩堆(根为10)的右孩⼦"和"较⼤堆(根为11)"进⾏合并。
合并的结果,相当于将"较⼤堆"设置"较⼩堆"的右孩⼦,如下图所⽰:第2步:将上⼀步得到的"根11的右⼦树"和"根为12的树"进⾏合并,得到的结果如下:第3步:将上⼀步得到的"根12的右⼦树"和"根为13的树"进⾏合并,得到的结果如下:第4步:将上⼀步得到的"根13的右⼦树"和"根为16的树"进⾏合并,得到的结果如下:第5步:将上⼀步得到的"根16的右⼦树"和"根为23的树"进⾏合并,得到的结果如下:⾄此,已经成功的将两棵树合并成为⼀棵树了。
堆排序详细图解

堆排序1:堆什么是堆呢?这里,必须引入一个完全二叉树的概念,然后过渡到堆的概念。
上图,就是一个完全二叉树,其特点在于:1.从作为第一层的根开始,除了最后一层之外,第N层的元素个数都必须是2的N次方;第一层2个元素,第二层4个,第三层8个,以此类推。
2.而最后一行的元素,都要紧贴在左边,换句话说,每一行的元素都从最左边开始安放,两个元素之间不能有空闲,具备了这两个特点的树,就是一棵完全二叉树。
那么,完全二叉树与堆有什么关系呢?我们假设有一棵完全二叉树,在满足作为完全二叉树的基础上,对于任意一个拥有父节点的子节点,其数值均不小于父节点的值;这样层层递推,就是根节点的值最小,这样的树,称为小根堆。
同理,又有一棵完全二叉树,对于任意一个子节点来说,均不大于其父节点的值,如此递推,就是根节点的值是最大的,这样的数,称为大根堆。
如上图,左边就是大根堆;右边则是小根堆,这里必须要注意一点,只要求子节点与父节点的关系,两个节点的大小关系与其左右位置没有任何关系。
明确下大根堆,小根堆的概念,继续说堆排序。
现在对于堆排序来说,我们先要做的是,把待排序的一堆无序的数,整理成一个大根堆,或者小根堆,下面讨论以大根堆为例子。
给定一个列表array=[16,7,3,20,17,8],对其进行堆排序(使用大根堆)。
步骤一构造初始堆。
将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。
a.假设给定无序序列结构如下2.此时我们从最后一个非叶子结点开始(叶结点自然不用调整,第一个非叶子结点arr.length/2-1=5/2-1=1,也就是下面的6结点),从左至右,从下至上进行调整。
此处必须注意,我们把6和9比较交换之后,必须考量9这个节点对于其子节点会不会产生任何影响?因为其是叶子节点,所以不加考虑;但是,一定要熟练这种思维,写代码的时候就比较容易理解为什么会出现一次非常重要的交换了。
4.找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换。
堆的原理和应用

堆的原理和应用1. 堆的定义和特点堆(Heap)是一种特殊的数据结构,它是一种完全二叉树,并且满足堆特性:对于最大堆,父节点的值大于或等于子节点的值;对于最小堆,父节点的值小于或等于子节点的值。
堆最常见的应用就是优先队列,能够高效地找到最大或最小元素。
堆具有以下特点: - 堆是一棵完全二叉树,节点顺序从上到下、从左到右; - 最大堆(或最小堆)的父节点的值大于等于(或小于等于)子节点的值; - 堆的根节点是整个堆中最大(或最小)的元素。
2. 堆的实现和操作堆可以使用数组来实现,通过满足以下规则: - 对于节点i,其左子节点的索引是2i+1,右子节点的索引是2i+2; - 对于节点i,其父节点的索引是(i-1)/2。
常用的堆操作包括插入元素、删除堆顶元素、堆元素的上浮和下沉。
•插入元素:将元素插入到堆的尾部,然后依次与父节点进行比较,若满足堆特性,则停止比较;否则继续交换位置。
•删除堆顶元素:将堆的尾部元素替换到堆顶,然后依次与子节点进行比较,交换位置直到满足堆特性。
•堆元素的上浮:将该元素与父节点进行比较,若满足堆特性,则停止比较;否则继续交换位置。
•堆元素的下沉:将该元素与子节点进行比较,交换位置直到满足堆特性。
3. 优先队列的实现优先队列是堆的一种常见应用,它能够高效地找到最大(或最小)元素。
优先队列可以支持插入操作和获取最大(或最小)元素操作。
使用堆实现优先队列的步骤如下: 1. 创建一个空的堆作为优先队列。
2. 将元素依次插入到堆中。
3. 获取堆顶元素并删除。
4. 执行上述操作,直到堆为空。
优先队列的应用非常广泛,例如任务调度、数据压缩、图像处理等领域。
4. 堆排序算法堆排序是一种基于堆的排序算法,它可以在O(nlogn)的时间复杂度下完成排序操作。
堆排序的基本思想是: 1. 将待排序的序列构建成一个最大堆。
2. 此时,整个序列的最大值就是堆顶的根节点。
3. 将根节点与最后一个节点交换,然后对前面n-1个节点进行堆调整。
堆的物理存储结构

堆的物理存储结构堆(Heap)是一种基于数组的完全二叉树数据结构,其中每个节点的值都大于等于或小于等于其子节点。
堆有两种类型:最大堆(Max Heap)和最小堆(Min Heap)。
最大堆中,每个父节点的值都大于或等于其子节点的值;最小堆中,每个父节点的值都小于或等于其子节点的值。
在堆中,根节点通常是最大值(最大堆)或最小值(最小堆)。
堆的物理存储结构在实现上通常使用数组来表示。
堆的节点可以按照数组的下标来访问,具体的关系可以通过下标计算得到。
堆的节点索引从1开始,对应于数组中的索引为0的位置。
这样,给定节点i(i>0),其父节点可以表示为i/2,左子节点可以表示为2i,右子节点可以表示为2i+1、在堆中,节点的排列顺序是按照层次进行的。
堆的物理存储结构的主要优势是易于实现和维护。
由于堆的特性,可以通过简单的数学计算来确定节点之间的关系,而不需要使用指针等其他数据结构来表示节点之间的链接。
在实现堆的操作(如插入和删除)时,可以直接对数组进行操作,而不需要像其他数据结构那样涉及指针的移动。
堆的物理存储结构中的数组通常具有固定大小,即在创建堆时就需要指定数组的大小。
当堆的容量不足时,需要进行重新分配和复制来扩展数组的大小,这可能会导致性能上的开销。
因此,在设计堆的物理存储结构时,需要合理估计堆的容量,以避免频繁的重新分配。
堆的物理存储结构的一个常见实现是使用连续的内存块来表示数组。
在这种情况下,可以使用静态数组或动态数组来实现堆。
静态数组在创建时就分配了固定大小的空间,而动态数组可以根据需要动态调整大小。
使用连续内存块的优点是访问速度快,因为可以直接通过索引进行访问。
然而,缺点是无法动态地增加或减少堆的大小。
另一个实现堆的方式是使用链表来表示节点之间的链接。
在这种情况下,每个节点都是一个独立的对象,包含一个值和指向其父节点、左子节点和右子节点的指针。
使用链表的优点是可以动态地增加或减少堆的大小,因为可以通过创建新的节点对象来扩展堆的容量。
堆的原理数据结构

堆的原理数据结构堆是一种常用的数据结构,常用于实现优先队列(priority queue)等应用。
它是一棵完全二叉树,树中的每个节点都满足堆的性质。
堆分为最大堆和最小堆两种类型。
最大堆的性质是:对于每个节点i,节点i的值大于等于其子节点的值;最小堆的性质是:对于每个节点i,节点i的值小于等于其子节点的值。
在堆中,根节点的元素具有最大或者最小值。
堆的实现可以使用数组或者链表。
在数组实现中,对于节点i,其左子节点的索引为2i+1,其右子节点的索引为2i+2;在链表实现中,每个节点有指向其子节点的指针。
堆的操作包括插入、删除堆顶元素、堆化等。
在插入操作中,需要保持堆的性质不变。
首先将要插入的元素放在堆的最后一个位置,并且将其与其父节点进行比较。
如果插入的元素大于父节点的值(最大堆)或者小于父节点的值(最小堆),则交换插入的元素和父节点的值,并且将索引指向父节点,继续进行比较,直到满足堆的性质为止。
在删除堆顶元素操作中,首先将堆顶元素与堆的最后一个元素进行交换,然后删除最后一个元素。
之后,从堆顶元素开始,将其与其子节点进行比较,如果堆顶元素小于其子节点的值(最大堆)或者大于其子节点的值(最小堆),则交换堆顶元素和较大(小)的子节点,并且将索引指向较大(小)的子节点,继续进行比较,直到满足堆的性质为止。
堆化操作用于构建一个堆。
可以通过两种方法进行堆化:自上而下和自下而上。
自上而下的堆化操作从堆的根节点开始,依次将其与其子节点进行比较,如果不满足堆的性质,则进行交换并继续向下进行比较。
自下而上的堆化操作从堆的最后一个非叶子节点开始,依次将其与其子节点进行比较,如果不满足堆的性质,则进行交换并继续向上进行比较。
堆的时间复杂度与树的高度相关,插入和删除堆顶元素操作的时间复杂度为O(log n),其中n是堆中元素的个数。
堆化的时间复杂度为O(n)。
堆作为一种常用的数据结构,在很多应用中都有着重要的作用。
例如,在操作系统的调度算法中,可以使用堆来实现对进程的调度;在图算法中,可以使用堆来实现最短路径算法;在模拟系统中,可以使用堆来实现事件驱动模拟等。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
ee is biased to get deep toward the left.
0
§6 Leftist Heap
【Theorem】: a leftist tree with r nodes on the right path must have at least 2r-1 nodes. Induction:
§7 Skew Heaps
-- a simple version of the leftist heaps
Target : Any M consecutive operations take at
most O(M log N) time.
Merge: Always swap the left and right children except that the
Home work:
p.183 5.24 Insertions
This tree has at least 2r-1 nodes
root
r+1 nodeThis on the right tree has path at least 2r-1 nodes
left subtree
right subtree
r node on the right path right subtree right subtree
12
24 33
26
26
3 6 7 24 37 33 18 8 18 17 26 7 37 18 33 12 24 21 23 10 14
Merge (iterative version):
3 10 21 23 14 26 8 17 18 12 6
H1
H2
§7 Skew Heaps
Note:
Skew heaps have the advantage that no extra space is required to maintain path lengths and no tests are required to determine when to swap children. It is an open problem to determine precisely the expected right path length of both leftist and skew heaps.
Step 2: Swap children if necessary
3 18 6 7 21 23 10 14
Home work: 12
5.16 18 24 8 37 33 heaps 17 18 two given leftist Step 1: Sort Merge the right paths without
at least r node on the right path
§6 Leftist Heaps
Discussion 6: How long is the right path of a leftist tree of N nodes? What does this conclusion mean to us?
Note: Npl(X) = min { Npl(C) + 1 for all C as children of X } 【Definition】The leftist heap property is that for every node
X in the heap, the null path length of the left child is at least as large as that of the right child. 1 1 0 0 0 0 1
Merge1 („6‟, „ 8‟)
7 37 24 8 17
7 37 18
18
Merge („7‟, „ 8‟) Merge1 („7‟, „ 8‟)
33 26
§6 Leftist Heaps
Merge (iterative version):
3 10 21 23 14 26 8 17 18 33 12 24 37 6 7
Trouble makers: Insert and Merge
Note: Insertion is merely a special case of merging.
§6 Leftist Heaps
Declaration:
struct TreeNode { ElementType PriorityQueue PriorityQueue int };
Order Property – the same Structure Property – binary tree, but unbalanced
§6 Leftist Heaps
【Definition】The null path length, Npl(X), of any node X is
the length of the shortest path from X to a node without two children. Define Npl(NULL) = –1.
Merge (recursive version):
3 10 21 23 14 26 8 17 18 33 12 24 37 6 7
37 18
Step 3:
H1
H2
Swap(H1->Right, H1->Left ) if necessary
§6 Leftist Heaps
PriorityQueue Merge ( PriorityQueue H1, PriorityQueue H2 ) { if ( H1 == NULL ) return H2; if ( H2 == NULL ) return H1; if ( H1->Element < H2->Element ) return Merge1( H1, H2 ); else return Merge1( H2, H1 ); } static PriorityQueue Merge1( PriorityQueue H1, PriorityQueue H2 ) { if ( H1->Left == NULL ) /* single node */ H1->Left = H2; /* H1->Right is already NULL and H1->Npl is already 0 */ else { H1->Right = Merge( H1->Right, H2 ); /* Step 1 & 2 */ if ( H1->Left->Npl < H1->Right->Npl ) SwapChildren( H1 ); /* Step 3 */ H1->Npl = H1->Right->Npl + 1; } /* end else */ return H1; } T = O(log N)
Step 1:
Merge( H1->Right, H2 )
12
6 7 24 33 8 17 26 18 37
Element; Left; Right; Npl;
18
Step 2:
Attach( H2, H1->Right )
3 10 21 14 23 18 18 33 12 24 8 17 26 6 7
Merge („8‟, „ 6‟)
„8‟->Right = Merge(„NULL‟, „ 18‟)
Merge1 („8‟, „ 18‟) Merge (NULL, „ 18‟)
Return ‟1 8‟
„3‟->Right = Merge(„8‟, „ 6‟)
6 12
„6‟->Right = Merge(„7‟, „ 8‟)
H1
changing their left children
3 10 21 23 14 18 33 26 12 24 37 6
p.181 H2
p.183 5.22
26
DeleteMin: BuildHeap in O(N)
7 8 17 18
Step 1: Delete the root
Step 2: Merge the two
subtrees
Tp = O(log N)
§6 Leftist Heap
Insertion routine
PriorityQueue Insert1( ElementType X, PriorityQueue H) { PriorityQueue SingleNode; SingleNode = malloc(sizeof(struct TreeNode)); if (SingleNode == NULL) FatalError(“ out of space!!!”); else { SingleNode->Element = X; SingleNode->Npl = 0; SingleNode->left = SingleNode->Right = NULL; H = Merge(SingleNode, H); } return H; }
largest of all the nodes on the right paths does not have its children swapped. No Npl.
3 10 21 23 14 26 8 17 18 33 12 24 37 6 7 18 8
Not really a special 6 but a natural 10 case, 7stop in the 12 recursions. 21 14