最小堆与最大堆

最大堆和最小堆是二叉堆的两种形式。

最大堆:根结点的键值是所有堆结点键值中最大者,且每个结点的值都比其孩子的值大。

最小堆:根结点的键值是所有堆结点键值中最小者,且每个结点的值都比其孩子的值小。




最小堆和最大堆的增删改相似,其实就是把算法中的大于改为小于,把小于改为大于。

生成最大堆:最大堆通常都是一棵完全二叉树,因此我们使用数组的形式来存储最大堆的值,从1号单元开始存储,因此父结点跟子结点的关系就是两倍的关系。

即:heap[father * 2] = heap[leftChild];  heap[father * 2 + 1] = heap[rightChild];


最大堆的初始化

在生成最大堆时,我们可以采取一次次遍历整棵树找到最大的结点放到相应的位置中。

但是这种方法有个不好的地方,就是每个结点都要重复比较多次。

所以我们可以换一种思考的顺序,从下往上进行比较。先找到最后一个有子结点的结点,先让他的两个子结点进行比较,找到大的值再和父结点的值进行比较。如果父结点的

值小,则子结点和父结点进行交换,交换之后再往下比较。然后一步步递归上去,知道所在结点的位置是0号位置跳出。这样就可以有效地减少比较所用到的时间。



但是每一次交换都要重复三步:heap[i] = temp; heap[i] = heap[j]; heap[j] = temp;

当树的结构比较大的时候,就会浪费很多时间。

所以我们可以先把父结点的值存到temp中跟子结点比较,如果子结点大的话就把子结点的值赋值给父结点,再往下比较,最后才把temp的值存到相应的位置。



  1. void Initialize(T a[], int size, int ArraySize)  
  2. {  
  3.     delete []heap;  
  4.     isExist = false;  
  5.     heap = a;  
  6.     CurrentSize = size;  
  7.     MaxSize = ArraySize;  
  8.     //产生一个最大堆  
  9.     for(int i = CurrentSize / 2; i >= 1; i --)  
  10.     {  
  11.         T y = heap[i];          //子树的根  
  12.         //寻找放置y的位置  
  13.         int c = 2 * i; //c的父节点是y的目标位置  
  14.         while(c <= CurrentSize)  
  15.         {  
  16.             //heap[c]应是较大的同胞节点  
  17.             if(c < CurrentSize && heap[c] < heap[c + 1])  
  18.                 c ++;  
  19.             //能把y放入heap[c / 2]吗?  
  20.             if(y >= heap[c])  
  21.                 break;          //能               
  22.             else            //不能  
  23.             {  
  24.                 heap[c / 2] = heap[c];      //将孩子上移  
  25.                 c *= 2;  
  26.             }           //下移一层  
  27.         }  
  28.         heap[c / 2] = y;  
  29.     }  
  30. }  


最大堆的插入

最大堆的插入的思想就是先在最后的结点添加一个元素,然后沿着树上升。跟最大堆的初始化大致相同。

  1. MaxHeap<T> &Insert(const T&x)  
  2.     {     
  3.         if(CurrentSize == MaxSize)  
  4.             exit(1);        //没有足够空间  
  5.   
  6.         //为x寻找应插入位置  
  7.         //i从新的叶节点开始,并沿着树上升  
  8.         int i = ++ CurrentSize;  
  9.         while(i != 1 && x > heap[i/2])  
  10.         {  
  11.             //不把x放进heap[i]  
  12.             heap[i] = heap[i/2];        //元素下移  
  13.             i /= 2;     //移向父节点  
  14.         }  
  15.         heap[i] = x;        //这时候才把x放进去  
  16.         return *this;  
  17.     }  

最大堆的删除

最大堆的删除,即删除最大的元素。我们先取最后的元素提到根结点,然后删除最大值,然后再把新的根节点放到合适的位置。

  1. MaxHeap<T> &DeleteMax(T &x)  
  2.     {  
  3.         //检查堆是否为空  
  4.         if(CurrentSize == 0)  
  5.             exit(1);        //队列空  
  6.   
  7.         x = heap[1];        //最大元素  
  8.         T y = heap[CurrentSize--];      //最后一个元素  
  9.         //从根开始,重构大堆  
  10.         int i = 1, ci = 2;      //ci为i的儿子  
  11.         while(ci < CurrentSize)  
  12.         {  
  13.             if(ci < CurrentSize && heap[ci] < heap[ci + 1])           //比较两个子节点大小,取其大  
  14.                 ci ++;  
  15.             //能否放y  
  16.             if(heap[ci] > y)     //不能  
  17.             {  
  18.                 heap[i] = heap[ci];     //孩子上移  
  19.                 i = ci;                 //下移一层  
  20.                 ci *= 2;  
  21.             }  
  22.             else            //能  
  23.                 break;  
  24.         }  
  25.         heap[i] = y;  
  26.         return *this;  
  27.     }  
