在数据结构的世界里,优先级队列是一种非常实用的工具,它能让元素按照特定的优先级进出队列。在 C++ 标准库中,已经提供了priority_queue容器适配器,但深入理解其底层实现原理,对于提升编程能力和优化代码有着重要意义。本文将详细解析一段自定义的 C++ 优先级队列模板类代码,带大家一探究竟。
代码通过模板类Priority_Queue实现优先级队列,它包含了构造函数、拷贝构造函数、析构函数、赋值运算符重载等基本的类操作函数,同时还实现了入队、出队、获取队顶元素、判断队列是否为空以及获取队列大小等优先级队列的核心功能。此外,还定义了一些私有辅助函数,用于实现元素的上浮、下沉和队列扩容等操作。
template>
class Priority_Queue
{
private:
T* que_;
int size_;
int cap_;
Comp comp_;
//...
};
Priority_Queue是一个模板类,它有两个模板参数:
类中包含以下私有成员变量:
构造函数
Priority_Queue(int cap = 10, const Comp& comp = Comp()) :size_(0),comp_(comp)
{
if (cap <= 0)throw" the Priority_Queue is too small";
cap_ = cap;
que_ = new T[cap_];
}
构造函数接受两个参数,分别是队列的初始容量cap和比较器对象comp。它首先检查容量是否合法,如果容量小于等于 0,就抛出异常。然后初始化成员变量cap_,并动态分配大小为cap_的内存空间用于存储队列元素,同时将size_初始化为 0 ,comp_初始化为传入的比较器对象。
拷贝构造函数
Priority_Queue(const Priority_Queue& other)
{
int newcap_ = other.cap_;
T* newque_ = new T[newcap_];
std::copy(other.que_, other.que_ + other.size_, newque_);
que_ = newque_;
cap_ = newcap_;
size_ = other.size_;
comp_ = other.comp_;
}
拷贝构造函数用于创建一个与已有Priority_Queue对象完全相同的新对象。它首先根据原对象的容量cap_分配新的内存空间,然后使用std::copy函数将原对象中的元素复制到新分配的内存中,最后更新新对象的各个成员变量,使其与原对象一致。
析构函数
~Priority_Queue()
{
delete[] que_;
que_ = nullptr;
}
析构函数在对象生命周期结束时被调用,用于释放动态分配的内存空间。它通过delete[]操作符释放que_指向的内存,并将que_置为nullptr,防止悬空指针的出现。
赋值运算符重载
template
Priority_Queue& Priority_Queue::operator=(const Priority_Queue &other)
{
if (&other == this)return *this;
Priority_Queue temp(other);
Swap(*this,temp);
return *this;
}
赋值运算符重载实现了将一个Priority_Queue对象赋值给另一个对象的功能。它首先检查是否是自赋值,如果是则直接返回。然后通过创建一个临时对象temp,使用拷贝构造函数将other对象复制到temp中,再通过自定义的Swap函数交换当前对象和temp对象的内容,从而实现了高效的深拷贝赋值。
入队操作(push)
void push(T val)
{
if (size_ == 0)que_[0] = val;
if (size_ == cap_)expand();
sift_up(val);
}
push函数用于将元素val插入到优先级队列中。如果队列当前为空,直接将元素放在队列的第一个位置。如果队列已满,则调用expand函数进行扩容。最后通过调用sift_up函数,将新插入的元素上浮到合适的位置,以维护优先级队列的性质。
出队操作(pop)
void pop()
{
sift_down();
}
pop函数用于移除并返回队列中优先级最高的元素。它通过调用sift_down函数,将队顶元素下沉到合适的位置,同时将队顶元素与队尾元素交换,并减少元素个数,从而实现出队操作。
获取队顶元素(top)
T& top() {
return que_[0];
}
top函数返回队列中优先级最高的元素的引用,即队列的第一个元素。
判断队列是否为空(empty)
bool empty()
{
return size_ == 0;
}
empty函数通过判断队列中元素个数size_是否为 0,来确定队列是否为空
获取队列大小(size)
int size()
{
return size_;
}
size函数直接返回队列中当前元素的个数size_。
上浮操作(sift_up)
void sift_up(T val)
{
que_[size_++] = val;
int i = size_ - 1;
while (i > 0) {
int father = (i - 1) / 2;
if (comp_(que_[father],val)) {
que_[i] = que_[father];
i = father;
}
else break;
}
que_[i] = val;
}
sift_up函数用于将新插入的元素上浮到合适的位置。它首先将元素插入到队列的末尾,并增加元素个数。然后通过不断比较当前元素和其父元素的优先级,如果当前元素优先级更高,则将父元素下移,当前元素继续向上比较,直到找到合适的位置。
下沉操作(sift_down)
void sift_down()
{
if (size_ == 0)throw"the space was nullptr";
swap(que_[0], que_[size_ - 1]);
size_--;
int i = 0;
T val = que_[0];
while (i * 2 + 1 < size_) {
int child = i * 2 + 1;
if (child + 1 < size_ && comp_(que_[child], que_[child+1]))child = child + 1;
if (comp_(val,que_[child]))
{
que_[i] = que_[child];
i = child;
}
else break;
}
que_[i] = val;
}
sift_down函数用于将队顶元素下沉到合适的位置。它首先将队顶元素和队尾元素交换,并减少元素个数。然后从队顶开始,比较当前元素和其孩子节点的优先级,如果存在优先级更高的孩子节点,则将孩子节点上移,当前元素继续向下比较,直到找到合适的位置。
扩容操作(expand)
void expand()
{
int newcap_ = cap_ * 2;
if (newcap_ / 2 != cap_)throw" the space if spill";
T* newque_ = new T[newcap_];
std::copy(que_, que_ + size_, newque_);
delete[] que_;
que_ = newque_;
cap_ = newcap_;
}
expand函数用于在队列满时进行扩容操作。它首先计算新的容量newcap_,为原来容量的两倍。然后检查是否发生了整数溢出,如果发生溢出则抛出异常。接着分配新的内存空间,并使用std::copy函数将原队列中的元素复制到新的内存中,最后释放原内存空间,更新que_和cap_。
通过对这段 C++ 优先级队列模板类代码的详细解析,我们深入了解了优先级队列的底层实现原理。从构造函数的内存分配,到核心功能函数的实现,再到辅助函数对队列性质的维护,每一个环节都体现了数据结构设计的巧妙之处。掌握这些知识,不仅有助于我们更好地使用优先级队列,还能在实际开发中根据具体需求灵活定制和优化数据结构,提升程序的性能和效率。希望本文能对大家理解和应用优先级队列有所帮助。
上述博客全面解析了优先级队列模板类代码。你对博客内容的篇幅、某些部分的讲解深度是否有调整需求,或者还有其他想法,都能随时告诉我。