[编程之美] PSet3.7 队列中取最大值操作问题

本文探讨了如何设计一种数据结构和算法,使得在队列中执行MaxElement操作的时间复杂度尽可能低。提出了三种解法:传统队列(时间复杂度O(N))、最大堆法(MaxElement操作时间复杂度O(1),Push和Pop操作时间复杂度O(logN))以及辅助数组法(MaxElement操作时间复杂度O(1),用空间换取时间)。最后还介绍了一种使用两个栈实现队列并快速获取最大值的方法,平均复杂度为线性。
问题描述:

       假设有这样一个拥有3个操作的队列:
      1. EnQueue(v): 将v加入队列中
      2. DeQueue(): 使队列中的队首元素删除并返回此元素
      3. MaxElement: 返回队列中的最大元素

      设计一种数据结构和算法,让MaxElement操作的时间复杂度尽可能地低。

思路:

      解法一:传统队列

      传统队列的方式,采用数组或链表来存储队列的元素,利用指针遍历整个队列元素,在长度为N的队列,时间复杂度O(N)。对于小飞的思路,在Push处维护一个MaxVal,方法是可以的,但是需要在Pop函数处考虑是否弹出了最大值,如果弹出了,需要重新遍历队列一次查找最大值。解法一代码如下:

//方法一:普通队列
//用链表作为队列基础数据结构
struct List{
	int data;
	List *next;
};
class Queue{
private:
	List *front;
	List *rear;
public:
	Queue();//默认构造函数
	~Queue();//析构函数
	bool isEmpty();//队列是否为空
	int DeQueue();//从队列头部弹出元素
	void EnQueue(int elem);//向队列尾部压入元素
	void traverse();//遍历队列
	int MaxElem();//取最大值操作
};
Queue::Queue()
{
	front = new List;//头结点,不存放数据
	front->next = NULL;
	rear = front;
}
Queue::~Queue()
{
	//---将头结点也析构掉
	if(rear == front)
		delete front;
	else
	{
		List *p = front;
		List *q = p->next;
		while(p != rear){
			delete p;
			p=q;
			q=q->next;
		}
		delete rear;
	}
	rear = front = NULL;
}
bool Queue::isEmpty()
{
	if(front == rear)
		return true;
	return false;
}
void Queue::EnQueue(int elem)
{
	List *temp = new List;
	temp->data = elem;
	temp->next = NULL;
	//从队尾压入元素
	rear->next = temp;
	rear = temp;
}
int Queue::DeQueue()
{
	//---判断是否为空队列
	if(isEmpty())
		return -9999;
	//---保存需要删除的指针
	List *temp = front->next;
	int val = temp->data;
	front->next = temp->next;
	if(temp == rear){//如果队列只有最后一个元素,则置rear为front
		rear = front;
	}
	delete temp;
	return val;
}
int Queue::MaxElem()
{
	List *temp = front->next;//第一个元素
	int maxVal = -9999;
	while(temp != NULL){
		if(temp->data > maxVal)
			maxVal = temp->data;
		temp = temp->next;
	}
	return maxVal;
}
void Queue::traverse()
{
	if(front == rear){//队列为空
		cout<<"队列为空!"<<endl;
		return;
	}
	List *temp = front->next;//第一个节点
	cout<<"队列元素为:";
	while(temp != NULL){
		cout<<temp->data<<" ";
		temp = temp->next;
	}
	cout<<endl;
}
int main()
{
	Queue q;
	for(int i=6 ; i>=0 ; i--)
		q.EnQueue(i);
	q.traverse();
	cout<<q.MaxElem()<<endl;
	return 0;
}

      解法二:最大堆法

      考虑用最大堆维护队列中元素,堆中的每个元素都有指针指向它的后继元素。这样MaxElement操作就是维护这个最大堆的过程,时间复杂度O(1),入队和出队操作时间复杂度O(logN)。代码如下:

       新思路:辅助数组法可以快速取得栈的最大值

      由于栈的取数和存数操作都在栈顶,所以维护栈中最大值很容易!用一个辅助数组记住栈在当前状态(辅助数组下标表示)下最大值所在位置,这样Max操作的时间复杂度O(1),相当于用空间复杂度换区了时间复杂度。

      解法三:用两个栈来实现

      用两个栈(新思路所示)来实现一个队列,因为入栈一次相当于对原数据进行逆序一次,而入栈两次则维持原数据顺序。具体做法为设有A、B两栈,则在未取数据之前将数据压入B中缓存起来,当取数时,如果栈A数据为空,则将栈B中数据统统压入栈A中。这种有辅助数组的栈可以让平均复杂度为线性。代码如下:

