C++——优先队列

今天,我们来学习一下优先队列。

一:优先队列的认识

首先,我们来认识一下什么是优先队列?它与我们之前写的队列有什么区别呢?

看看文档中的priority_queue说明:

<queue> - C++ Reference

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

其中,我们在c语言阶段也写过堆了,有兴趣的伙伴可以自行去了解:

数据结构——二叉树——堆(1)-优快云博客


 

简单认识priority_queue的接口

总括:有了我们之前对各种接口的认识,现在看到这些接口名字应该可以大致明白什么意思和如何使用了。

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

 好了,简单看了它们的接口后,现在我们来正式来模拟实现一下:

模拟实现

(一)priority_queue成员变量

1.由上面我们知道,priority_queue的底层容器是用vector或者deque来实现的。为了方便,我们不妨使用一个模板来实现,到我们使用时去决定调用vector还是deque自己决定即可。

namespace bai
{
	template<class T, class Container = vector<T>, class Cmp = less<T>>
    private:
		Container _con;
};

(二)priority_queue 构造函数

1.这里我们使用迭代器区间来进行初始化。即,用这个迭代器区间的元素插入到这个堆,然后再根据优先队列本质上是堆,来建成大堆。

2.而我们又知道迭代器的类型有多种。那么,这里利用模板来自由控制迭代器的类型。

C语言阶段的向下调整的思路代码: 

 

向下调整部分 

        void Adjustdown(int parent)
		{
			Cmp com;
            //父亲与孩子结点之间的关系
			int child = parent * 2 + 1;
		
			while (child<_con.size())
			{
                //由于我们上面用了类模板的比较,所以变更加灵活,
                //主要看自己实现时控制的 是less还是greater
				if (child + 1 < _con.size() && com(_con[child] , _con[child + 1]))
				{
					child=child +1;
				}
				if (com(_con[parent],_con[child] ))
				{
                    //交换,更新
					swap(_con[child], _con[parent]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}

 1.为什么int i = (_con.size() - 1 - 1) / 2?

答:我们在C语言阶段也有解释过,_con.size() - 1是最后一个元素的下标(即孩子),又因为i找的是父亲的结点。它与孩子结点的关系是:parent=(child-1)/2。

template<class Inputiterator>
Priority_Queue(Inputiterator frist, Inputiterator last)
{
    //插入迭代器区间元素
    while (frist != last)
    {
		_con.push_back(*frist);
		frist++;
	}
			
	//建大堆
    
	for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--)
	{
		Adjustdown(i);
	}

}

无参构造函数:

为什么还要写一个无参构造函数呢?

因为我们上面使用了迭代器区间来初始化了,所以编译器就不会自动帮你生成一个 无参的构造函数,所以在我们后边要定义无参的优先队列变量得自己去实现。

Priority_Queue()
{}

(三)push插入部分

这部分的整体思路比较简单,因为底层是vector(这里也可以list)所以直接调用它们的接口插入元素。接入元素后,原来的大堆就可能变乱了,所以重新向上调整法建大堆。 

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

向上调整法

 

void Adjustup(int child)
{
	Cmp com;
	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;
		}
	}
}

(四)pop删除部分

思路:看上面 

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

 (五)返回top堆顶元素

1.即第一个元素 

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

(六)返回size大小

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

 (七)判断空

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

好了,优先队列的模拟实现基本就完成了。

附上完整代码和测试代码:

namespace bai
{
	template<class T, class Container = vector<T>, class Cmp = less<T>>
	class Priority_Queue
	{
	private:
		void Adjustdown(int parent)
		{
			Cmp com;
			int child = parent * 2 + 1;
		
			while (child<_con.size())
			{
				if (child + 1 < _con.size() && com(_con[child] , _con[child + 1]))
				{
					child=child +1;
				}
				if (com(_con[parent],_con[child] ))
				{
					swap(_con[child], _con[parent]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}

		void Adjustup(int child)
		{
			Cmp com;
			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;
				}
			}
		}

	public:
		Priority_Queue()
		{}

		template<class Inputiterator>
		Priority_Queue(Inputiterator frist, Inputiterator last)
		{
			while (frist != last)
			{
				_con.push_back(*frist);
				frist++;
			}
			
			//
			for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--)
			{
				Adjustdown(i);
			}

		}


		void push(const T& val)
		{
			_con.push_back(val);
			Adjustup(_con.size() - 1);
		}
		void pop()
		{
			swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
			Adjustdown(0);
		}

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

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

	private:
		Container _con;
	};

	void Test_priority_queue()
	{
		Priority_Queue<int, vector<int>,Greater<int>> pq;
		pq.push(3);
		pq.push(5);
		pq.push(1);
		pq.push(4); 
		//pq.pop();
		while (!pq.empty())
		{
			cout << pq.top() << " ";
			pq.pop();
		}
		cout << endl;

	}

但是,文章还没有结束,我们还额往补充一些知识:

仿函数:

1.概念:一种行为类似函数的对象。它是通过定义一个类,并重载()运算符来实现,这样该类的对象就可以像函数一样使用被调用。

2.举例子:

1)简单的仿函数:

2)仿函数用于排序:

这里Greater仿函数类重载了()运算符,它返回一个布尔值,用于告诉sort函数如何比较两个元素,从而实现降序排序。 

好了,现在我们再以日期的比较再次认识仿函数:

class Date
	{
	public:
		Date(int year = 1999, int month = 7, int day = 10)
			:_year(year)
			,_month(month)
			,_day(day)
		{}

		bool operator<(const Date& d1)const
		{
			return (_year < d1._year)
				|| (_year == d1._year && _month < d1._month)
				|| (_year == d1._year && _month == d1._month && _day < d1._day);
		}
		bool operator ==(const Date& d1)const
		{
			return (_year == d1._year) 
				&& (_month == d1._month) 
				&& (_day == d1._day);
		}
		bool operator>(const Date& d1)const
		{
			return (_year > d1._year)
				|| (_year == d1._year && _month > d1._month)
				|| (_year == d1._year && _month == d1._month && _day > d1._day);
		}
		friend 	ostream& operator<<(ostream& out,const Date& d);


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

	//仿函数的用法
	struct LessDate
	{
		bool operator()(const Date* p1,const Date* p2)
		{
			return (*p1) < (*p2);
		}
	};

	void test_Date()
	{
		Priority_Queue<Date*, vector<Date*>,LessDate> pq;
		pq.push(new Date(1999, 1, 1));
		pq.push(new Date(1999, 1, 5));
		pq.push(new Date(1999, 1, 3));
		pq.push(new Date(1999, 1, 2));
		while (!pq.empty())
		{
			cout << *pq.top() << " ";
			pq.pop();
		}
		cout << endl;
	}

我们发现如果没有加入仿函数的话,日期的比较是错误的 

在C++ 中,通常自定义的日期类如果没有重载比较运算符,那么直接比较对象时,比较的是对象在内存中的地址。但一般情况下,我们会重载比较运算符(如 == 、 < 、 >  等)来按照日期的实际值进行比较,比如按照年、月、日的顺序依次比较

 

 

好了,本次分享就先到这里了,希望大家都有所进步!

最后,到了本次鸡汤环节:

图片文字与大家共勉!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值