模拟实现一个双端队列(理解使用仿函数)

双端队列是什么?

首先我们来看一下库中的双端队列能够调用的接口有哪些

可以看到它的成员函数就只有上面的几个,如果学过数据结构那么双端队列(priority_queue)其实就是一个堆,如果你没有学过双端队列,那么下面我们先来使用以下库中给我们的双端队列是怎么样运行的。

可以看到我们传到dp中的数字按照从大到小的优先级顺序被从双端队列中删除了。也就是每一次都取堆顶的元素,然后删除堆顶的元素。

那么能不能让顺序变成 1 3 5 6呢?肯定是可以的。

那么这是为什么呢?待会我们就会知道了。

模拟实现双端队列(priority_queue)

既然双端队列在使用上就是一个堆,那么要模拟实现双端队列,那么底层容器,自然就需要使用vector(或者使用deque也可以,能支持下标访问)。

那么首先让我们来看一看我们要是实现的这个双端队列的基本结构。

下面来完成成员函数的实现

默认构造和迭代器区间初始化

namespace LHY
{

    #include<vector>
	template<class T,class Container = vector<T>,class Compare = less<T>>
	class priority_queue
	{
		priority_queue()
		{}//即使你什么也不写,对于内置的自定义成员,编译器会自动的调用自定义成员的默认构造函数
	  template<class InputIterator>
			priority_queue(InputIterator first, InputIterator last)
			:_con(first, last)
		{
			for (int i = (_con.size() - 2) / 2; i >= 0; --i)
			{
				adjustdown(i);
			}
		}//这里是使用了初始化队列来调用_con的迭代器区间初始化的方式

	private:
		Container _con;
		Compare _com;
	};
}

这里需要记住的知识点为:如果成员为自定义类型,那么编译器会帮我们调用自定义类型的默认构造函数,析构也是一样的,可以使用初始化列表来调用成员中的特定初始化函数。以及对于模板函数不需要显示实例化,因为编译器可以通过传入的形参来推断类型,但是模板类就不可以。

push函数

push函数的功能肯定就是将一个值放到双端队列中,那么既然双端队列本质是一个大堆,那么往堆中放数据,其实也就是将数据放到vector的最后一位然后,使用向上调整算法调整一次即可。

向上调整算法:

	void adjustup(int child)
		{
			int parent = (child - 1) / 2;
			while (child > 0)
			{
				if (_con[child]>_con[parent])
				{
					swap(_con[child], _con[parent]);
					child = parent;
					parent = parent - 1 / 2;
				}
				else
				{
					break;
				}
			}
		}//这里建立的是大堆

向下调整算法:

void adjustdown(int parent)
		{
			int child = 2 * parent + 1;

			while (child<_con.size())
			{
				if (child + 1 < _con.size() && _con[child + 1] > _con[child])
				{
					child++;
				}//选择左右孩子中大的那一个
				if (_con[child] > _con[parent])
				{
					swap(_con[child], _con[parent]);//交换孩子和父亲节点
					parent = child;
					child = 2 * parent + 1;
				}
				else
				{
					break;
				}
			}
		}

如果你对这两个算法的感到有不理解的地方,请看我的这篇博客:​​​​https://blog.51cto.com/u_15838996/6327521​

那么有了这两个函数之后就可以来进行push函数的编写了。

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

重点知识:回顾向上和向下调整算法,堆插入数据的方法。

pop函数

既然双端队列是按照优先级顺序删除元素的,那么要删除元素肯定是要将顶部得元素先和最后的元素交换,交换之后让vector删除最后一个元素,在使用一次向下调整算法重新建堆即可。

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

重点知识:理解堆是如何删除数据的

top函数

这个函数就很简单了直接返回vevtor中位置为0的数据即可

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

size函数和判空函数

这两个函数更加的简单,只需要返回vector中的size函数,以及调用vector的empty函数就可以了

bool empty() const
		{
			return _con.empty();
		}
size_t size() const
		{
			return _con.szie();
		}

经过上面的步骤我们就模拟实现了一个属于我们的一个不太完善的双端队列。让我们来调用尝试一下。

可以看到成功了,但是如果我是想要建立一个小堆呢?如果没有仿函数的话,要做到这一点只有一个方法,那就是修改向上和向下调整的代码,但是这显然不是一个好方法。所以为了让我们能够和系统调用一样,只用修改模板实例化的参数就能够实现将建立大堆变成建立小堆。就让我们来实现和学习一下仿函数的用法。

学习使用仿函数

首先我们要知道仿函数的本质不是一个函数,而是一个类。下面我们来实现一个最简单的仿函数。

一个比较大小的仿函数。

class My_Compare
{
public:
	bool operator()(int x, int y)
	{
		return x > y;
	}//在这个类里面重载()操作符
};
void testmy_com()
{
	My_Compare com;
	cout << com(1, 2) << endl;//本质就是下面的那个式子
	cout << com.operator()(1, 2) << endl;//编译器底层调用
}

上面就是创建一个类然后使用这个类

然后让我们看看调用的结果:

那么由此就完成了一个很简单的仿函数,但是这个仿函数却只能比较int类型的,那么能不能加入模板呢?答案当然是可以

template<class T>
class My_Compare
{
public:
	bool operator()(const T& x, const T& y)
	{
		return x > y;
	}
};

这样我们在使用一下这个仿函数

只需要修改一下实例化的参数,就可以比较double类型

使用这个仿函数就能够实现由用户决定是建立大堆还是建立小堆

