用C++模板打造高效优先队列:从零实现详解

在数据结构的世界里,优先级队列是一种非常实用的工具,它能让元素按照特定的优先级进出队列。在 C++ 标准库中,已经提供了priority_queue容器适配器,但深入理解其底层实现原理,对于提升编程能力和优化代码有着重要意义。本文将详细解析一段自定义的 C++ 优先级队列模板类代码,带大家一探究竟。

一、整体代码结构概览​

代码通过模板类Priority_Queue实现优先级队列,它包含了构造函数、拷贝构造函数、析构函数、赋值运算符重载等基本的类操作函数,同时还实现了入队、出队、获取队顶元素、判断队列是否为空以及获取队列大小等优先级队列的核心功能。此外,还定义了一些私有辅助函数,用于实现元素的上浮、下沉和队列扩容等操作。

二、模板类定义与成员变量

template>
class Priority_Queue
{
private:
    T* que_;
    int size_;
    int cap_;
    Comp comp_;
    //...
};

Priority_Queue是一个模板类,它有两个模板参数:​

  • T:表示队列中存储元素的类型。​
  • Comp:是一个比较器类型,默认使用less,即默认按照从小到大的顺序确定元素优先级。​

类中包含以下私有成员变量:​

  • que_:是一个指针,指向动态分配的用于存储队列元素的内存空间。​
  • size_:记录队列中当前元素的个数。​
  • cap_:表示队列当前分配的内存空间大小。​
  • comp_:是一个比较器对象,用于比较元素的优先级。

三、构造函数与内存管理 

构造函数

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++ 优先级队列模板类代码的详细解析,我们深入了解了优先级队列的底层实现原理。从构造函数的内存分配,到核心功能函数的实现,再到辅助函数对队列性质的维护,每一个环节都体现了数据结构设计的巧妙之处。掌握这些知识,不仅有助于我们更好地使用优先级队列,还能在实际开发中根据具体需求灵活定制和优化数据结构,提升程序的性能和效率。希望本文能对大家理解和应用优先级队列有所帮助。​

上述博客全面解析了优先级队列模板类代码。你对博客内容的篇幅、某些部分的讲解深度是否有调整需求,或者还有其他想法,都能随时告诉我。

你可能感兴趣的:(c++,开发语言,算法,数据结构,优先队列,Queue,队列)