queue和priority_queue(理解使用和模拟实现)
- 什么是queue
- queue的使用
- 1.queue构造函数
- 2.empty()
- 3.size()
- 4.front()
- 5.back();
- 6.push
- 7.emplace
- 8.pop()
- 9.swap
- queue模拟实现
- 什么是priority_queue
- priority_queue的使用
- 1.priority_queue构造函数
- 1.1 模板参数 Compare
- 1.2 什么是仿函数?
- 2.empty()
- 3.size()
- 4.top()
- 5.push
- 6.emplace
- 7.pop()
- 8.swap
- 模拟实现priority_queue
- 结语
什么是queue
- 队列是一种容器适配器,专门用于在
FIFO
上下文(先进先出)中操作,其中从容器一端插入元素,另一端提取元素。 - 队列作为容器适配器实现,容器适配器即将特定容器类封装作为其底层容器类,
queue
提供一组特定的成员函数来访问其元素。元素从队尾入队列,从队头出队列。 - 底层容器可以是标准容器类模板之一,也可以是其他专门设计的容器类。该底层容器应至少支持以下操作:
empty:检测队列是否为空
size:返回队列中有效元素的个数
front:返回队头元素的引用
back:返回队尾元素的引用
push_back:在队列尾部入队列
pop_front:在队列头部出队列
- 标准容器类
deque
和list
满足了这些要求。默认情况下,如果没有为queue
实例化指定容器类,则使用标准容器deque
。
queue的使用
1.queue构造函数
initialize (1):使用已存在的容器初始化队列。
explicit queue(const container_type& ctnr);
这个构造函数接受一个已存在的容器 ctnr
,并使用其内容初始化队列。
示例:
std::deque<int> myDeque = {1, 2, 3};
std::queue<int> myQueue(myDeque); // 使用已存在的 deque 初始化队列
move-initialize (2):使用已存在的容器初始化队列,并在初始化后将原容器置为空。
explicit queue(container_type&& ctnr = container_type());
这个构造函数接受一个右值引用的容器 ctnr
,将其用于初始化队列,并在初始化后清空 ctnr
。
示例:
std::deque<int> myDeque = {1, 2, 3};
std::queue<int> myQueue(std::move(myDeque)); // 使用已存在的 deque 初始化队列,并清空 myDeque
allocator (3):使用自定义分配器 Alloc 初始化队列。
template <class Alloc> explicit queue(const Alloc& alloc);
这个构造函数接受一个自定义分配器类型 Alloc
,用于在初始化时分配内存。
示例:
std::allocator<int> myAllocator;
std::queue<int> myQueue(myAllocator); // 使用自定义分配器初始化队列
init + allocator (4):使用已存在的容器和自定义分配器初始化队列。
template <class Alloc> queue(const container_type& ctnr, const Alloc& alloc);
这个构造函数接受已存在的容器 ctnr
和自定义分配器 alloc
,用于在初始化时指定底层容器和分配器。
示例:
std::deque<int> myDeque = {1, 2, 3};
std::allocator<int> myAllocator;
std::queue<int> myQueue(myDeque, myAllocator); // 使用已存在的 deque 和自定义分配器初始化队列
move-init + allocator (5):使用已存在的容器和自定义分配器初始化队列,并在初始化后将原容器置为空。
template <class Alloc> queue(container_type&& ctnr, const Alloc& alloc);
类似于 (2),这个构造函数接受右值引用的容器 ctnr
和自定义分配器 alloc
,用于初始化队列,并在初始化后清空 ctnr
。
示例:
std::deque<int> myDeque = {1, 2, 3};
std::allocator<int> myAllocator;
std::queue<int> myQueue(std::move(myDeque), myAllocator); // 使用已存在的 deque 和自定义分配器初始化队列,并清空 myDeque
copy + allocator (6):复制已存在队列,并使用自定义分配器初始化新队列。
template <class Alloc> queue(const queue& x, const Alloc& alloc);
这个构造函数接受一个已存在的队列 x
和自定义分配器 alloc
,用于在初始化时复制 x
中的元素到新的队列。
示例:
std::queue<int> originalQueue;
// 添加一些元素到 originalQueue
std::allocator<int> myAllocator;
std::queue<int> myQueue(originalQueue, myAllocator); // 复制已存在队列并使用自定义分配器初始化新队列
move + allocator (7):移动已存在队列,并使用自定义分配器初始化新队列。
template <class Alloc> queue(queue&& x, const Alloc& alloc);
类似于 (5),这个构造函数接受右值引用的队列 x
和自定义分配器 alloc
,用于初始化新队列并从 x
移动元素。
示例:
std::queue<int> originalQueue;
// 添加一些元素到 originalQueue
std::allocator<int> myAllocator;
std::queue<int> myQueue(std::move(originalQueue), myAllocator); // 移动已存在队列并使用自定义分配器初始化新队列
2.empty()
bool empty() const
是 std::queue
类的成员函数之一,用于判断队列是否为空。这个函数不会修改队列的内容,因此被声明为 const
,表示它不会对对象进行修改。
返回值:
如果队列为空,返回 true
。
如果队列不为空,返回 false
。
使用示例:
#include <iostream>
#include <queue>
int main() {
std::queue<int> myQueue;
if (myQueue.empty()) {
std::cout << "Queue is empty." << std::endl;
} else {
std::cout << "Queue is not empty." << std::endl;
}
myQueue.push(42);
if (myQueue.empty()) {
std::cout << "Queue is empty." << std::endl;
} else {
std::cout << "Queue is not empty." << std::endl;
}
return 0;
}
在这个示例中,首先创建了一个空队列 myQueue
,然后通过 empty()
函数判断队列是否为空。在添加一个元素后,再次调用 empty()
函数来验证队列的状态。根据输出,你可以看到队列在没有元素时为空,在添加元素后不为空。
3.size()
size_type size() const
是 std::queue
类的成员函数之一,用于获取队列中元素的数量。这个函数不会修改队列的内容,因此被声明为 const
,表示它不会对对象进行修改。
返回值:
返回队列中当前的元素数量(大小)。
使用示例:
#include <iostream>
#include <queue>
int main() {
std::queue<int> myQueue;
std::cout << "Initial size: " << myQueue.size() << std::endl;
myQueue.push(42);
myQueue.push(20);
myQueue.push(10);
std::cout << "Updated size: " << myQueue.size() << std::endl;
return 0;
}
在这个示例中,首先创建一个空队列 myQueue
,然后使用 size()
函数获取队列的初始大小。接着添加三个元素到队列中,并再次调用 size()
函数来获取更新后的队列大小。根据输出,你可以看到队列在添加元素后大小发生了变化。
4.front()
reference& front()
和 const_reference& front() const
是 std::queue
类的成员函数之一,用于访问队列的前(头)元素。这些函数不会修改队列的内容,因此被声明为 const
或者返回常量引用,表示它们不会对对象进行修改。
reference
和 const_reference
是模板参数 T
的引用类型和常量引用类型,分别用于表示队列中元素的类型和常量引用类型。返回的引用允许你访问队列的第一个元素。
使用示例:
#include <iostream>
#include <queue>
int main() {
std::queue<int> myQueue;
myQueue.push(42);
myQueue.push(20);
int& firstElement = myQueue.front();
const int& constFirstElement = myQueue.front();
std::cout << "First element: " << firstElement << std::endl;
std::cout << "Constant first element: " << constFirstElement << std::endl;
return 0;
}
在这个示例中,首先创建一个队列 myQueue
,然后添加两个元素。通过 front()
函数获取队列的第一个元素,并将其存储到一个非常量引用 firstElement
和一个常量引用 constFirstElement
中。根据输出,你可以看到两种引用都可以访问队列的第一个元素的值。
5.back();
reference& back()
和 const_reference& back() const
是 std::queue
类的成员函数之一,用于访问队列的后(尾)元素。这些函数不会修改队列的内容,因此被声明为 const
或者返回常量引用,表示它们不会对对象进行修改。
reference
和 const_reference
是模板参数 T
的引用类型和常量引用类型,分别用于表示队列中元素的类型和常量引用类型。返回的引用允许你访问队列的最后一个元素。
使用示例:
#include <iostream>
#include <queue>
int main() {
std::queue<int> myQueue;
myQueue.push(42);
myQueue.push(20);
int& lastElement = myQueue.back();
const int& constLastElement = myQueue.back();
std::cout << "Last element: " << lastElement << std::endl;
std::cout << "Constant last element: " << constLastElement << std::endl;
return 0;
}
在这个示例中,首先创建一个队列 myQueue
,然后添加两个元素。通过 back()
函数获取队列的最后一个元素,并将其存储到一个非常量引用 lastElement
和一个常量引用 constLastElement
中。根据输出,你可以看到两种引用都可以访问队列的最后一个元素的值。
6.push
void push (const value_type& val)
和 void push (value_type&& val)
是 std::queue 类的成员函数之一,用于将元素添加到队列的末尾。
const value_type& val
是一个常量引用,用于传递需要添加到队列的元素。
value_type&& val
是一个右值引用,用于传递需要添加到队列的元素,通常用于支持移动语义。
使用示例:
#include <iostream>
#include <queue>
int main() {
std::queue<int> myQueue;
myQueue.push(42); // 使用右值
int value = 20;
myQueue.push(value); // 使用左值
return 0;
}
在这个示例中,首先创建一个队列 myQueue
,然后使用 push()
函数将两个不同类型的值添加到队列中。第一个 push()
使用右值 42
,第二个 push()
使用左值变量 value
。队列将会按照元素添加的顺序保持它们。
7.emplace
template <class... Args> void emplace (Args&&... args)
是 std::queue
类的成员函数之一,用于通过构造函数参数列表直接在队列的末尾构造一个新元素。
Args... args
是模板参数包,表示传递给构造函数的参数列表。
使用 emplace()
函数可以避免额外的对象复制或移动操作,直接在容器内部构造对象。
使用示例:
#include <iostream>
#include <queue>
class MyObject {
public:
MyObject(int value) : m_value(value) {
std::cout << "Constructed: " << m_value << std::endl;
}
~MyObject() {
std::cout << "Destructed: " << m_value << std::endl;
}
private:
int m_value;
};
int main() {
std::queue<MyObject> myQueue;
myQueue.emplace(42); // 使用右值参数构造
myQueue.emplace(20); // 使用右值参数构造
return 0;
}
在这个示例中,首先创建一个队列 myQueue
,然后使用 emplace()
函数通过右值参数直接在队列末尾构造两个 MyObject
对象。由于使用了 emplace()
,对象将在队列中直接构造,而不会发生额外的复制或移动操作。
8.pop()
void pop()
是 std::queue
类的成员函数之一,用于从队列中移除队列头部的元素。
调用 pop()
函数会删除队列中的第一个元素,并将队列中的其余元素向前移动,以填补被移除的元素的位置。
使用示例:
#include <iostream>
#include <queue>
int main() {
std::queue<int> myQueue;
myQueue.push(42);
myQueue.push(20);
myQueue.push(10);
std::cout << "Size before pop: " << myQueue.size() << std::endl;
myQueue.pop();
std::cout << "Size after pop: " << myQueue.size() << std::endl;
return 0;
}
在这个示例中,首先创建一个队列 myQueue
,然后使用 push()
函数添加三个元素到队列中。通过调用 pop()
函数,将队列头部的元素 42
移除。根据输出,你可以看到在调用 pop()
后,队列的大小减少了。
9.swap
void swap(queue& x) noexcept
是 std::queue
类的成员函数之一,用于交换当前队列与另一个队列 x
的内容。
x
:一个要与当前队列进行交换的另一个队列。
noexcept
:这个函数被声明为不会抛出异常,表示在交换过程中不会引发异常。
使用示例:
#include <iostream>
#include <queue>
int main() {
std::queue<int> queue1;
std::queue<int> queue2;
queue1.push(42);
queue1.push(20);
queue2.push(10);
std::cout << "Before swap:" << std::endl;
std::cout << "Queue 1 front: " << queue1.front() << std::endl;
std::cout << "Queue 2 front: " << queue2.front() << std::endl;
queue1.swap(queue2);
std::cout << "After swap:" << std::endl;
std::cout << "Queue 1 front: " << queue1.front() << std::endl;
std::cout << "Queue 2 front: " << queue2.front() << std::endl;
return 0;
}
在这个示例中,首先创建两个队列 queue1
和 queue2
,并分别添加元素到它们中。通过调用 swap()
函数,交换了队列的内容。根据输出,你可以看到在交换后,队列的内容也发生了交换。
queue模拟实现
#pragma once
#include <deque>
namespace xzq
{
template<class T, class Container = deque<T>>
class queue
{
public:
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_front();
}
T& back()
{
return _con.back();
}
T& front()
{
return _con.front();
}
const T& back() const
{
return _con.back();
}
const T& front() const
{
return _con.front();
}
bool empty() const
{
return _con.empty();
}
size_t size() const
{
return _con.size();
}
private:
Container _con;
};
}
首先,这个队列类使用了一个名为 Container
的模板参数,默认值为 std::deque<T>
。这允许你在创建队列对象时指定底层容器类型,如果不指定,默认使用 std::deque
。
push(const T& x)
函数用于将元素 x
添加到队列的末尾,它实际上调用了底层容器的 push_back()
方法。
pop()
函数用于移除队列头部的元素,它实际上调用了底层容器的 pop_front()
方法。
back()
函数返回队列中的最后一个元素的引用,它可以用于访问队列的末尾元素。
front()
函数返回队列中的第一个元素的引用,用于访问队列的头部元素。
empty()
函数用于检查队列是否为空,它实际上调用了底层容器的 empty()
方法。
size()
函数用于获取队列中元素的数量,它实际上调用了底层容器的 size()
方法。
私有成员 _con
是底层容器对象,用于存储队列的元素。
使用这个模拟队列类,你可以选择不同的底层容器类型(默认为 std::deque
),并调用类的方法来模拟队列的基本操作,如添加元素、移除元素、访问元素、判断是否为空等。这种实现方式允许你通过模板来适应不同的数据类型和底层容器,从而更加灵活地使用队列。
什么是priority_queue
- 优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。
- 此上下文类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素)。
- 优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,
queue
提供一组特定的成员函数来访问其元素。元素从特定容器的“尾部”弹出,其称为优先队列的顶部。 - 底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过随机访问迭代器访问,并支持以下操作:
empty():检测容器是否为空
size():返回容器中有效元素个数
front():返回容器中第一个元素的引用
push_back():在容器尾部插入元素
pop_back():删除容器尾部元素
- 标准容器类
vector
和deque
满足这些需求。默认情况下,如果没有为特定的priority_queue
类实例化指定容器类,则使用vector
。 - 需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数
make_heap
、push_heap
和pop_heap
来自动完成此操作。
priority_queue的使用
1.priority_queue构造函数
std::priority_queue
是 C++ STL
提供的优先队列容器,它是基于堆实现的,允许你以特定的顺序添加和移除元素。
下面是这些构造函数的解释及示例:
priority_queue(const Compare& comp, const Container& ctnr)
:构造一个优先队列,使用给定的比较函数 comp
和底层容器 ctnr
。
priority_queue(InputIterator first, InputIterator last, const Compare& comp, const Container& ctnr)
:使用迭代器范围 [first, last)
中的元素构造一个优先队列,并使用给定的比较函数 comp
和底层容器 ctnr
。
explicit priority_queue(const Compare& comp = Compare(), Container&& ctnr = Container())
:构造一个优先队列,使用给定的比较函数 comp
和移动语义传递的底层容器 ctnr
。
template <class InputIterator> priority_queue(InputIterator first, InputIterator last, const Compare& comp, Container&& ctnr = Container())
:使用迭代器范围 [first, last)
中的元素构造一个优先队列,并使用给定的比较函数 comp
和移动语义传递的底层容器 ctnr
。
Allocator
版本:这些构造函数使用不同的分配器allocator
来构造优先队列。
示例:
#include <iostream>
#include <queue>
#include <vector>
int main() {
// 使用默认底层容器 std::vector,以默认比较函数(最大堆)构造优先队列
std::priority_queue<int> maxHeap;
// 使用自定义比较函数(最小堆)和底层容器 std::deque 构造优先队列
auto compare = [](int a, int b) { return a > b; };
std::deque<int> container = {5, 3, 8, 1, 9};
std::priority_queue<int, std::deque<int>, decltype(compare)> minHeap(compare, container);
// 使用迭代器范围构造优先队列
std::vector<int> elements = {7, 2, 4, 6, 0};
std::priority_queue<int, std::vector<int>> iteratorQueue(elements.begin(), elements.end());
// 输出优先队列中的元素
while (!iteratorQueue.empty()) {
std::cout << iteratorQueue.top() << " ";
iteratorQueue.pop();
}
return 0;
}
在这个示例中,我们演示了不同构造函数的用法。首先,使用默认构造函数构造了一个最大堆的优先队列。然后,我们使用自定义比较函数和底层容器 std::deque
构造了一个最小堆的优先队列。最后,使用迭代器范围构造了一个优先队列。根据输出,你可以看到优先队列以不同的顺序输出元素。
1.1 模板参数 Compare
在 std::priority_queue
类中,通过模板参数 Compare
来指定用于比较元素的函数对象,从而影响堆的排序方式。Compare
是一个仿函数,它定义了元素之间的比较方式。根据不同的 Compare
,优先队列可以变成大堆(最大堆)或小堆(最小堆)。
默认的 std::less<T>
(大堆):
std::less 是一个函数对象,它重载了 operator(),用于比较两个元素。它返回一个布尔值,表示是否第一个参数小于第二个参数。在默认情况下,如果不提供 Compare 参数,优先队列使用 std::less 作为比较函数对象,即大堆。这意味着在大堆中,父节点的值总是大于或等于子节点的值。
std::greater<T>
(小堆):
std::greater<T>
是另一个函数对象,它重载了 operator()
,用于比较两个元素。与 std::less<T>
不同,std::greater<T>
返回一个布尔值,表示第一个参数是否大于第二个参数。如果你将 std::greater<T>
传递给 priority_queue
,它将会构造一个小堆。在小堆中,父节点的值总是小于或等于子节点的值。
以下是示例代码,演示了如何使用不同的比较函数对象来创建大堆和小堆:
#include <iostream>
#include <queue>
int main() {
std::priority_queue<int> maxHeap; // 默认大堆
std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap; // 小堆
maxHeap.push(5);
maxHeap.push(3);
maxHeap.push(8);
minHeap.push(5);
minHeap.push(3);
minHeap.push(8);
std::cout << "Max Heap (Top element): " << maxHeap.top() << std::endl;
std::cout << "Min Heap (Top element): " << minHeap.top() << std::endl;
return 0;
}
在这个示例中,我们分别创建了一个大堆和一个小堆。通过 top()
函数,我们可以看到大堆的顶部元素是最大的,小堆的顶部元素是最小的。这反映了不同比较函数对象的影响。
1.2 什么是仿函数?
仿函数(Functor)是一种重载了函数调用操作符 operator()
的类对象,使得该对象可以像函数一样被调用。它实际上是一种函数对象,它可以具有自己的成员变量和操作,同时可以在使用上类似于普通函数。
使用仿函数的主要优点之一是可以将函数的行为和状态封装在对象中,从而使代码更具有可读性和可维护性。仿函数可以用于各种情况,包括标准算法、STL
容器和其他需要函数式操作的地方。
一个仿函数类通常至少需要实现 operator()
,它可以具有不同的参数和返回类型,具体取决于仿函数的用途。以下是一个简单的示例:
#include <iostream>
class Adder {
public:
Adder(int value) : value_(value) {}
int operator()(int x) {
return x + value_;
}
private:
int value_;
};
int main() {
Adder addFive(5);
Adder addTen(10);
int result1 = addFive(7); // 调用仿函数 addFive
int result2 = addTen(7); // 调用仿函数 addTen
std::cout << "Result 1: " << result1 << std::endl;
std::cout << "Result 2: " << result2 << std::endl;
return 0;
}
在这个示例中,Adder
类被定义为一个仿函数,它接受一个整数值,并在调用时将该值添加到传递的参数上。在 main()
函数中,我们创建了两个 Adder
对象 addFive
和 addTen
,然后通过调用它们来执行加法操作。
总之,仿函数是一种函数对象,它使得对象可以像函数一样被调用,从而可以将函数的行为和状态封装在一个对象中。这在编写更加灵活和可读性高的代码时非常有用。
2.empty()
bool empty() const
是 std::priority_queue
类的成员函数之一,用于检查优先队列是否为空。
empty()
函数返回一个布尔值,表示优先队列是否为空。
const
修饰符表示这个函数不会修改优先队列的内容。
使用示例:
#include <iostream>
#include <queue>
int main() {
std::priority_queue<int> maxHeap;
if (maxHeap.empty()) {
std::cout << "The priority queue is empty." << std::endl;
} else {
std::cout << "The priority queue is not empty." << std::endl;
}
maxHeap.push(42);
if (maxHeap.empty()) {
std::cout << "The priority queue is empty." << std::endl;
} else {
std::cout << "The priority queue is not empty." << std::endl;
}
return 0;
}
在这个示例中,我们首先创建了一个空的 std::priority_queue
对象 maxHeap
,然后使用 empty()
函数检查它是否为空。根据输出,你可以看到在添加一个元素后,优先队列不再为空。
3.size()
size_type size() const
是 std::priority_queue
类的成员函数之一,用于获取优先队列中元素的数量。
size()
函数返回一个无符号整数size_type
,表示优先队列中元素的数量。
const
修饰符表示这个函数不会修改优先队列的内容。
使用示例:
#include <iostream>
#include <queue>
int main() {
std::priority_queue<int> maxHeap;
maxHeap.push(42);
maxHeap.push(20);
maxHeap.push(10);
std::cout << "Size of the priority queue: " << maxHeap.size() << std::endl;
return 0;
}
在这个示例中,我们创建了一个包含三个元素的大堆优先队列 maxHeap
,然后使用 size()
函数获取队列中元素的数量。根据输出,你可以看到队列中有三个元素。
4.top()
const_reference top() const
是 std::priority_queue
类的成员函数之一,用于获取优先队列的顶部元素(最大元素或最小元素,取决于堆的类型),但不改变队列的内容。
top()
函数返回队列的顶部元素的常引用const_reference
,即最大元素或最小元素。
const
修饰符表示这个函数不会修改优先队列的内容。
使用示例:
#include <iostream>
#include <queue>
int main() {
std::priority_queue<int> maxHeap;
maxHeap.push(42);
maxHeap.push(20);
maxHeap.push(10);
std::cout << "Top element: " << maxHeap.top() << std::endl;
return 0;
}
在这个示例中,我们创建了一个大堆优先队列 maxHeap
,并添加了三个元素。使用 top()
函数,我们获取了队列中的顶部元素(最大元素)。根据输出,你可以看到顶部元素的值是 42
。
5.push
void push(const value_type& val)
和 void push(value_type&& val)
是 std::priority_queue
类的成员函数之一,用于将新元素添加到优先队列中。
push(const value_type& val)
接受一个常引用 val
,将一个新的元素拷贝到优先队列中。
push(value_type&& val)
接受一个右值引用 val
,使用移动语义将一个新的元素移动到优先队列中。
使用示例:
#include <iostream>
#include <queue>
int main() {
std::priority_queue<int> maxHeap;
maxHeap.push(42); // 使用 push(const value_type& val)
maxHeap.push(20); // 使用 push(const value_type& val)
maxHeap.push(10); // 使用 push(const value_type& val)
std::cout << "Top element: " << maxHeap.top() << std::endl;
return 0;
}
在这个示例中,我们使用两种不同的方式将元素添加到大堆优先队列 maxHeap
中。第一种是使用 push(const value_type& val)
,第二种是使用 push(value_type&& val)
。这两种方式都会将元素添加到队列中。根据输出,你可以看到顶部元素的值是 42
,这是因为优先队列会自动将最大(或最小)的元素放在顶部。
6.emplace
template <class... Args> void emplace(Args&&... args)
是 std::priority_queue
类的成员函数之一,用于以就地构造的方式在优先队列中插入一个新元素。
emplace()
函数使用参数包parameter pack
来接受构造元素所需的参数。
它可以在现有元素中进行插入,同时避免额外的拷贝或移动操作,提高效率。
这个函数使用完美转发来传递参数,以适应不同类型的构造函数。
使用示例:
#include <iostream>
#include <queue>
int main() {
std::priority_queue<int> maxHeap;
maxHeap.emplace(42); // 插入一个新元素
int value = 20;
maxHeap.emplace(value); // 插入一个新元素,使用拷贝构造函数
maxHeap.emplace(10); // 插入一个新元素
std::cout << "Top element: " << maxHeap.top() << std::endl;
return 0;
}
在这个示例中,我们使用 emplace()
函数以就地构造的方式插入新元素到大堆优先队列 maxHeap
中。在插入时,我们可以传递构造元素所需的参数。这个函数会自动调用适当的构造函数,以在堆中插入新元素。根据输出,你可以看到顶部元素的值是 42
。
7.pop()
void pop()
是 std::priority_queue
类的成员函数之一,用于移除优先队列的顶部元素(最大元素或最小元素,取决于堆的类型)。
pop()
函数将优先队列的顶部元素移除,同时会重新调整堆以维持堆的性质。
注意,调用这个函数前需要确保队列不为空,否则会产生未定义的行为。
使用示例:
#include <iostream>
#include <queue>
int main() {
std::priority_queue<int> maxHeap;
maxHeap.push(42);
maxHeap.push(20);
maxHeap.push(10);
std::cout << "Top element before pop: " << maxHeap.top() << std::endl;
maxHeap.pop();
std::cout << "Top element after pop: " << maxHeap.top() << std::endl;
return 0;
}
在这个示例中,我们首先创建了一个大堆优先队列 maxHeap
,然后使用 push()
函数添加了三个元素。通过 top()
函数,我们获取了队列的顶部元素。接着,我们使用 pop()
函数将顶部元素移除,并再次通过 top()
函数获取了新的顶部元素。根据输出,你可以看到在移除一个元素后,队列的顶部元素变为 20
。
8.swap
void swap(priority_queue& x) noexcept
是 std::priority_queue
类的成员函数之一,用于交换两个优先队列的内容。
swap()
函数用于交换调用对象和传递的参数 x 之间的内容。
这个操作会导致两个优先队列的内容互换,但不会改变它们的比较函数或其他属性。
noexcept
关键字表示这个函数不会抛出异常。
使用示例:
#include <iostream>
#include <queue>
int main() {
std::priority_queue<int> maxHeap1;
std::priority_queue<int> maxHeap2;
maxHeap1.push(42);
maxHeap1.push(20);
maxHeap2.push(10);
maxHeap2.push(30);
std::cout << "Max Heap 1 (Top element before swap): " << maxHeap1.top() << std::endl;
std::cout << "Max Heap 2 (Top element before swap): " << maxHeap2.top() << std::endl;
maxHeap1.swap(maxHeap2);
std::cout << "Max Heap 1 (Top element after swap): " << maxHeap1.top() << std::endl;
std::cout << "Max Heap 2 (Top element after swap): " << maxHeap2.top() << std::endl;
return 0;
}
在这个示例中,我们创建了两个大堆优先队列 maxHeap1
和 maxHeap2
,并分别添加了不同的元素。通过 top()
函数,我们获取了两个队列的顶部元素。然后使用 swap()
函数,我们交换了两个队列的内容。根据输出,你可以看到交换后,两个队列的内容互换了。
模拟实现priority_queue
#pragma once
namespace xzq
{
// Compare进行比较的仿函数 less->大堆
// Compare进行比较的仿函数 greater->小堆
template<class T, class Container = vector<T>, class Compare = std::less<T>>
class priority_queue
{
public:
priority_queue()
{}
template <class InputIterator>
priority_queue(InputIterator first, InputIterator last)
{
while (first != last)
{
_con.push_back(*first);
++first;
}
for (int i = (_con.size()-1-1)/2; i >= 0; --i)
{
adjust_down(i);
}
}
void adjust_up(size_t child)
{
Compare com;
size_t parent = (child - 1) / 2;
while (child > 0)
{
if (com(_con[parent], _con[child]))
{
std::swap(_con[child], _con[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void push(const T& x)
{
_con.push_back(x);
adjust_up(_con.size() - 1);
}
void adjust_down(size_t parent)
{
Compare com;
size_t child = parent * 2 + 1;
while (child < _con.size())
{
if (child + 1 < _con.size() && com(_con[child],_con[child + 1]))
{
++child;
}
if (com(_con[parent],_con[child]))
{
std::swap(_con[child], _con[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void pop()
{
std::swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
adjust_down(0);
}
const T& top()
{
return _con[0];
}
bool empty() const
{
return _con.empty();
}
size_t size() const
{
return _con.size();
}
private:
Container _con;
};
}
命名空间 xzq:
这段代码位于命名空间 xzq
中,这是一个自定义的命名空间,用于将相关的类、函数等封装在一起,以避免与其他代码的命名冲突。
模板类 priority_queue:
这是一个模板类,它代表了一个优先队列的实现。它接受三个模板参数:T
(元素类型),Container
(底层容器类型,默认为 std::vector<T>
),和 Compare
(用于比较元素的仿函数,默认为 std::less<T>
)
Compare
是一个模板参数,用于进行元素的比较。它是一个仿函数,可以是 std::less<T>
(默认)或 std::greater<T>
,具体取决于用户提供的优先队列类型。Compare
仿函数用于确定在堆中的元素排序方式,从而决定了是最大堆还是最小堆。在 priority_queue
类的各个成员函数中,通过调用 Compare
仿函数来进行元素的比较,从而实现了插入和调整堆的操作。
构造函数 priority_queue():
这是一个默认构造函数,不需要使用 Compare
仿函数进行比较。
构造函数模板 priority_queue(InputIterator first, InputIterator last):
在构造函数内部,使用 Compare
仿函数来执行比较操作,以确定元素的顺序。在添加元素后,通过调用 adjust_down
函数来构建堆。
成员函数 adjust_up(size_t child):
在上浮操作中,使用 Compare
仿函数执行比较,以确定是否需要交换父子节点的位置,从而保持堆的性质。
成员函数 push(const T& x):
在插入操作中,首先将新元素添加到底层容器 _con
,然后通过调用 adjust_up
函数来执行上浮操作,保证新元素位于合适的位置。
成员函数 adjust_down(size_t parent):
在下沉操作中,使用 Compare
仿函数执行比较,以确定是否需要交换父子节点的位置,从而保持堆的性质。
成员函数 pop():
在删除操作中,首先将顶部元素与底层容器的最后一个元素交换,然后通过调用 adjust_down 函数来执行下沉操作,保证堆的性质。
成员函数 const T& top():
通过返回 _con[0]
,获取优先队列的顶部元素。
结语
有兴趣的小伙伴可以关注作者,如果觉得内容不错,请给个一键三连吧,蟹蟹你哟!!!
制作不易,如有不正之处敬请指出
感谢大家的来访,UU们的观看是我坚持下去的动力
在时间的催化剂下,让我们彼此都成为更优秀的人吧!!!