<C++> priority_queue模拟实现

目录

前言

一、priority_queue的使用

1. 成员函数

2. 例题

二、仿函数

三、模拟实现

1.  迭代器区间构造函数 && AdjustDown

2. pop

3.  push && AdjustUp

4. top

5. size

6. empty

 四、完整实现

总结


前言

        优先级队列以及前面的双端队列基本上已经脱离了队列定义,只是占了队列名字

优先级队列——priority_queue

1. 优先级队列是一种容器适配器,根据一些严格的弱排序标准,经过专门设计,其第一个元素始终是它所包含的最大元素。

2. 此上下文类似于堆,其中元素可以随时插入,并且只能检索最大堆元素(优先级队列中顶部的元素)。

3. 优先级队列作为容器适配器实现,容器适配器是使用特定容器类的封装对象作为其基础容器的类,提供一组特定的成员函数来访问其元素。元素从特定容器的“背面”弹出,这称为优先级队列的顶部

4. 底层容器可以是任何标准容器类模板,也可以是一些其他专门设计的容器类。容器应可通过随机访问迭代器访问,并支持以下操作:

  • empty()
  • size()
  • front()
  • push_back()
  • pop_back()


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

6. 需要支持随机访问迭代器,以便始终在内部保持堆结构。这是由容器适配器通过自动调用算法函数 make_heap、push_heap、pop_heap 自动完成的,并在需要时完成。

注意:

  • priority_queue还是适配器,但是适配的是vector
  • 底层是二叉树的堆
  • priority_queue仍然包括在queue头文件中 
  • 默认大堆
  • Compare的缺省是less,表示的是大根堆

        可以使用仿函数修改为小根堆

priority_queue<int, deque<int>, greater<int>> pq;

一、priority_queue的使用

1. 成员函数

2. 例题

215. 数组中的第K个最大元素

方法一:直接使用sort排序

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) 
    {
        sort(nums.begin(), nums.end(), greater<int>());
        return nums[k - 1];
    }
};

 方法二:优先级队列,建大堆

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) 
    {
        priority_queue<int> pq(nums.begin(), nums.end());
        while (--k)
        {
            pq.pop();
        }
        return pq.top();
    }
};

方法三:维护一个有K个数据的小堆,遍历nums数组,若比top()值大,就入堆,最后返回top()数据

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) 
    {
        priority_queue<int, vector<int>, greater<int>> pq(nums.begin(), nums.begin() + k);
        for (int i = k; i < nums.size(); ++i)
        {
            if (nums[i] > pq.top())
            {
                pq.pop();
                pq.push(nums[i]);
            }
        }

        return pq.top();
        
    }
};

注意:

        sort函数最后一个参数是greater<int>(),是一个匿名对象,而在priority_queue<>内部,第三个模板是greater<int>,是类型,不能加括号

二、仿函数

        我们在之前就遇到过sort函数如果想实现降序,需要加上仿函数greater<类型>(),那么什么是仿函数呢?它又有什么作用?

我们来看一个简单的例子:

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

int main()
{     
    Fun func;
    cout << func(1, 2) << endl;
    //等价于
    cout << func.operator()(1, 2) << endl;

    return 0;
}
  • 这里的Fun就是仿函数,由Fun类定义的对象称为函数对象
  • 仿函数,顾名思义,一个类的对象可以像函数一样使用,它替代了c语言里的函数指针,我们只需要重载 () 符号就可以像使用函数一样调用它的 () 运算符重载函数

那么这样的仿函数有什么作用呢?

  •  替代c语言的函数指针,因为c语言的函数指针很复杂、容易出错
  • 搭配模板可以实现多类型的函数运算,不会将函数“写死”,例如:我们写Less、Greater类,重载()符号,在需要的地方实例化函数对象,如果有比较大小的情况就使用函数对象(参数1,参数2),原理就是调用operator()函数,像函数一样调用

下面是priority_queue带上第三个模板参数Less后使用仿函数的代码:

template<class T>
class Less
{
public:
	bool operator()(const T& a, const T& b)
	{
		return a < b;
	}
};

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

