C++——优先级队列

目录

  • 1.priority_queue的介绍和使用
    • 1.1 priority_queue的介绍
    • 1.2 priority_queue的使用
    • 1.3 在OJ中的使用
    • 1.4 priority_queue的模拟实现
      • 仿函数/函数对象
      • 向上调整
      • 向下调整

1.priority_queue的介绍和使用

1.1 priority_queue的介绍

  1. 优先级队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。
  2. 此上下文类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素)。
  3. 优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从特定容器的“尾部”弹出,其称为优先队列的顶部。
  4. 优先级队列被实现为容器适配器,容器适配器是使用特定容器类的封装对象作为其底层容器的类,提供一组特定的成员函数来访问其元素。元素只能从特定容器的顶部弹出。
  5. 底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过随机访问迭代器访问,并支持以下操作:
  • empty():检测容器是否为空
  • size():返回容器中有效元素个数
  • front():返回容器中第一个元素的引用
  • push_back():在容器尾部插入元素
  • pop_back():删除容器尾部元素
  1. 标准容器类vector和deque满足这些需求。默认情况下,如果没有为特定的priority_queue类实例化指定容器类,则使用vector
  2. 需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数make_heap、push_heap和pop_heap来自动完成此操作。

1.2 priority_queue的使用

优先级队列默认使用vector作为其底层存储数据的容器,在vector上又使用了堆算法将vector中元素构造成堆的结构,因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用priority_queue。注意:默认情况下priority_queue是大堆

函数声明 接口说明
priority_queue()/priority_queue(first,last) 构造一个空的优先级队列
empty( ) 检测优先级队列是否为空,是返回true,否则返回false
top( ) 返回优先级队列中最大(最小元素),即堆顶元素
push(x) 在优先级队列中插入元素x
pop() 删除优先级队列中最大(最小)元素,即堆顶元素

【注意】

  1. 默认情况下,priority_queue是大堆。
  2. 如果在priority_queue中放自定义类型的数据,用户需要在自定义类型中提供> 或者< 的重载

1.3 在OJ中的使用

数组中的第K个最大元素
思路:
1、建大堆:把数组中的n个数全部放到默认的优先级队列pq里,此时pq就是一个n个数的大堆,然后让优先级队列pop K次,此时堆顶的元素就是数组中的第K个最大元素。
2、建小堆:先把数组的前K个数push到优先级队列pq里(这里比较容易出错),然后从K+1个数开始遍历数组,如果遇到比pq的堆顶元素大的数,那么就先把堆顶pop掉然后把这个数push到pq里,这样遍历到最后一个数,此时pq中的K个元素就是前K大的K个数,堆顶就是数组中的第K个最大元素。
代码:

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        // //用默认的去 建大堆,然后pop  k次
        // priority_queue pq (nums.begin(),nums.end());
        // //找第k个最大的数pop  k-1次。
        // while(--k)
        // {
        //     pq.pop();
        // }

        // return pq.top();


        //建小堆
        priority_queue<int,vector<int>,greater<int>> pq 
        (nums.begin(),nums.begin()+k);
        //容易出错的地方nums.begin()到nums.begin()+k,才是k个元素
        
        for(int i=k;i<nums.size();i++)
        {
            if(nums[i]>pq.top())
            {
                pq.pop();
                pq.push(nums[i]);
            }

        }

        return pq.top();
    }
};

1.4 priority_queue的模拟实现

通过对priority_queue的底层结构就是堆,因此此处只需对vector或者deque这样的容器进行通用的封装,把他们按照堆的模式封装即可。

仿函数/函数对象

仿函数(functor),就是使一个类的使用看上去像一个函数。其实现就是类中实现一个operator(),也就是对于()的运算符重载,这个类就有了类似函数的行为,就是一个仿函数类了。目的是为了代替C语言中的函数指针
运用在排序中用户可以自己定义升序或者降序。
在这里插入图片描述
对应到堆里面就是大堆或是小堆。
在这里插入图片描述

标准库仿函数的模拟实现:

//仿函数/函数对象
template<class T>
class less
{
public:
	bool operator()(const T& x, const T& y) const
	{
		return x < y;
	}
};

template<class T>
class greater
{
public:
	bool operator()(const T& x, const T& y) const
	{
		return x > y;
	}
};
  • 对于内置类型,天然的就支持<和>这样比大小的操作。
  • 对于自定义类型,如果类里重载了<和>运算符,支持类与类之间的<和>操作,并且功能符合预期,那下面代码中的<和>就转化为对应的运算符重载进行比较即可;若功能不满足我们的预期,那么需要自己去实现对应的仿函数。

功能不满足预期的举例:

//日期类
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}

	bool operator<(const Date& d)const
	{
		return (_year < d._year) ||
			(_year == d._year && _month < d._month) ||
			(_year == d._year && _month == d._month && _day < d._day);
	}

	bool operator>(const Date& d)const
	{
		return (_year > d._year) ||
			(_year == d._year && _month > d._month) ||
			(_year == d._year && _month == d._month && _day > d._day);
	}

	friend ostream& operator<<(ostream& _cout, const Date& d)
	{
		_cout << d._year << "-" << d._month << "-" << d._day;
		return _cout;
	}

private:
	int _year;
	int _month;
	int _day;
};


struct PDateLess//需要自己写仿函数,这里是自己实现的仿函数
{
	bool operator()(const Date* d1, const Date* d2)
	{
		return *d1 < *d2;
	}
};

struct PDateGreater//需要自己写仿函数,这里是自己实现的仿函数
{
	bool operator()(const Date* d1, const Date* d2)
	{
		return *d1 > *d2;
	}
};


void TestPriorityQueue()
{
	// 大堆
	priority_queue<Date*, vector<Date*>, PDateLess> q3;
	//定义类的时候用的是PDateLess——仿函数的类型
	q3.push(new Date(2018, 10, 29));
	q3.push(new Date(2018, 10, 28));
	q3.push(new Date(2018, 10, 30));
	cout << *q3.top() << endl;

	// 小堆
	priority_queue<Date*, vector<Date*>, PDateGreater> q4;
	//定义类的时候用的是PDateGreater——仿函数的类型
	q4.push(new Date(2018, 10, 29));
	q4.push(new Date(2018, 10, 28));
	q4.push(new Date(2018, 10, 30));
	cout << *q4.top() << endl;
}

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

仿函数的使用:不管是用库里面的还是自己实现的仿函数,在定义一个类的时候,用的是仿函数的类型(一般类名就是类型,但是在模板类中需要加上模板参数才是一个完整的类型),在类里会拿这个类型去实例化一个对象,通过这个对象来调用对于()的运算符重载,以此来达到控制类里对应的行为。

下面是类里向上调整和向下调整的代码:

向上调整

void adjust_up(size_t child)
{
	Compare com;//实例化一个对象com
	size_t parent = (child - 1) / 2;
	while (child > 0)
	{
		//if (_con[parent] < con[child])
		if (com(_con[parent], _con[child]))//通过这个对象com来调用对于()的运算符重载
		{
			swap(_con[child], _con[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

向下调整

void adjust_down(size_t parent)
{
	Compare com;//实例化一个对象com
	size_t child = parent * 2 + 1;
	while (child < _con.size())
	{
		//if (child+1 < _con.size() && _con[child] < _con[child+1])
		if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))//通过对象com来调用对于()的运算符重载
		{
			child++;
		}

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

如果对堆其他知识有不清楚的老铁可以看这篇文章
(C语言)数据结构二叉树之堆

你可能感兴趣的:(C++,c++,java,算法)