成都网站建设设计

将想法与焦点和您一起共享

【C++】优先级队列、仿函数和反向迭代器-创新互联

​🌠作者:@阿亮joy.
🎆专栏:《吃透西嘎嘎》
🎇座右铭:每个优秀的人都有一段沉默的时光,那段时光是付出了很多努力却得不到结果的日子,我们把它叫做扎根
在这里插入图片描述

天津ssl适用于网站、小程序/APP、API接口等需要进行数据传输应用场景,ssl证书未来市场广阔!成为创新互联建站的ssl证书销售渠道,可以享受市场价格4-6折优惠!如果有意向欢迎电话联系或者加微信:18980820575(备注:SSL证书合作)期待与您的合作!

目录
    • 👉priority_queue 的介绍👈
    • 👉priority_queue 的使用👈
    • 👉仿函数👈
    • 👉priority_queue 的模拟实现👈
    • 👉反向迭代器👈
    • 👉总结👈

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

代码示例:

#include// 优先级队列priority的头文件
#includeusing namespace std;
#include// greater算法的头文件

int main()
{// 默认情况下,创建的是大堆,其底层是按照小于号比较的
	priority_queuepq1; 
	pq1.push(3);
	pq1.push(2);
	pq1.push(8);
	pq1.push(5);
	pq1.push(1);

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

	// 如果要创建小堆,将第三个模板参数换成greater比较方式,其底层是按照大于号比较的
	priority_queue, greater>pq2;
	pq2.push(3);
	pq2.push(2);
	pq2.push(8);
	pq2.push(5);
	pq2.push(1);

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

	return 0;
}

在这里插入图片描述

如果在priority_queue中放自定义类型的数据,用户需要在自定义类型中提供 >或者< 的重载。

#include// 优先级队列priority的头文件
#include// greater算法的头文件
#includeusing namespace std;

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;
};
void TestPriorityQueue()
{// 大堆,需要用户在自定义类型中提供<的重载
	priority_queueq1;
	q1.push(Date(2018, 10, 29));
	q1.push(Date(2018, 10, 28));
	q1.push(Date(2018, 10, 30));
	while (!q1.empty())
	{cout<< q1.top()<< "  ";
		q1.pop();
	}
	cout<< endl;

	// 如果要创建小堆,需要用户提供>的重载
	priority_queue, greater>q2;
	q2.push(Date(2018, 10, 29));
	q2.push(Date(2018, 10, 28));
	q2.push(Date(2018, 10, 30));
	while (!q2.empty())
	{cout<< q2.top()<< "  ";
		q2.pop();
	}
	cout<< endl;
}

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

在这里插入图片描述

优先级的队列使用起来没有什么难的,接下来我们来做一道题目,顺便回顾一下堆。

数组中的第K个大元素

给定整数数组 nums 和整数 k,请返回数组中第 k 个大的元素。

请注意,你需要找的是数组排序后的第 k 个大的元素,而不是第 k 个不同的元素。

你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

在这里插入图片描述

解决这道题有两种思路,第一种就是用数组的元素建大堆,然后 pop 掉 k - 1 个元素,此时堆顶的元素就是第 k 大的数。这种思路的时间复杂度为O(N + k * lgN),当 N 很大时,就会需要非常多的空间。这时候,我们可以考虑第二种思路就是用前 k 个数建只包含 k 个数的小堆,再遍历数组剩下的元素,比堆顶元素大则替换掉堆顶元素,再向下调整堆。这种思路的时间复杂度为O(k + (N - k) * lgk)注:顶元素不允许修改,因为堆顶元素修改可以会破坏堆的特性。

class Solution 
{public:
    int findKthLargest(vector& nums, int k) 
    {// 建n个数的大堆
        priority_queuepq(nums.begin(), nums.end());
        // pop掉前k-1个大的数
        while(--k)  pq.pop();
        return pq.top();
    }
};

在这里插入图片描述