namespace my_priority_queue
{
	// Less<T> 才是Less类的类型
	template<class T, class Container = vector<T>, class Compare = Less<T>>
	class priority_queue
	{
	private:
		//大堆,向下调整
		void AdjustDown(int parent)
		{
			//创建函数对象,Less模板类型就是<比较,Greater模板类型就是>比较
			Compare com;

			//找左右孩子中最大的哪一个
			int child = parent * 2 + 1;
			while (child < _con.size())
			{
				//因为com是比较小于关系,所以需要将原先大于的表达式逆一下
				
				//判断右孩子是否存在
				/*if (child + 1 < _con.size() && _con[child + 1] > _con[child])*/
				if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
				{
					++child;
				}

				//if (_con[child] > _con[parent])
				if (com(_con[parent], _con[child]))
				{
					swap(_con[parent], _con[child]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}

		}

		void AdjustUp(int child)
		{
			Compare com;

			int parent = (child - 1) / 2;
			while (child > 0)	//最坏情况:孩子等于0时结束
			{
				if (com(_con[parent], _con[child]))
				{
					swap(_con[parent], _con[child]);
					child = parent;
					parent = child - 1 >> 1;
				}
				else
				{
					break;
				}
			}
		}

	public:
		template<class InputIterator>	//input只写迭代器

		//迭代器区间构造函数
		priority_queue(InputIterator first, InputIterator last)
		{
			while (first != last)
			{
				_con.push_back(*first);
				++first;
			}

			//建堆
			for (int i = (_con.size() - 1 - 1) / 2; i >= 0; --i )
			{
				AdjustDown(i);
			}
		}

		void pop()
		{
			//交换后,尾删,并向下调整
			swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
			AdjustDown(0);
		}

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

	private:
		//容器
		Container _con;
	};
}

        如果比较的类型是其他自定义类型,并且该类没有重载operator<函数,那么我们就需要手写一个仿函数进行比较,否则编译错误,无法进行比较

       因为库里的仿函数使用了模板,是什么类型就按什么类型相比,那么如果是new返回的指针类型,由于每次new返回的地址相当于是随机的,又比较的是指针类型的大小,所以比较结果也是随机结果。所以我们还是需要写仿函数,修改比较方式,去比较指针指向的内容即可。

三、模拟实现

编译错误,找不到出错位置怎么办?

        如果代码编译错误,找不到在哪,那么逐步屏蔽掉一些代码,逐步排查,是十分有效的找到错误处方法

        priority_queue也同queue、stack一样,是适配器,适配vector容器,要理解这个思想,封装一个东西为我所用,就跟list里的iterator类一样,为了实现解引用直接得到val值,而封装了Node*成为iterator类,后面的反向迭代器也是同样的道理,有了成品的正向iterator,我们就可以封装正向迭代器为我们所用,从而实现反向迭代器。

1.  迭代器区间构造函数 && AdjustDown

  • 采用封装容器vector的push_back尾插数据,因为默认是大堆,向下建堆,所以尾插数据后,将从最后一个元素的父亲结点—— (_con.size() - 1 - 1) / 2 开始向下调整。
  • 向下调整函数不用多说,在二叉树部分我们详细讲解过
namespace my_priority_queue
{
	// Less<T> 才是Less类的类型
	template<class T, class Container = vector<T>, class Compare = Less<T>>
	class priority_queue
	{
	private:
		//大堆,向下调整
		void AdjustDown(int parent)
		{
			//创建函数对象,Less模板类型就是<比较,Greater模板类型就是>比较
			Compare com;

			//找左右孩子中最大的哪一个
			int child = parent * 2 + 1;
			while (child < _con.size())
			{
				//因为com是比较小于关系,所以需要将原先大于的表达式逆一下
				
				//判断右孩子是否存在
				/*if (child + 1 < _con.size() && _con[child + 1] > _con[child])*/
				if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
				{
					++child;
				}

				//if (_con[child] > _con[parent])
				if (com(_con[parent], _con[child]))
				{
					swap(_con[parent], _con[child]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}

		}

	public:
        //迭代器区间构造函数
		template<class InputIterator>	//input只写迭代器
		priority_queue(InputIterator first, InputIterator last)
		{
			while (first != last)
			{
				_con.push_back(*first);
				++first;
			}

			//建堆
			for (int i = (_con.size() - 1 - 1) / 2; i >= 0; --i )
			{
				AdjustDown(i);
			}
		}


	private:
		//容器
		Container _con;
	};
}

 2. pop

  • 堆的pop,将堆顶数据与最后一个数据进行交换,再进行pop_back,再将堆顶数据向下调整
		void pop()
		{
			//交换后,尾删,并向下调整
			swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();

			AdjustDown(0);
		}

 3.  push && AdjustUp

  • 尾插数据后,将该数据向上调整
		void AdjustUp(int child)
		{
			Compare com;

			int parent = (child - 1) / 2;
			while (child > 0)	//最坏情况:孩子等于0时结束
			{
				if (com(_con[parent], _con[child]))
				{
					swap(_con[parent], _con[child]);
					child = parent;
					parent = child - 1 >> 1;
				}
				else
				{
					break;
				}
			}
		}

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

