STL详解——priority_queue的使用以及模拟实现

目录

priority_queue的介绍

仿函数的介绍

什么是仿函数?

仿函数的运用

priority_queue的功能展示

priority_queue的模拟实现

向上调整算法以及向下调整算法

常用功能实现

一些内容的补充:

什么是适配器

STL标准库中stack和queue的底层结构

deque的原理介绍

priority_queue的介绍

标准容器类vector和deque满足这些需求。默认情况下,如果没有为特定的priority_queue类实例化指定容器类,则使用vector。

需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数make_heap、push_heap和pop_heap来自动完成此操作。

上图是在C++官方文档中对优先队列的介绍,其中提到使其第一个元素总是其所包含元素中最大的一个,这是不是很像我们学过的大堆,大堆中的根节点所包含的元素不正是堆中最大的元素吗?

cpp 复制代码
template <class T, class Container = vector<T>,
  class Compare = less<typename Container::value_type> > class priority_queue;

我们可以看到,优先队列默认使用vector存储数据,在vector上又使用了堆算法将vector中的元素构造成堆的结构,因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用priority_queue。

其中Compare是 比较准则,它是一个仿函数类型 ,用于决定元素的优先级顺序。默认std::less<T> 意味着大堆(最大值优先);如果传入 std::greater<T>,就会变成小堆(最小值优先)。

如果没有学习过堆或者对该部分内容有遗忘,可以看看我写过的下面两篇文章:

堆的定义以及模拟实现

堆额外功能解析

仿函数的介绍

什么是仿函数?

  仿函数,或者叫函数对象,是指任何一个可以像函数一样被调用的对象。

看下面的代码:

cpp 复制代码
template<class T>
class Less
{
public:
	bool operator()(const T& x1, const T& x2)
	{
		return x1 < x2;
	}
};

template<class T>
class Greater
{
public:
	bool operator()(const T& x1, const T& x2)
	{
		return x1 > x2;
	}
};

我们通过对()进行重载使得该类实例化后的对象就像一个函数,但它有函数的本质所不具备的特性:

  仿函数通过成员变量维护内部状态,实现有状态(保留了之前的调用信息)的可调用对象,且多个仿函数实例可以各自拥有独立状态,互不干扰。它可以被编译器内联,还可以作为模板参数传递。

一般不需要我们自己来实现,可以直接使用库里面的,头文件<functional>

需要我们自己实现仿函数的情况:

  1. 类类型不支持比较大小
  2. 支持比较大小,但是比较的逻辑不是我们想要的

仿函数的运用

我们在冒泡排序中使用仿函数,可以根据我们的需要决定实现正序、倒序,而不对代码进行直接的更改:

cpp 复制代码
#include<vector>
using namespace std;
template<class T>
class Less
{
public:
	bool operator()(const T& x1, const T& x2)
	{
		return x1 < x2;
	}
};
template<class T>
class Greater
{
public:
	bool operator()(const T& x1, const T& x2)
	{
		return x1 > x2;
	}
};
template<class Compare>
void BubbleSort(int* a, int n, Compare com)
{
	for (int i = 0; i < n; i++)
	{
		int flag = 0;
		for (int j = 1; j < n - i; j++)
		{
			//if (a[j - 1] > a[j])
			if (com(a[j], a[j - 1]))
			{
				swap(a[j - 1], a[j]);
				flag = 1;
			}
		}

		if (flag == 0)
		{
			break;
		}
	}
}
int main()
{
	Less<int> less;
	Greater<int> greater;

	int a[] = { 2,6,1,8,4,9,7 };
	BubbleSort(a, 7, less);
	BubbleSort(a, 7, greater);

	//传匿名对象
	BubbleSort(a, 7, Less<int>());
	BubbleSort(a, 7, Greater<int>());

	return 0;
}

priority_queue的功能展示

主要功能如下:

cpp 复制代码
#include<queue>
#include<iostream>
using namespace std;
void test()
{
	priority_queue<int> pq;
	for (int i = 0;i < 5;i++)
	{
		pq.push(i);
	}
	if (pq.empty())
	{
		cout << "空" << endl;
	}
	const int size = pq.size();
	for (int i = 0;i < size;i++)
	{
		cout << pq.top() << ' ';
		pq.pop();
	}

}
int main()
{
	test();
	return 0;
}

