C++必修:STL之priority_queue的用法与实现

✨✨ 欢迎大家来到贝蒂大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:C++学习
贝蒂的主页:Betty’s blog

1. priority_queue的介绍

1.1. priority_queue的概念

priority_queue即为**优先级队列,**它是STL对数据结构中堆的具体封装,因为priority_queue的头文件倍包含在queue的头文件中,所以使用时只需要包含头文件#include<queue>,并且使用优先级队列建堆,默认为大堆。

img

1.2. priority_queue的特点

priority_queue的模版参数有三个,第一个T是我们的元素类型,第二个Container是我们的容器适配器,第三个Compare是我们的仿函数。

img

其中需要注意的是:

  • priority_queue的容器适配器需要具有empty()size()front()push_back()pop_back()五种功能,其中常见的容器有vectordequepriority_queue默认选择的就是vector
  • priority_queue的容器适配器需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数make_heappush_heappop_heap来自动完成此操作。

2. 仿函数

2.1. 仿函数的概念

接下来我们来重点介绍一下priority_queue的三个模版参数Compare——仿函数。

**仿函数(Functor)**是一种行为类似函数的对象,它可以被用作函数并接受参数。在 C++ 中,仿函数通常是重载了函数调用运算符 operator()的类对象。通过重载 operator(),仿函数可以像函数一样被调用,并且可以保存状态信息。

下面是一个具体的仿函数例子:

// 定义一个比较小于的仿函数
template<class T>
struct Less 
{
    //重载函数调用运算符 
    bool operator()(const T&a, const T&b)  
    {
        return a < b; 
    }
};
int main() 
{
    Less<int> le;//定义一个仿函数对象
    int a = 5, b = 9;
    cout << le(a, b) << endl;//采用仿函数比较
    cout << (Less<int>().operator()(a,b)) << endl;//完整调用
    return 0;
}

然后我们可以通过定义对象的方式调用,也可以通过完整的类调用函数方式调用。

2.2. 仿函数的应用

在C++的标准库中,仿函数的使用就较为普遍,一般都作为模版参数进行传参。比如说算法库algorithm中的排序算法sort,默认排序就是升序,如果需要降序排序就需要传仿函数或函数调整。这个仿函数或者函数我们可以自己定义,也可以借用标准库中提供的函数。其中比较常用的就是比较小于的less,比较大于的greater。使用时需要包含头文件#include<functional>

img

void Test1()
{
    vector<int> arr1 = { 5,4,6,7,2,9,10,8 };
    vector<int> arr2 = arr1;
    sort(arr1.begin(), arr1.end());//默认为升序
    for (auto& e : arr1)
    {
        cout << e << " ";
    }
    cout << endl;
    //通过匿名函数对象greater<int>()调整为升序
    sort(arr2.begin(), arr2.end(),greater<int>());
    for (auto& e : arr2)
    {
        cout << e << " ";
    }
    cout << endl;
}

img

2.3. 仿函数与函数指针的对比

其实仿函数的用法与我们C语言中的函数指针非常类似,或者说就是为了简化C语言的函数指针,C++才引入了仿函数这个概念。

以下是仿函数与函数指针的对比:

灵活性

  • 仿函数可以保存状态信息,因为它是一个类对象,可以有成员变量来存储相关数据。例如,一个计算累加和的仿函数可以保存当前的总和。
  • 函数指针只是指向一个函数,无法保存额外的状态。

封装性

  • 仿函数可以将相关的操作和数据封装在一个类中,提供更好的封装性和代码组织性。
  • 函数指针本身不具备封装特性。

代码可读性

  • 仿函数的使用在某些情况下可以使代码更具可读性,特别是当相关的操作较为复杂且需要维护状态时。
  • 函数指针的使用可能会使代码看起来较为简洁,但对于复杂逻辑可能不太直观。

可扩展性

  • 仿函数可以通过继承和多态性进行扩展和定制。
  • 函数指针在这方面的扩展性较差。

3. priority_queue的用法

priority_queue提供的接口相比较与其他容器也明显较少,这是由于其结构决定的,并且由于priority_queue也并不需要遍历,所以也不存在迭代器的概念。

以下就是priority_queue的接口:

img

并且由于我们学习过数据结构中的堆,所以使用这里的接口的成本也比较低。

void Test2()
{
    priority_queue<int> heap;//默认为大堆
    heap.push(1);
    heap.push(2);
    heap.push(3);
    heap.push(4);
    heap.push(5);
    heap.push(6);
    cout << "堆的大小为:" << heap.size() << endl;
    cout << "堆顶元素为:" << heap.top() << endl;//最大值
    while (!heap.empty())
    {
        int top = heap.top();
        cout << top << " ";
        heap.pop();
    }
    cout << endl;
}

img

当然我们也可以使用greater仿函数让其变为小堆。

void Test3()
{
    priority_queue<int,vector<int>,greater<int>> heap;//小堆
    heap.push(1);
    heap.push(2);
    heap.push(3);
    heap.push(4);
    heap.push(5);
    heap.push(6);
    cout << "堆的大小为:" << heap.size() << endl;
    cout << "堆顶元素为:" << heap.top() << endl;//最小值
    while (!heap.empty())
    {
        int top = heap.top();
        cout << top << " ";
        heap.pop();
    }
    cout << endl;
}

img

4. 模拟实现priority_queue

因为我们学习过堆这个数据结构,所以封装priority_queue会非常简单,只需要在原有的基础上增加一个仿函数即可。

4.1. 插入

向堆的末尾插入新的元素,然后与父节点比较,然后判断是否需要向上调整。假设插入元素23,如下图:

img

img

//向上调整
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& val)
{
    _con.push_back(val);
    adjust_up(_con.size() - 1);
}

4.2. 删除

先将堆顶元素与最后一个元素交换,将删除堆顶元素转化为删除末尾元素,然后再对堆顶元素进行向下调整。

img

img

img

//向下调整
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[parent], _con[child]);
            parent = child;
            child = parent * 2 + 1;
        }
        else
        {
            break;
        }
    }
}
void pop()
{
    std::swap(_con[0], _con[_con.size() - 1]);
    _con.pop_back();
}

4.3. 其他接口

剩余的size()empty()top()等接口实现就非常容易了,这需要复用我们容器适配器的接口即可。

const T& top()const
{
    return _con[0];
}
size_t size()const
{
    return _con.size();
}
bool empty()const
{
    return _con.empty();
}

5. 源码

#include<vector>
#include<functional>
namespace betty
{
	template<class T,class Container=vector<T>,class Compare=less<T>>
	class priority_queue
	{
	public:
		//向上调整
		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 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[parent], _con[child]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}
		void push(const T& val)
		{
			_con.push_back(val);
			adjust_up(_con.size() - 1);
		}
		void pop()
		{
			std::swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
		}
		const T& top()const
		{
			return _con[0];
		}
		size_t size()const
		{
			return _con.size();
		}
		bool empty()const
		{
			return _con.empty();
		}
	private:
		Container _con;
	};
}
on.push_back(val);
			adjust_up(_con.size() - 1);
		}
		void pop()
		{
			std::swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
		}
		const T& top()const
		{
			return _con[0];
		}
		size_t size()const
		{
			return _con.size();
		}
		bool empty()const
		{
			return _con.empty();
		}
	private:
		Container _con;
	};
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Betty’s Sweet

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值