 4. top

  • 返回堆顶数据,返回值是 const T&
		const T& top()
		{
			return _con[0];
		}

 5. size

  • 返回vector的size()即可
		size_t size()
		{
			return _con.size();
		}

 6. empty

  • 直接调用vector的empty函数即可
		size_t size()
		{
			return _con.empty();
		}

 四、完整实现

#pragma once
#include<iostream>
#include<vector>
#include<functional>
using namespace std;

template<class T>
class Less
{
public:
	bool operator()(const T& a, const T& b)
	{
		return a < b;
	}
};

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

namespace my_priority_queue
{
	// Less<T> 才是Less类的类型
	template<class T, class Container = vector<T>, class Compare = Less<T>>
	class priority_queue
	{
	private:
		//大堆,向下调整
		void AdjustDown(int parent)
		{
			//创建函数对象,Less模板类型就是<比较,Greater模板类型就是>比较
			Compare com;

			//找左右孩子中最大的哪一个
			int child = parent * 2 + 1;
			while (child < _con.size())
			{
				//因为com是比较小于关系,所以需要将原先大于的表达式逆一下
				
				//判断右孩子是否存在
				/*if (child + 1 < _con.size() && _con[child + 1] > _con[child])*/
				if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
				{
					++child;
				}

				//if (_con[child] > _con[parent])
				if (com(_con[parent], _con[child]))
				{
					swap(_con[parent], _con[child]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}

		}

		void AdjustUp(int child)
		{
			Compare com;

			int parent = (child - 1) / 2;
			while (child > 0)	//最坏情况:孩子等于0时结束
			{
				if (com(_con[parent], _con[child]))
				{
					swap(_con[parent], _con[child]);
					child = parent;
					parent = child - 1 >> 1;
				}
				else
				{
					break;
				}
			}
		}

	public:

		priority_queue()
		{}

		//迭代器区间构造函数
		template<class InputIterator>	//input只写迭代器
		priority_queue(InputIterator first, InputIterator last)
		{
			while (first != last)
			{
				_con.push_back(*first);
				++first;
			}

			//建堆
			for (int i = (_con.size() - 1 - 1) / 2; i >= 0; --i )
			{
				AdjustDown(i);
			}
		}

		void pop()
		{
			//交换后,尾删,并向下调整
			swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();

			AdjustDown(0);
		}

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

			AdjustUp(_con.size() - 1);
		}

		const T& top()
		{
			return _con[0];
		}

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

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

	private:
		//容器
		Container _con;
	};
}

void test_priority_queue1()
{
	// 默认是大堆 -- less
	//priority_queue<int> pq;

	// 仿函数控制实现小堆
	my_priority_queue::priority_queue<int, vector<int>, Greater<int>> pq;

	pq.push(3);
	pq.push(5);
	pq.push(1);
	pq.push(4);

	while (!pq.empty())
	{
		cout << pq.top() << " ";
		pq.pop();
	}
	cout << endl;
}

总结

        priority_queue优先级队列就是存储在vector内的堆,掌握向上向下调整函数,可以回顾之前的文章堆的实现一节。

        最后,如果小帅的本文哪里有错误,还请大家指出,请在评论区留言(ps:抱大佬的腿),新手创作,实属不易,如果满意,还请给个免费的赞,三连也不是不可以(流口水幻想)嘿!那我们下期再见喽,拜拜!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在优先队列,底层存储数据的容器默认是vector,因此优先队列的数组实现就是使用vector作为底层容器来存储数据。\[1\] 优先队列通过堆算法将vector的元素构造成堆的结构,实现了优先级的排序。默认情况下,优先队列是大堆,即堆顶元素是最大的数据。\[1\] 优先队列的模板声明带有三个参数,分别是数据类型、保存数据的容器和元素比较方式。容器必须是用数组实现的容器,比如vector、deque,而STL默认使用的是vector作为容器。比较方式默认使用operator<来进行比较,所以如果不指定后面两个参数,优先队列就是大顶堆,队头元素最大。\[2\] #### 引用[.reference_title] - *1* *2* [C++优先级队列priority_queue详解及其模拟实现](https://blog.csdn.net/qq_61635026/article/details/125807805)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v4^insert_chatgpt"}} ] [.reference_item] - *3* [priority_queue(优先级队列)的用法,模拟实现priority_queue(堆排序、仿函数)](https://blog.csdn.net/qq_46131499/article/details/120731928)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v4^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值