堆and优先队列

优先队列 是一种抽象的数据类型,而 是一种数据结构。

是实现 优先队列 的一种方式。

实现 优先队列 的方式有很多种,比如数组和链表。

但是,这些实现方式只能保证插入操作和删除操作中的一种操作可以在 O(1)的时间复杂度内完成,而另外一个操作则需要在 O(N) 的时间复杂度内完成。

能够使 优先队列 的插入操作在 O(logN) 的时间复杂度内完成,删除操作在 O(logN)的时间复杂度内完成。


是一种特别的二叉树,满足以下条件的二叉树,可以称之为

    1.完全二叉树
    2. 每一个节点的值都必须 >=或者<= 其孩子节点的值。

 虽然所有节点都比孩子节点的值小,但它不是一个完全二叉树。

具有以下的特点:

    1.可以在 O(logN)的时间复杂度内向 中插入元素;
    2.可以在 O(logN)的时间复杂度内向 中删除元素;
    3.可以在 O(1)的时间复杂度内获取 中的最大值或最小值。

有两种类型:大根堆小根堆

大根堆:堆中每一个节点的值 都大于等于 其孩子节点的值。堆顶元素(根节点)是堆中的最大值。

小根堆:堆中每一个节点的值 都小于等于 其孩子节点的值。堆顶元素(根节点)是堆中的最小值。


插入操作 是指向 中插入一个元素。元素插入之后, 依旧需要维持它的特性。

删除操作 是指在 中删除堆顶元素。元素删除之后, 依旧需要维持它的特性。

在堆的数据结构中,常用堆的插入删除获取堆顶元素的操作。

可以用数组实现堆,将堆中的元素以二叉树的形式存入在数组中。 

在大部分编程语言中,都已经有内置方法实现它。在实际解题或者工作中,一般很少需要自己去实现堆。


堆的常用方法:

创建 指的是初始化一个堆实例。所有堆方法的前提必须是在堆实例上进行操作。

堆化 就是将一组数据变成 的过程。

C++的STL提供了make_heap()、push_heap、pop_heap、sort_heap等算法,它们用来将一个随机存储的数组或者容器等转换为一个heap。

构造:make_heap 的功能是将一段现有的数据转化成一个堆,默认情况下生成的是一个大堆。想实现小堆转化时,首先要#include <functional>,然后参数中使用great<int>()。需要注意的是当make_heap中使用了greater()后,后面pop_heap、push_heap和sort_heap都需要加上greater()。

插入:push_heap 的功能是往堆中插入一个数据。STL库提供的push_heap算法没有插入元素,仅仅是完成插入元素后的调整工作,将插入元素后的区间恢复成堆结构。需要注意,当一次性插入多个数据然后再调用push_heap进行调整的时候,除最后插入的数据之外的其他前面插入的数据必须保证和已有数据依然能够构成堆结构,因为push_heap只是对左闭右开区间的最后一个数据进行调整,并且认为前面的数据已经满足堆结构,所以如何前面插入的数据不能保证堆结构的话就会报错。

删除:pop_heap 的功能也是实现调整工作,但是是删除前的调整。pop_head会将堆顶的元素与最后一个元素(注意最后一个元素是last的上一个元素)交换,然后再调整,将除了最后一个元素的剩余其他元素重新恢复成堆结构。需要注意的是每次pop一个元素后,下一次pop时就需要将区间缩小1(last减1)。

排序:sort_heap 的功能就相当于多次调用pop_heap,每次调用pop_heap都将堆顶元素与最后元素进行交换(将堆中最大元素与堆中最后一个元素交换),然后last减1,重复调用heap_pop一直到只剩下1个元素,就实现了将元素排序。

//构造一个堆(最大堆)
int myints[] = {10,20,30,5,15};
std::vector<int> v(myints,myints+5);

//获取堆顶元素
std::make_heap (v.begin(),v.end());
std::cout << "initial max heap   : " << v.front() << '\n'; 