<think>好的,我现在需要解答用户关于最小最大堆的问题。首先,我得回忆一下的基本概念,然后区分最小最大堆的不同之处,以及它们的应用场景。用户还提到了数据结构中的,所以需要结合这些知识点来组织回答。 首先,是一种完全二叉树,这一点我记得很清楚。完全二叉树的特点是除了最后一层,其他层都是满的,且最后一层的节点都靠左排列。这个结构特性使得可以用数组来高效地表示,而不需要指针,这样节省了空间,同时访问效率也高。 接下来,最小最大堆的定义。最小的每个父节点的值都小于或等于其子节点,所以根节点是整个中的最小值。相反,最大堆的父节点值都大于或等于子节点,根节点是最大值。这两个结构的主要区别就在于节点的排列顺序不同,这影响了它们在不同场景下的应用。 然后,我需要思考它们的区别。除了定义上的不同,操作的时间复杂度也是关键点。插入和删除操作的时间复杂度都是$O(\log n)$,因为需要维护的结构。而获取极值(最小或最大)是$O(1)$时间,这对于需要频繁访问极值的场景非常有用。 应用场景方面,最小常用于需要快速获取当前最小元素的场合,比如优先队列、Dijkstra算法和哈夫曼编码。而最大堆则用于需要最大元素的场景,比如排序、任务调度系统。这里可能需要结合用户提供的引用内容,比如引用[4]提到的Top-K问题,最小可以用来维护前K个最大的元素,而最大堆可能用于维护前K个最小的元素?或者需要再确认一下。比如,Top-K最大的元素通常用最小,因为顶是K个中的最小值,可以快速替换掉更小的元素。而Top-K最小的则用最大堆顶是最大的那个,可以替换更大的元素。 还有引用[5]提到优先队列,是实现优先队列的常用结构,最小实现最小优先队列,最大堆反之。另外,排序使用最大堆来逐步将最大元素放到数组末尾。 用户提供的引用中有一些相关知识点,比如引用[1]提到用最小解决Top-K问题,当大小超过K时删除顶,这样里保留的就是最大的K个元素,顶是第K大的。而引用[4]也提到解决Top-K问题的高效性,避免全部排序。需要把这些引用内容自然地融入到回答中,作为例子或引用支持。 接下来,我需要考虑如何组织内容,确保结构清晰。可能的结构是: 1. 定义结构特性 - 完全二叉树、数组表示 - 最小最大堆的定义 2. 核心区别 - 节点顺序 - 根节点值 - 应用方向不同 3. 操作复杂度 - 插入、删除的时间复杂度 - 获取极值的复杂度 4. 应用场景 - 最小的应用:优先队列、Dijkstra、哈夫曼、Top-K最大 - 最大堆的应用:排序、任务调度、Top-K最小 5. 代码示例(可选) - 可能用Python实现最小最大堆的基本操作,或者引用用户提供的代码示例中的内容。比如引用[2]提到了实现最小结构,可以提及。 需要注意用户提到的引用内容,例如引用[1]中的构建最小解决Top-K问题,引用[5]中的优先队列应用,这些都需要在相应的应用场景部分提到,并添加引用标识。 可能的问题点:在Top-K问题中,使用最小还是最大堆?例如,找最大的K个元素,通常使用最小,因为当新元素比顶大时,替换顶并调整,这样中始终保持最大的K个元素,而顶是其中最小的,即第K大的元素。而如果使用最大堆顶是最大的元素,但无法直接知道第K大的值,所以可能不太适用。因此需要明确说明这点。 另外,在Java中的内存和数据结构的区别,如引用[3]所提,但用户的问题可能不需要涉及这部分,所以可能不需要展开,但如果有必要可以简要提及,但重点在数据结构的。 现在,需要确保回答符合用户的要求:结构清晰,分步解释,真实可靠,使用中文,正确使用LaTeX格式,引用相关部分。并且生成相关问题。 最后,生成的问题需要主题相关,比如的实现、应用场景的比较、复杂度分析等。</think>### 最小最大堆的详解 #### 一、定义结构特性 是一种**完全二叉树**,可以用数组高效表示。其核心特性分为两类: 1. **最小**:每个父节点的值 $\leq$ 其子节点值,根节点为全局最小值。 2. **最大堆**:每个父节点的值 $\geq$ 其子节点值,根节点为全局最大值。 例如,最小的数组表示满足: $$ \text{对于任意位置 }i,\quad arr[i] \leq arr[2i+1] \text{ 且 } arr[i] \leq arr[2i+2] $$ 最大堆则反之。 #### 二、核心区别 | 特性 | 最小 | 最大堆 | |--------------|---------------------------|---------------------------| | **节点顺序** | 父节点 ≤ 子节点 | 父节点 ≥ 子节点 | | **根节点值** | 全局最小值 | 全局最大值 | | **应用方向** | 快速取最小、过滤较小值 | 快速取最大、过滤较大值 | #### 三、操作复杂度 - **插入/删除**:$O(\log n)$(需调整结构) - **获取极值**:$O(1)$(直接取根节点) #### 四、应用场景 1. **最小** - **优先队列**:任务调度中优先处理耗时短的任务[^5]。 - **Dijkstra算法**:高效获取当前最短路径节点。 - **Top-K最大元素**:维护容量为$K$的最小顶即第$K$大的元素(例如引用[1]中的解法)。 - **哈夫曼编码**:合并频率最小的节点。 2. **最大堆** - **排序**:反复取最大值并调整。 - **任务调度**:优先分配资源给优先级高的任务。 - **Top-K最小元素**:维护容量为$K$的最大堆顶即第$K$小的元素[^4]。 #### 五、代码示例(最小插入) ```python def heapify(arr, n, i): smallest = i left = 2 * i + 1 right = 2 * i + 2 if left < n and arr[left] < arr[smallest]: smallest = left if right < n and arr[right] < arr[smallest]: smallest = right if smallest != i: arr[i], arr[smallest] = arr[smallest], arr[i] heapify(arr, n, smallest) def insert_min_heap(arr, val): arr.append(val) i = len(arr) - 1 while i > 0 and arr[(i-1)//2] > arr[i]: arr[(i-1)//2], arr[i] = arr[i], arr[(i-1)//2] i = (i-1) // 2 ``` --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值