下面就来看一看仿函数可以修改哪些地方。

	void adjustup(int child)
		{
			int parent = (child - 1) / 2;
			while (child > 0)
			{
				//if (_con[child] > _con[parent])//下面的功能和上面这个是一样的。
				if(_com(_con[child],_con[parent]))
        //把这里的比较换成了使用实例化的一个仿函数比较
				{
					swap(_con[child], _con[parent]);
					child = parent;
					parent = (parent - 1) / 2;
				}
				else
				{
					break;
				}
			}
		}
		void adjustdown(int parent)
		{
			int child = 2 * parent + 1;

			while (child<_con.size())
			{
				if (child + 1 < _con.size() && _com(_con[child + 1],_con[child]))
        //把这里的比较换成了使用实例化的一个仿函数比较
				{
					child++;
				}
				if (_com(_con[child],_con[parent]))
        //把这里的比较换成了使用实例化的一个仿函数比较
				{
					swap(_con[child], _con[parent]);
					parent = child;
					child = 2 * parent + 1;
				}
				else
				{
					break;
				}
			}
		}

因为我在实现的时候,如果用户不传入额外的显示实例化,那么就会采用默认的less类,那么最后建立的就是大堆,如果用户最后显示实例化的时候写了我所定义的第二个greater函数,那么优先级顺序就会被修改。

那么我们能否传入我们自己写的仿函数呢?当然可以我们来看一下下面的这个例子:

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()
{
	// 大堆,需要用户在自定义类型中提供<的重载
	LHY::priority_queue<Date> q1;
	q1.push(Date(2018, 10, 29));
	q1.push(Date(2018, 10, 28));
	q1.push(Date(2018, 10, 30));
	cout << q1.top() << endl;
	LHY::priority_queue<Date*, vector<Date*>, greater<Date*>> q2;
	q2.push(new Date(2018, 10, 29));
	q2.push(new Date(2018, 10, 28));
	q2.push(new Date(2018, 10, 30));
	cout << *(q2.top()) << endl;
}

可以看到在将一个日期类放到双端队列中后,最后返回的是日期最大的那个对象。因为在日期类中定义了日期对象的大小比较。

但是如果我是将一个日期类的地址放到双端队列中呢?也就是将上面注释的代码解注释。

可以看到这里就出现了问题,那是为什么呢?因为我们在我们的命名空间中所写的那个仿函数是不具有将指针解引用然后比较的功能的,所以这里就是按照三个对象new出来的空间所在的序列大小进行的比较,所以会出现随机的答案。如何结局呢?

很简单就是我们自己写一个能够解引用比较的仿函数。

class My_com
{
public:
	bool operator()(Date* x, Date* y)
	{
		return (*x) > (*y);
	}
};

然后再下面将这个仿函数显示实例化

void TestPriorityQueue()
{
	// 大堆,需要用户在自定义类型中提供<的重载
	LHY::priority_queue<Date> q1;
	q1.push(Date(2018, 10, 29));
	q1.push(Date(2018, 10, 28));
	q1.push(Date(2018, 10, 30));
	cout << q1.top() << endl;
	LHY::priority_queue<Date*, vector<Date*>, My_com> q2;
	q2.push(new Date(2018, 10, 29));
	q2.push(new Date(2018, 10, 28));
	q2.push(new Date(2018, 10, 30));
	cout << *(q2.top()) << endl;
}

这样运行出来的代码就不会出现任何的错误了

完整的双端队列代码

namespace LHY
{
	template<class T>
	class less
	{
	public:
		bool operator()(const T& x, const T& y)
		{
			return x > y;
		}
	};
	template<class T>
	class greater
	{
	public:
		bool operator()(const T& x, const T& y)
		{
			return x < y;
		}
	};
	template<class T, class Container = vector<T>, class Compare = less<T>>
	class priority_queue
	{
	public:
		void adjustup(int child)
		{
			int parent = (child - 1) / 2;
			while (child > 0)
			{
				//if (_con[child] > _con[parent])
				if(_com(_con[child],_con[parent]))
				{
					swap(_con[child], _con[parent]);
					child = parent;
					parent = (parent - 1) / 2;
				}
				else
				{
					break;
				}
			}
		}
		void adjustdown(int parent)
		{
			int child = 2 * parent + 1;

			while (child<_con.size())
			{
				if (child + 1 < _con.size() && _com(_con[child + 1],_con[child]))
				{
					child++;
				}
				if (_com(_con[child],_con[parent]))
				{
					swap(_con[child], _con[parent]);
					parent = child;
					child = 2 * parent + 1;
				}
				else
				{
					break;
				}
			}
		}
		void push(const T& x)
		{
			_con.push_back(x);
			adjustup(_con.size()-1);
		}
		void pop()
		{
			std::swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
			adjustdown(0);
		}
		 const T& top()
		{
			return _con[0];
		}
		bool empty() const
		{
			return _con.empty();
		}
		size_t size() const
		{
			return _con.size();
		}
		priority_queue()
		{}//即使你什么也不写,对于内置的自定义成员,编译器会自动的调用自定义成员的默认构造函数
		template<class InputIterator>
		priority_queue(InputIterator first, InputIterator last)
			:_con(first, last)
		{
			for (int i = (_con.size() - 2) / 2; i >= 0; --i)
			{
				adjustdown(i);
			}
		}//这里是使用了初始化队列来调用_con的迭代器区间初始化的方式
	private:
		Container _con;
		Compare _com;//这里就是示例化了我们内部所写的less
	};
}

希望这篇博客能对你有所帮助,如果发现错误,欢迎指正。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值