//插入元素
v.push_back(99); std::push_heap (v.begin(),v.end());
std::cout << "max heap after push: " << v.front() << '\n'; 

//删除元素
std::pop_heap (v.begin(),v.end()); v.pop_back();
std::cout << "max heap after pop : " << v.front() << '\n';  

//获取堆的长度
v.size();

堆的应用:

1.优先队列:

首先要包含头文件 #include<queue>, 他和queue不同的就在于可以自定义其中数据的优先级, 让优先级高的排在队列前面,优先出队。

优先队列具有队列的所有特性,包括队列的基本操作(

  • top 访问队头元素   队列取队首元素用的是front函数,而优先队列用的是top函数.
  • empty 队列是否为空
  • size 返回队列内元素个数
  • push 插入元素到队尾 (并排序)
  • emplace 原地构造一个元素并插入队列
  • pop 弹出队头元素
  • swap 交换内容

),只是在这基础上添加了内部的一个排序,它本质是一个堆实现的。

priority_queue<Type(数据类型), Container(数组实现的容器), Functional(比较函数)>;

//greater和less是std实现的两个仿函数
(就是使一个类的使用看上去像一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了)

priority_queue<int, vector<int>, greater<int>(小顶堆)>q;

priority_queue<int, vector<int>, less<int>(大顶堆)>q;

priority_queue<int>a;默认大顶堆

priority_queue<pair<int,int>>a;

2.堆排序:利用的数据结构对一组无序元素进行排序。

小根堆 排序算法步骤如下:

  1.     将所有元素堆化成一个 小根堆 ;
  2.     取出并删除堆顶元素,并将该堆顶元素放置在存储有序元素的数据集 T 中;
  3.     此时,堆 会调整成新的 最小堆;
  4.     重复 3 和 4 步骤,直到 堆 中没有元素;
  5.     此时得到一个新的数据集 T,其中的元素按照 从小到大 的顺序排列。

最大堆 排序算法步骤如下:

  1.     将所有元素堆化成一个 最大堆;
  2.     取出并删除堆顶元素,并将该堆顶元素放置在存储有序元素的数据集 T 中;
  3.     此时,堆 会调整成新的 最大堆;
  4.     重复 3 和 4 步骤,直到 堆 中没有元素;
  5.     此时得到一个新的数据集 T,其中的元素按照从大到小的顺序排列。

时间复杂度:O(NlogN)。N是 堆 中的元素个数。

空间复杂度:O(N)。N是 堆 中的元素个数。


用堆解题:

1.Top K 大/小的元素:

解法1:

  1. 创建一个「大/小根堆」;
  2. 将所有元素都加到「大/小根堆」中;
  3. 通过 「边删除边遍历」 方法,将堆顶元素删除,并将它保存到结果集 T 中;
  4. 重复 3 步骤 K 次,直到取出前 K 个最大/小的元素;

时间复杂度: O(KlogN)

空间复杂度:O(N)

解法二:

  1. 创建一个大小为 K 的「小/大根堆」;
  2. 依次将元素添加到「小/大堆」中;
  3. 当「小/大堆」的元素个数达到 K 时,将当前元素与堆顶元素进行对比:如果当前元素小于/大于堆顶元素,则放弃当前元素,继续进行下一个元素;如果当前元素大于/小于堆顶元素,则删除堆顶元素,将当前元素加入到「小/大根堆」中。
  4. 重复步骤 2 和步骤 3,直到所有元素遍历完毕。
  5. 此时「小/大堆」中的 K 个元素就是前 K 个最大/小的元素。

时间复杂度: O(NlogK)

空间复杂度:O(K)

2.The Kth 大/小元素。(同上,最后记录即可)

