STL-queue和priority_queue的模拟实现

回顾

对于STL,我们已经知道了vector和list,而它们是STL中被称为六大组件之一的容器,我们还学习了模拟实现stack,而stack在STL中被称为六大组件之一的适配器,今天,我们来学习queue的模拟实现和priority_queue的模拟实现,并且,它们也是适配器。

对于适配器而言,我们参照stack的模拟实现的经验,适配器的接口不过是调用容器的接口实现的,所以要实现一个适配器,只要理解了它的数据结构,实现是很简单的。

queue(队列)

什么是队列?在日常生活中,我们如果要去超市买东西,收银的时候如果人比较多,我们就需要排队,从队尾排,直到我们前面的人都收完钱,这就是一个队列,先排的人先走,后排的人后走。队列遵循的是先进先出的原则。

再回过头来看看stack,stack是遵循先进后出的原则,它与我们今天要学的queue相反。

所以,queue的整体还是很好理解的,那么我们如果要实现它,应该采用怎样的数据结构呢? 

stack我们可以使用vector作为容器完成,那么queue呢?是否也能使用vector作为容器完成?

这里就需要考虑到我们的时间复杂度来进行衡量了,因为queue的先进后出的原则,如果我们要进行删除,就只能从队头的数据开始删除,但是如果从队头开始删除数据,vector作为连续的数组空间,那么势必每次删除都需要挪动数据,每次删除数据都将会是一个O(N)的时间复杂度,所以并不高效,也就不能使用vector作为queue的容器。那么大家是否已经想到了比较合适容器比较高效的实现我们的queue呢? -----list和deque就很好,它们对于头删的成本并不高,时间也很高效!

而STL是采用deque作为queue的默认容器。

模拟实现queue

template<typename T , class Container = deque<T>>
class queue
{
public:
	void push(const T& data)
	{
		_data.push_back(data);
	}

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

	void pop()
	{
		_data.pop_front();
	}

	bool empty() const
	{
		return _data.empty();
	}
		
	T& front()
	{
		return _data.front();
	}
	const T& front() const 
	{
		return _data.front();
	}

	T& back()
	{
		return _data.back();
	}

	const T& back() const
	{
		return _data.back();
	}
private:
	Container _data;
	};

模版的实现很简单,实现它的接口也只是调用容器的接口,这里也有一个原因说明了了为什么不能使用vector来作为容器,因为vector根本没有pop_front这个接口,也就不可能用来作为我们的容器。

priority_queue(优先级队列)

priority_queue与queue的区别还是挺大的,priority_queue的数据结构其实本质就是堆,对于堆的数据结构而言,它的容器可以为vector,而queue是不可以的。

 对于堆的内容,如果不太理解,可以去查找堆先关的内容,堆是一种基于二叉树的数据结构。

queue的增删是这样的(以大堆举例)

对于删除数据,它会先使堆顶元素(最大元素)与最后的元素交换,然后删除最大元素(尾删),然后再进行堆排序,堆顶元素(本来是尾部的元素)进行向下调整,这一系列操作完成删除操作

对于插入数据,它先在尾部插入数据,然后再进行堆排序

因为它要采用堆的储存方式,所以,我们就必须要实现堆排的两个函数---向上调整和向下调整

向上调整和向下调整都是堆 用于保持堆结构的排序算法

向上调整

用于插入数据时,进行堆排序,保持堆的特性

void adjust_up(size_t pos)    //向上调整
{
	Compare com;
	size_t child = pos;
	size_t parent = (child - 1) / 2;
	while (child > 0)
	{
		//if (_data[parent] < _data[child])
		if (com(_data[parent],_data[child])) //仿函数  等会讲
		{
			std::swap(_data[parent], _data[child]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

向下调整

void adjust_down(size_t pos)    //向下调整
{
	Compare com;
	size_t parent = pos;
	size_t child = parent * 2 + 1;
	while (child < _data.size())
	{
		if (child + 1 < size() && com(_data[child], _data[child + 1]))
		{
			child = child + 1;
		}
		if (com(_data[parent], _data[child]))  //仿函数
		{
			std::swap(_data[parent], _data[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

这里判断是否向上(下)调整的if判断语句中,用了仿函数的知识,这里暂时不讲,后面再讲,只需要知道这是用来判断是否需要调整。

push

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

当插入数据之后,对新插入的元素进行向上调整,使得保持堆结构。

pop

void pop()     //头部删除
{
	assert(!empty());
	std::swap(_data.front(), _data.back());   
	_data.pop_back();
	adjust_down(0);
}

为什么是采用先交换再尾删的方法呢?因为如果要进行堆排序,使用向上调整的时间复杂度为O(N*logN),而如果使用向下调整它的复杂度为O(N)。

其他函数

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


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


const T& top() const 
{
	assert(!empty());
	return _data.front();
}

仿函数

template<typename T , class Container = vector<T>, class Compare = less<T> >
class priority_queue
{
private:
	Container _data;
};

template<typename T>
struct greater
{
public:
	bool operator()(const T& t1, const T& t2) const
	{
		return t1 > t2;
	}
};

在实现priority_queue中,它的模版函数是这样的

template<typename T , class Container = vector<T>, class Compare = less<T> >

前两个不难理解,作为适配器,需要传入容器,那么第三个是什么呢?

在实现刚刚的priority_queue中,既然是堆的方式实现,那么有时候我们就可能需要大堆或者是小堆,这时候,就可以使用仿函数来实现,可为什么叫做仿函数?

仿函数也是一个类,但是你看我们使用它的时候,是不是就像一个函数的调用?

void adjust_up(size_t pos)    //向上调整
{
	Compare com;
	size_t child = pos;
	size_t parent = (child - 1) / 2;
	while (child > 0)
	{
		//if (_data[parent] < _data[child])
		if (com(_data[parent],_data[child])) //仿函数
		{
			std::swap(_data[parent], _data[child]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

而它在类中是如何实现的?-> 是通过重载()达到这种效果。

template<typename T>
struct greater
{
public:
	bool operator()(const T& t1, const T& t2) const
	{
		return t1 > t2;
	}
};

这就是仿函数类 greater

如果你想实现 仿函数类 less,就是这样写

template<typename T>
struct less
{
public:
	bool operator()(const T& t1, const T& t2) const
	{
		return t1 < t2;
	}
};

猜你喜欢

转载自blog.csdn.net/fengjunziya/article/details/130844929