class Solution 
{public:
    int findKthLargest(vector& nums, int k) 
    {// 建k个数的小堆,从下标k开始遍历数组
        // 如果num[i]大于堆顶的数据,那么nums[i]入堆
        // 遍历结束,堆顶的数就是第k大的数
        priority_queue, greater>pq(nums.begin(), nums.begin() + k);
        for(size_t i = k; i< nums.size(); ++i)
        {if(nums[i] >pq.top())
            {pq.pop();
                pq.push(nums[i]);
            }
        }
        return pq.top();
    }
};

在这里插入图片描述

👉仿函数👈

仿函数也称为函数对象,它是具有operator()的类对象,可以想函数一样来使用它。

#includeusing namespace std;

// 仿函数/函数对象
templateclass Less
{public:
	templatebool operator()(const T& left, const T& right)
	{return left< right;
	}
};

templateclass Greater
{public:
	templatebool operator()(const T& left, const T& right)
	{return left >right;
	}
};

int main()
{// 仿函数用起来就像是函数
	// 有名对象
	LesslessFunc;
	cout<< lessFunc(1, 2)<< endl;
	// lessFunc(1, 2)<==>lessFunc.operator()(1, 2);
	
	// 匿名对象
	cout<< Greater()(1, 2)<< endl;

	return 0;
}

在这里插入图片描述

这样一看,好像仿函数也没有特别大的又是,和函数指针也没有什么大的区别嘛!其实不然,函数指针需要写函数的参数和返回值类型。所以仿函数用起来还是比函数指针舒服的。

#includeusing namespace std;

// 仿函数/函数对象
templateclass Less
{public:
	templatebool operator()(const T& left, const T& right)
	{return left< right;
	}
};

templateclass Greater
{public:
	templatebool operator()(const T& left, const T& right)
	{return left >right;
	}
};

templatevoid BubbleSort(T* a, int n, Compare com)
{for (int i = 0; i< n - 1; ++i)
	{int exchange = 0;
		for (int j = 1; j< n - i; ++j)
		{	// if (a[i]< a[i - 1])
			if (com(a[j], a[j - 1]))
			{		swap(a[j], a[j - 1]);
				exchange = 1;
			}
		}

		if (exchange == 0)
			break;
	}
}

int main()
{LesslessFunc;
	int arr[] = {2, 7,3, 1, 5, 9 ,10,20 };
	BubbleSort(arr, sizeof(arr) / sizeof(arr[0]), lessFunc);
	for (auto e : arr)
		cout<< e<< " ";
	cout<< endl;

	BubbleSort(arr, sizeof(arr) / sizeof(arr[0]), Greater());
	for (auto e : arr)
		cout<< e<< " ";
	cout<< endl;

	return 0;
}

在这里插入图片描述

注:没有成员变量的类的大小是 1 个字节,所以传参时加不加引用都没有关系。如果参数用 const 修饰了,那么仿函数也要用 const 来修饰。

如果库提供的仿函数不符合我们的需求,我们可以自己写!

#include// 优先级队列priority的头文件
#include// greater算法的头文件
#includeusing namespace std;

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, PDateLess>q3;
	q3.push(new Date(2018, 10, 29));
	q3.push(new Date(2018, 10, 28));
	q3.push(new Date(2018, 10, 30));
	while (!q3.empty())
	{cout<< *q3.top()<< "  ";
		q3.pop();
	}
	cout<< endl;

	priority_queue, PDateGreater>q4;
	q4.push(new Date(2018, 10, 29));
	q4.push(new Date(2018, 10, 28));
	q4.push(new Date(2018, 10, 30));
	while (!q4.empty())
	{cout<< *q4.top()<< "  ";
		q4.pop();
	}
	cout<< endl;
}

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

在这里插入图片描述

现在学习到的仿函数还只是冰山一角,以后还会有更多的仿函数要学!!!

👉priority_queue 的模拟实现👈
// PriorityQueue.h
#pragma once

namespace Joy
{// 仿函数
	templatestruct less
	{bool operator()(const T& left, const T& right)
		{	return left< right;
		}
	};