//方法三:两个栈建立队列
//可以在平均复杂度O(1)的情况下取出最大值,入队和出队的平均复杂度为O(1)(摊还分析)
//因为每个元素最多有可能移动了三次:压入栈A,从栈A压入栈B,从栈B弹出。
//底层数据结构用vector
class Stack{
private:
	//top指针可以不要,因为vector提供了back与pop_back函数,可以用size()-1替代顶指针
	vector<int> data;
	vector<int> MaxIndex;
public:
	Stack (){}
	~Stack(){}
	void Push(int elem);
	int Pop();
	int MaxElem();
	bool isEmpty();
	void traverse();
	void Inv_traverse();
};
void Stack::Push(int elem)
{
	//---如果栈为空或当前元素大于栈中最大元素,更新最大值下标数组
	if(data.empty() || elem>data[MaxIndex[MaxIndex.size()-1]])
		MaxIndex.push_back(data.size());
	data.push_back(elem);
}
bool Stack::isEmpty()
{
	return data.empty();
}
int Stack::Pop()
{
	if(isEmpty()){
		cout<<"栈为空!"<<endl;
		return INT_MIN;
	}
	if(data.size()-1 == MaxIndex[MaxIndex.size()-1])//如果弹出的是当前最大值
		MaxIndex.pop_back();
	int val = data[data.size()-1];
	data.pop_back();
	return val;
}
int Stack::MaxElem()
{
	if(isEmpty()){//栈为空
		return INT_MIN;
	}
	return data[MaxIndex[MaxIndex.size()-1]];
}
void Stack::traverse()
{
	if(isEmpty()){
		cout<<"栈为空"<<endl;
		return;
	}
	for(int i=data.size()-1 ; i>=0 ; i--)//不能为size_t,因为size_t为unsigned int,从0减一过后并不为负数,而是一个非常大的正数
		cout<<data[i]<<" ";
	cout<<endl;
}
void Stack::Inv_traverse()
{
	if(isEmpty()){
		cout<<"栈为空"<<endl;
		return;
	}
	for(int i=0 ; i<data.size() ; i++)
		cout<<data[i]<<" ";
	cout<<endl;
}
//---用两个有辅助数组的栈实现队列
class Queue{
private:
	Stack s1,s2;
public:
	Queue(){}
	~Queue(){}
	int MaxElem();
	void EnQueue(int elem);
	int DeQueue();
	bool isEmpty();
	void traverse();
};
int Queue::MaxElem()
{
	return s1.MaxElem()>s2.MaxElem()?s1.MaxElem():s2.MaxElem();
}
bool Queue::isEmpty()
{
	return s1.isEmpty()&&s2.isEmpty() ;
}
void Queue::EnQueue(int elem)
{
	//---入队时将所有元素存放在s2中
	s2.Push(elem);
}
int Queue::DeQueue()
{
	//---如果s1空了,则将s2缓存区中数据压入s1中
	if(s1.isEmpty()){
		while(!s2.isEmpty())
			s1.Push(s2.Pop());
	}
	return s1.Pop();
}
void Queue::traverse()
{
	if(isEmpty()){//如果队列为空
		cout<<"队列为空"<<endl;
		return;
	}
	else if(s1.isEmpty()){//s1空s2非空
		s2.Inv_traverse();
	}else{
		s1.traverse();
	}
}
int main()
{
	Queue q;
	q.EnQueue(3);
	q.EnQueue(19);
	q.EnQueue(8);
	q.EnQueue(1);
	q.EnQueue(5);
	q.EnQueue(6);
	cout<<q.DeQueue()<<endl;//从队头弹出元素
	q.traverse();
	cout<<q.MaxElem()<<endl;
	
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值