功能是比较简单的,同时我们也有一定的基础,所以在这里对功能仅做简单展示。

priority_queue的模拟实现

当我们将元素依次放在数组里,会有如下规则:

下标为 i 的节点的父节点下标为 (i - 1) / 2,左孩子为 2 * i + 1,右孩子为 2 * i + 2

那我们就可以利用这个规则、堆的向上调整以及向下调整算法来完成优先队列的模拟实现,我们先完成向上调整算法以及向下调整算法。

先给出大概的框架:

cpp 复制代码
namespace mine
{
	template<class T,class Container=std::vector<T>,class compare=Less<T>>
	class priority_queue
	{
	public:

		
	private:
		Container _con;
	};
}

向上调整算法以及向下调整算法

cpp 复制代码
//用于push
void AdjustUp(int child)//我们默认建大堆
{
	int parent = (child - 1) / 2;
	while (child > 0)//当child=0,就一定是最大了,也就调整成堆了
	{
		//_con[child]>_con[parent]
		if (compare()(_con[parent], _con[child]))
		//我们这里使用的是less,就是<,所以需要调换一下顺序
		{
			std::swap(_con[parent], _con[child]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;//只要有一次满足大堆,那么就实现了
		}
	}
}
void AdjustDown(int parent)
{
	//对于父节点来说,当我们要构造大堆时,我们应该与大的那个孩子进行交换
	int child = parent * 2 + 1;//我们先认为左孩子是大的哪一个
	while (child < _con.size())
		//因为每次都要与孩子比较,当孩子到了叶子节点后,再调整一次就可以了
	{
		//if (child + 1 < _con.size() && _con[child] < _con[child + 1])
		if (child + 1 < _con.size() && compare()(_con[child], _con[child + 1]))
		{
			child++;
		}
		//if (_con[parent] < _con[child])
		if(compare()(_con[parent],_con[child]))
		{
			std::swap(_con[parent],_con[child]);
			parent=child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

在代码中都包含详细的解释,请读者耐心观看。

常用功能实现

cpp 复制代码
void push(const T& val)
{
	_con.push_back(val);
	AdjustUp(_con.size() - 1);
}
void pop()
{
	std::swap(_con[0], _con[_con.size() - 1]);
	_con.pop_back();
	AdjustDown(0);

}
bool empty() const
{
	return _con.size() == 0;
}
size_t size() const
{
	return _con.size();
}
const T& Top() const
{
	return _con.front();
}
void swap(priority_queue& pq)
{
	_con.swap(pq._con);
}

一些内容的补充:

什么是适配器

适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设 计经验的总结),该种模式是将一个类的接口转换成客户希望的另外一个接口

STL标准库中stackqueue****的底层结构

虽然stack和queue中也可以存放元素,但在STL中并没有将其划分在容器的行列,而是将其称为 容器适配器,这是因为stack和队列只是对其他容器的接口进行了包装,STL中stack和queue默认 使用deque,比如:

deque****的原理介绍

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

deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维数组

相关推荐
Tony Bai15 小时前
从 Go 迁移到 Rust
开发语言·后端·golang·rust
江屿风15 小时前
【C++笔记】string类流食般投喂
开发语言·c++·笔记
我是一颗柠檬15 小时前
【JDK8新特性】JDK8实战与面试高频考点汇总Day12
java·开发语言·后端·面试·职场和发展
wjs202415 小时前
C# 索引器(Indexer)
开发语言
Brilliantwxx15 小时前
【算法题】 面试级别的二叉树题目OJ复习(下)
数据结构·c++·算法·leetcode·面试·哈希算法·推荐算法
千寻girling15 小时前
机器学习 | 监督学习算法(了解) | 尚硅谷学习
开发语言·人工智能·后端·python·学习·算法·机器学习
阿方.91815 小时前
C++ string 超全精讲 | 从零使用、底层原理、手搓简易string、高频考点、易错点、面试手撕
开发语言·c++·字符串·string·知识分享
Chase_______15 小时前
【Java基础】5 / 2 为什么等于 2?整数除法、取余和 floorMod 一次讲清
java·开发语言
fish_xk15 小时前
c++11(二)
java·前端·c++