C++ - priority_queue讲解, 适配器 和 仿函数

前言

之前了解 stack 和 queue 的使用时, 我们会发现, 他们的构造函数都非常简单.

stack<int> st;
queue<int> qe;

但是, 当我们在官网上 (cplusplus.com - The C++ Resources Network) 查找 stack 或 queue 时, 会发现他们的模板参数不止一个.

我们可以观察到, stack 和 queue 除了一个 T 模板参数, 还有一个 Container 模板参数, 而且这个模板参数还有默认值 deque<T>.

1. 那么这个 Container 这个模板参数是什么?

2. deque<T> 又是个什么?

适配器

这个 Container 就是一个适配器.

什么是适配器:

适配器(Adapter)是一种软件设计模式, 它允许不兼容的接口之间能够一起工作.

那么具体一点: 

例子: 我们在宿舍, 如果只给每个人分配一个三孔的插座, 那么我们的手机的充电头都是两孔的, 此时手机就充不上电.
解决方法: 我们可以买一个排插(假设这个排插上只有一个两孔的插座), 那么我们就可以通过这个插座来解决充电口不匹配的问题. 这个排插就被称为 "适配器".

那么这里的适配器有什么作用呢?

我们知道 vector 的底层就是用顺序表来实现的, 也就是对顺序表进行了封装.

那么 stack 底层也可以使用 顺序表来实现.

这样就提高了代码的复用性, stack 底层可以直接使用 vector, 不需要从 0 开始实现一个 stack

这里模拟实现以下栈: 

template<class T, class Container = std::deque<T>>
class stack
{
public:

	void push(const T& val)
	{
		_con.push_back(val);
	}

	void pop()
	{
		_con.pop_back();
	}

	const T& top()
	{
		_con.back();
	}

	bool empty()
	{
		return _con.empty();
	}

	size_t size()
	{
		return _con.size();
	}

private:
	Container _con;
};

int main()
{
    stack<int, vector<int>> st
    return 0;
}

可以看到, 我们没有实现任何功能, 所有的功能都是由 Container(vector) 提供的.

那么 queue 也是如此, 也可以通过复用 list 来简化代码.

deque

上面讲解了 适配器. 知道了可以通过适配器来复用代码, 但是 默认值中的 deque 又是什么?

deque 也是 STL 中的一个容器. 这个容器比较的特殊.

deque又被称为双端队列是一种双开口的"连续"空间的数据结构,双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为O(1),与vector比较,头插效率高,不需要搬移元素,与list比较,空间利用率比较高

 deque 的底层并不是一个真正的连续空间, 而是有一段段的小空间拼接起来的

可以看到 deque 是再一定程度上中和了 vector 和 list 的优势. 局部 vector, 整体 list.

deque 的结构比较的复杂, 这里只是简单介绍. 日常也比较少的使用 deque.

priority_queue

priority_queue 又称为优先级队列.
这个名称听起来非常高大上, 实际上它就是数据结构中的 "堆"

可以看到, priority_queue 的底层默认使用的就是 vector 来实现的.
但是这里还看到了第三个模板参数 Compare, 这是一个仿函数, 我们留在后面讲解.

先来熟悉 priority_queue 的使用.

构造

#include <queue>
#include <vector>

// 默认构造一个空的优先队列
std::priority_queue<int> pq;

// 也可以从一个给定的容器构造优先队列
std::vector<int> vec = {5, 3, 1, 4, 2};
std::priority_queue<int> pq_from_vector(vec);

empty / size

empty: 判断数据是否为空, 是返回 true, 否者返回 false

size: 返回数据的个数

bool is_empty = pq.empty();
size_t n = pq.size(); 

top

top: 返回堆顶的元素.

int top_element = pq.top(); // 访问优先队列顶部的元素,不删除它

push / pop

push: 向堆中插入一个数据

pop: 删除堆顶的数据

pq.push(10); // 将元素 10 添加到优先队列中
pq.pop(); // 移除优先队列顶部的元素

仿函数

什么是仿函数?

仿函数, 也称为函数对象, 是一种通过重载 operator()运算符来模拟函数行为的类对象.

简单说: 如果一个类, 在内部重载了 运算符 "()". 那么这个类就可以被称为是仿函数.

#include<iostream>

class Func
{
public:
    bool operator() (int a, int b)
    {
        return a < b;
    }
};

int main()
{
    Func less;
    cout << less(2, 10);
    // 2 < 10, 所以会返回 true, 并打印出来
    return 0;
}

在上面的 priority_queue 介绍中, 我们了解到 priority_queue 是有仿函数的.

仿函数的作用就体现出来了:
当我们使用默认的仿函数时, 创建的堆是一个大根堆.
如果我们想要创建一个小根堆, 那不就是要传递一个仿函数,
修改比较的规则.

std::priority_queue<int, std::vector<int>, std::greater<int>> pq_min_heap;
// std::greater 就是 STL 官方提供的一个仿函数, 当我们将这个仿函数传递给 priority_queue 后
// 建立的就是一个小根堆
// 如果我们不想使用 STL 官方提供的, 也可以自己实现一个 仿函数
// 自定义比较规则, 提高了代码的灵活性

下面来看看 priority_queu 的模拟实现: 

对于数据的存取, 我们可以直接复用, 主要实现的就是 "堆" 的调整.
如果对于堆的调整有疑惑可以看: C语言实现堆-优快云博客

template<class T, class Container = std::vector<T>, class Compare = std::less<T>>
class priority_queue
{
	Compare com; // 仿函数实例化出一个对象, 才能使用成员函数 operator()
public:
	priority_queue()
	{}
	template<class iterator>
	priority_queue(iterator begin, iterator end)
		:_con(begin, end)
	{
		int pos = ((int)size() - 1 - 1) / 2;
		while (pos >= 0)
		{
			adjust_down(pos, size());
			pos--;
		}
	}

	void adjust_down(int parent, int n) // 向下调整
	{
		int child = parent * 2 + 1;
		while (child < n)
		{
			if (child + 1 < n && com(_con[child], _con[child + 1]))
			{
				child++;
			}
			if (com(_con[parent], _con[child]))
			{
				swap(_con[parent], _con[child]);
				parent = child;
				child = parent * 2 + 1;
			}
			else
			{
				break;
			}
		}
	}

	void adjust_up(int child) // 向上调整
	{
		int parent = (child - 1) / 2;
		while (child > 0)
		{
			if (com(_con[parent], _con[child]))
			{
				swap(_con[parent], _con[child]);
				child = parent;
				parent = (child - 1) / 2;
			}
			else
			{
				break;
			}
		}
	}

	~priority_queue()
	{}

	void swap(T& x1, T& x2)
	{
		std::swap(x1, x2);
	}

	void push(const T& val)
	{
		_con.push_back(val);
		adjust_up(size() - 1);
		
	}

	void pop()
	{
		swap(_con[0], _con[size() - 1]);
		_con.pop_bakc();
		adjust_down(0, size());
	}

	const T& top()
	{
		return _con.front();
	}

	size_t size()
	{
		return _con.size();
	}

private:
	Container _con;
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值