问题出处:《编程之美》3.7队列中取最大值操作问题
假定有这样一个拥有3个操作的队列:
1. EnQueue(v): 将v加入队列中
2. DeQueue(): 使队列中的队首元素删除并返回此元素
3. MaxElement(): 返回队列中的最大值
请设计一种数据结构和算法,让MaxElement()操作的时间复杂度尽可能的低。
常规思路:利用一个数组或者链表来存储队列的元素,利用两个指针分别指向队列的队首和队尾。如果采用这种方法,那么MaxElement()操作需要遍历队列中的所有元素。在队列的长度为N的条件下,时间复杂度为O(N).
下面的方法是在编程之美上看到的,能够实现MaxElement()的操作时间复杂度为O(1), 并且入队和出队的操作也很简单。
该队列使用两个栈stackA, stackB来实现的,stack有两个数组:stackItem[ ] 和 link2NextMaxItem[ ], stackItem 用于维护队列中的元素,link2NextMaxItem 用于维护栈中最大值的状态。入队操作是在stackB中执行入栈操作完成,出队操作是用stackA来完成,当stackA中没有元素时,将stackB中的所有元素都pop到stackA中,这样stackB中先入队的元素就会在stackA中的顶部,这样在stackA上执行pop操作,返回的元素就是最先入队的元素。
堆栈的程序描述如下,它实现了堆栈的入栈,出栈,维护栈中最大值状态,栈中元素打印的功能:
#define MAXN 100
class Stack
{
public:
//构造函数,初始化堆栈:
//|stackTop|指向当前堆栈的顶部元素,
//|maxStackItemIndex|存储当前堆栈中最大元素的index
Stack(){
stackTop = -1;
maxStackItemIndex = -1;
}
//判断当前堆栈是否为空
bool isEmpty() {
return stackTop == -1;
}
//判断当前堆栈是否满
bool isFull() {
return stackTop == MAXN - 1;
}
//向堆栈中push元素,
void push(int x) {
if(this->isFull()) { //首先检测堆栈空间是否满
cout<<"the stack is full now."<<endl;
}
else {
stackItem[++stackTop] = x;
//如果压入堆栈的值比之前记录的最大值大,那么那之前的最大值记录在
//link2NextMaxItem[stackTop]的位置,把当前最大值在堆栈stackItem
//中的位置用maxStackItemIndex保存
if(x > maxValue()) {
link2NextMaxItem[stackTop] = maxStackItemIndex;
maxStackItemIndex = stackTop;
}
else
link2NextMaxItem[stackTop] = -1;
}
}
int pop(){
int ret;
if(this->isEmpty()) {
cout<<"the stack is empty."<<endl;
}
else {
ret = stackItem[stackTop];
//如果pop出的值是当前的最大值,那么则需要将之前保存到link2NextMaxItem[stackTop]
//中的最大值取出,更新maxStackItemIndex的值
if( stackTop == maxStackItemIndex )
{
maxStackItemIndex = link2NextMaxItem[stackTop];
}
}
--stackTop;
return ret;
}
//取出堆栈中当前的最大值
int maxValue(){
if(maxStackItemIndex >= 0)
return stackItem[maxStackItemIndex];
else{
return INT_MIN;
}
}
void printItems() {
for (int i = 0; i <= stackTop; ++i) {
cout<<" "<<stackItem[i];
}
}
void reversePrintItems() {
for (int i = stackTop; i >= 0; --i) {
cout<<" "<<stackItem[i];
}
}
private:
int stackItem[MAXN];
int stackTop;
int link2NextMaxItem[MAXN];
int maxStackItemIndex;
};
使用上面的栈结构的两个栈对象就可以实现队列的结构了,并使MaxElement()操作的时间复杂度为O(1), 程序描述如下:
class Queue
{
public:
int maxValue(int x, int y) {
if(x>y)
return x;
else
return y;
}
//返回队列中最大值
int max() {
return maxValue(stackA.maxValue(), stackB.maxValue());
}
void printQueue() {
stackA.reversePrintItems();
stackB.printItems();
cout<<endl;
}
//在队列末尾插入元素
void insertQueue(int x) {
stackB.push(x);
}
//删除并返回队首元素,
//如果stackA中是空,则先将stackB中的所有元素pop到stackA中,
//这样stackB中先插入的元素就会pop到stackA中的顶部
int deQueue() {
if(stackA.isEmpty()) {
while(!stackB.isEmpty())
stackA.push(stackB.pop());
}
return stackA.pop();
}
private:
Stack stackA; //维护出队列操作
Stack stackB; //维护入队列操作
};
调试代码如下:
int main()
{
Queue queue;
int a[] = {909, 2, 3, 4, 9, 4, 5, 10, 6};
for(int i = 0; i < sizeof(a)/sizeof(int); ++i) {
queue.insertQueue(a[i]);
}
queue.insertQueue(101);
cout<<"queue maxvalue = "<<queue.max()<<endl;
queue.insertQueue(590);
cout<<"queue maxvalue = "<<queue.max()<<endl;
queue.printQueue();
int deq = queue.deQueue();
cout<<"deq = "<<deq<<endl;
cout<<"queue maxvalue = "<<queue.max()<<endl;
}
当前得自己加上头文件。