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

被折叠的 条评论
为什么被折叠?