	templatestruct greater
	{bool operator()(const T& left, const T& right)
		{	return left >right;
		}
	};

	template, class Compare = less>class priority_queue
	{public:

		priority_queue()
			: _con()
		{}

		templatepriority_queue(InputIterator first, InputIterator last)
			: _con(first, last)
		{	// 注意:这里要使用int!如果使用size_t,如果只有一个元素会出现Bug
			int size = _con.size();
			for (int i = (size - 2) / 2; i >= 0; --i)
				adjust_down(i);
		}

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

		void pop()
		{	//swap(_con[0], _con[_con.size() - 1]);
			swap(_con.front(), _con.back());
			_con.pop_back();
			adjust_down(0);
		}

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

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

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

	private:
		Container _con;	// 底层容器

		// 向上调整算法
		void adjust_up(size_t child)
		{	size_t parent = (child - 1) / 2;

			while (child >0)
			{		
				//if(_con[parent]< _con[child])
				if (Compare()(_con[parent], _con[child]))	// 匿名对象
				{swap(_con[parent], _con[child]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
					break;
			}
		}

		void adjust_down(size_t parent)
		{	size_t child = 2 * parent + 1;	
			size_t size = _con.size();
			
			while (child< size)
			{		// 找出较大的孩子
				if (child + 1< size && Compare()(_con[child], _con[child + 1]))
					++child;

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

// Test.cpp
#include#includeusing namespace std;
#include "PriorityQueue.h"

int main()
{//priority_queuepq; 默认是大堆
	//Joy::priority_queuepq;
	Joy::priority_queue, greater>pq;
	pq.push(3);
	pq.push(2);
	pq.push(8);
	pq.push(5);
	pq.push(1);

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

	return 0;
}

在这里插入图片描述

优先级队列的实现就不讲解了,和用C语言大的区别就是不需要自己造轮子了,直接使用 vector 适配出优先级队列。还有就是使用了仿函数,可以根据传入的仿函数生成大堆还是小堆。如果还有相关的实现细节不了解的话,可以看这篇文章:堆的实现!

👉反向迭代器👈

方向迭代器其实也是一种适配器,它可以适配出各种容器的反向迭代器,其主要的是将正向迭代器进行了封装,反向迭代器 ++ 就复用正向迭代器的 - -,反向迭代器 - - 就复用正向迭代器的 ++。

#pragma once

templateclass ReverseInterator
{public:
	typedef ReverseInteratorSelf;

	ReverseInterator(Interator it)
		: _it(it)
	{}

	Self& operator++()
	{--_it;
		return *this;
	}

	Self operator++(int)
	{auto tmp = _it;
		--_it;
		return tmp;
	}

	Self& operator--()
	{++_it;
		return *this;
	}

	Self operator--(int)
	{auto tmp = _it;
		++_it;
		return tmp;
	}

	Ref operator*()
	{auto tmp = _it;
		return *(--tmp);
	}
	
	// 返回当前对象的地址
	Ptr operator->()
	{return &(operator*());
	}

	bool operator!=(const Self& s) const
	{return _it != s._it;
	}

	bool operator==(const Self& s) const
	{return _it == s._it;
	}

private:
	Interator _it;	// 对正向迭代器进行封装
};

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
因为rbegin()是用end()来构造的,rend()使用begin()来构造的,所以反向迭代器的解引用需要创建一个临时对象tmp,再返回*(--tmp)

测试反向迭代器

在这里插入图片描述

在这里插入图片描述

注:以上的反向迭代器是用我们自己模拟实现的 list 来测试的!

👉总结👈

本篇博客主要讲解了什么是优先级队列、优先级队列的使用和模拟实现、仿函数以及反向迭代器的实现等等。那么以上就是本篇博客的全部内容了,如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家!💖💝❣️

你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧


当前标题:【C++】优先级队列、仿函数和反向迭代器-创新互联
URL标题:http://chengdu.cdxwcx.cn/article/csoehd.html