<think>嗯,用户想了解如何用大根构建优先队列,还希望有解析图。首先,我需要回忆大根优先队列的基本概念。优先队列是一种数据结构,通常用来实现,而大根的特点是父节点的值大于或等于子节点,所以元素最大。那构造大根的过程是怎样的呢?可能需要从最后一个非叶子节点开始调整,确保每个子树都满足大根的性质。 用户提到需要解析图,虽然我不能直接生成图片,但可以用文字描述结构,比如用数组表示,并展示如何调整节点。例如,初始数组可能是一个完全二叉树的结构,然后通过下沉或上浮操作来调整。比如,初始数组是[4, 10, 3, 5, 1],构建大根的过程可能需要从索引1(假设从0开始的话,应该是中间的位置?)开始调整。 然后要考虑插入和删除操作,这两个操作会影响的结构,需要调整以维持大根的性质。插入元素时放在末尾,然后上浮;删除元素时,将末尾元素移到顶,再下沉。这些步骤需要详细解释,可能还要结合代码示例,比如用Python实现。 用户可能还有更深层的需求,比如时间复杂度或者应用场景,但问题主要集中在构造和可视化上。需要确保引用的相关知识点正确,比如引用中的排序和优先队列的资料。可能需要检查引用内容是否相关,比如引用[1]提到排序是对选择排序的改进,引用[2]提到结构用于优先队列,这些都需要正确引用。 最后,生成相关问题时,要覆盖构造过程、操作的时间复杂度、应用场景和与小根的区别,这样用户可以有进一步学习的路径。需要确保回答结构清晰,分步骤解释,并附上代码示例,帮助用户理解实现过程。</think>### 大根构建优先队列的解析与可视化 #### 1. 大根优先队列的关系 优先队列(Priority Queue)是一种抽象数据结构,要求每次取出的元素是队列中的最大值(大根)或最小值(小根)。**大根通过完全二叉树结构维护父节点值≥子节点值的性质**,从而实现高效的最大值操作[^1]。 #### 2. 大根的构造过程(以数组 `[4, 10, 3, 5, 1]` 为例) **步骤1:初始化完全二叉树** ``` 4 / \ 10 3 / \ 5 1 ``` **步骤2:从最后一个非叶子节点开始调整** - **非叶子节点索引计算**:$i = \lfloor n/2 \rfloor - 1 = 1$(从0开始计数) - **调整节点10的子节点5和1**,无需交换 - **调整节点4的子节点10和3**,交换4与10: ``` 10 / \ 4 3 / \ 5 1 ``` **最终大根结构**: ``` 10 / \ 5 3 / \ 4 1 ``` #### 3. 优先队列的核心操作 **插入操作(上浮)**: ```python def insert(heap, val): heap.append(val) i = len(heap) - 1 while i > 0 and heap[(i-1)//2] < heap[i]: # 父节点索引公式 heap[(i-1)//2], heap[i] = heap[i], heap[(i-1)//2] i = (i-1) // 2 ``` **删除最大值(下沉)**: ```python def extract_max(heap): if not heap: return None max_val = heap[0] heap[0] = heap[-1] heap.pop() i, n = 0, len(heap) while 2*i + 1 < n: # 存在左子节点 child = 2*i + 1 if child + 1 < n and heap[child+1] > heap[child]: # 选择较大的子节点 child += 1 if heap[i] >= heap[child]: break heap[i], heap[child] = heap[child], heap[i] i = child return max_val ``` #### 4. 算法可视化要点 1. **颜色标记**:新插入节点标红,调整路径标黄 2. **动画演示**:分步展示节点交换过程 3. **数组与树形同步显示**:如: ``` 数组:[10, 5, 3, 4, 1] 树形: 10(0) / \ 5(1) 3(2) / \ 4(3)1(4) ``` #### 5. 性能分析 - 插入时间复杂度:$O(\log n)$(上浮操作) - 删除时间复杂度:$O(\log n)$(下沉操作) - 空间复杂度:$O(n)$(完全二叉树性质保证无空间浪费)[^2]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值