以下有关栈的知识请见这篇博客:https://blog.csdn.net/sandmm112/article/details/79860236
以下有关队列的知识请见这篇博客:https://blog.csdn.net/sandmm112/article/details/79860257
前面我们介绍过了栈和队列的两种实现方法以及一些基本操作。下面利用之前的栈和队列实现另外一些常见的问题。
一. 实现最小栈
这个是要实现一个特殊栈,要求可以Push(入栈)、Pop(出栈)、每次在出入栈后取栈中最小值的时间复杂度为O(1)。
前面实现的两种栈(顺序栈和链栈)的出栈和出栈的时间复杂度均为O(1),所以现在的问题就归结为取最小值的时间复杂度为O(1)。
若要实现每次取值的时间复杂度为O(1),所以只能取栈顶元素。 而又要求取得是最小值,所以就要求栈顶元素必须为最小值。但栈顶元素不一定是最小值,所以,这里就要实现一种特殊的栈使得栈顶元素始终为最小值,但是又不能影响元素的正常出入栈情况。比如说,现在要任意入栈四个元素:s,a,e,b。如果要取栈顶元素,即要取栈中的最小值a。此时可能会想到对入栈后的元素进行排序,使得栈中元素为:s,e,b,a。若是这样在出栈时就会是a出栈。而实际上入栈的最后一个元素是b,所以出栈也应该出b。因此,这种方法是不合适的。
基于这个问题:
(1)每次出入栈前后栈顶元素必须为栈中的最小值。
(2)出栈时又必须按照正常的入栈顺序来实现
下面介绍两种方法实现:
1. 一次出入栈两个元素
若要满足上述两个条件,可以在入栈时先插入正常的元素,在插入一个栈中的最小值。这样每次取得栈顶元素均为最小值。而在出栈时,先出最小值,再将正常的元素出栈,这样也保证了按照正常的入栈顺序出栈。
比如还是入栈四个元素:s,a,e,b。
(1)在入栈s时。s为正常要入栈的元素,栈中最小值也为s。所以先入栈正常元素s,在入栈最小值s,栈顶元素为最小值s;
(2)在入栈a时,a为正常要入栈的元素,栈中最小值为a。所以先入栈正常元素a,在入栈最小值a,栈顶元素为最小值a;
(3)在入栈e时,e为正常要入栈的元素,栈中最小值还为a。所以先入栈e,在入栈a,栈顶元素为最小值a;
(4)在入栈b时,b为正常要入栈的元素,栈中最小值为a。所以先入栈b,在入栈a,栈顶元素为最小值a。
在出栈时:
(1)因为上述入栈的四个元素中,b是最后入栈的,所以出栈时即出b。此时,先出栈最小值a,再出栈b即可达到目的,而出栈后栈顶元素还是最小值a;
(2)然后出栈e,先出栈最小值a,在出栈e,栈顶元素还是最小值a;
(3)出栈a,先出栈最小值a,在出栈a,栈顶元素为最小值s;
(4)出栈s,先出栈最小值s,在出栈s,此时栈即为空了。
该问题的实现只是在出入栈时与之前的方法不同。基于之前的顺序栈,分别实现该特殊栈的一些基本操作(出入栈元素的方法与顺序栈相同,这里直接调用),以下将特殊栈称为最小栈:
最小栈的结构:
该栈是基于顺序栈实现的,所以直接对顺序栈进行封装
//栈顶元素始终为最小值的栈结构定义
typedef struct MinStack
{
SeqStack stack;
}MinStack;
最小栈的初始化:
与顺序栈的初始化方法相同,直接调用即可。
//栈的初始化
void MinStackInit(MinStack* minstack)
{
if(minstack == NULL)
{
//非法输入
return;
}
InitSeqStack(&minstack->stack);//调用顺序栈的初始化方法
return;
}
最小栈的入栈操作
通过上述可知,在对最小栈入栈一个元素时,实际是先入正常的元素。再入最小值,因为入栈前栈顶元素为最小值,所以只需比较正常元素与入栈前栈顶元素的大小,取出二者最小值进行入栈即可。入栈的方法与顺序栈相同,这里直接调用:
//入栈操作
void MinStackPush(MinStack* minstack,SeqStackType value)
{
if(minstack == NULL)
{
//非法输入
return;
}
SeqStackType min = value;
SeqStackType top;
int ret = SeqStackTop(&minstack->stack,&top);//取入栈前的栈顶元素即栈中最小值
if(ret == 0 && top < value)//取原栈顶元素与要入栈的正常元素的最小值作为新的最小值即新的栈顶元素
{
min = top;
}
SeqStackPush(&minstack->stack,value);//入栈正常元素
SeqStackPush(&minstack->stack,min);//入栈新的最小值
}
最小栈的出栈操作
在栈不为空时。最小栈在出栈一个元素时,实际要出栈两个元素,一个是最小值即栈顶元素,一个是正常元素,出栈方法与顺序栈相同。
//出栈操作
void MinStackPop(MinStack* minstack)
{
if(minstack == NULL)
{
//非法输入
return;
}
if(minstack->stack.size == 0)
{
//空栈
return;
}
SeqStackPop(&minstack->stack);//出栈栈顶元素
SeqStackPop(&minstack->stack);//出栈正常元素
return;
}
取栈顶元素(取最小值的时间复杂度为O(1))
因为入栈时已经设定好栈顶元素始终为栈中最小值,所以直接取即可,方法与顺序栈相同。
//取栈顶元素
int MinStackTop(MinStack* minstack,SeqStackType* value)
{
if(minstack == NULL || value == NULL)
{
//非法输入
return -1;
}
if(minstack->stack.size == 0)
{
//空栈
return -1;
}
return SeqStackTop(&minstack->stack,value);//取栈顶元素
}
2. 利用两个栈保存正常元素和最小值元素
该方法与上述方法原理相同。分别利用两个栈来实现上述两个要求。要保证栈顶元素为最小值,就设置一个栈来保存栈中的最小值。要保证按入栈的顺序出栈,就在设置一个栈来保存正常元素。
(1)在入栈时分别对这两个栈进行正常元素和最小值的入栈,如入栈s,a,e,b时。正常栈中为:s,a,e,b;最小值栈中为:s,a,a,a。
(2) 在出栈时也分别出一个正常元素和一个最小值。
(3)只是在取栈顶元素时,只需对保存最小值的栈取栈顶元素即可。
上述可以始终保证两种的元素个数相同,且满足最小栈的两个要求。以下基于顺序栈来实现最小栈的基本操作:
最小栈的结构
要实现上述两个要求,所以最小栈的结构中要封装两个顺序栈。
typedef struct MinStack
{
SeqStack norstack;//该栈用于存放正常出入栈的元素
SeqStack topstack;//该栈栈顶永远为最小元素,两栈的元素一直相同
}MinStack;
最小栈的初始化
分别对两个顺序栈初始化即可
//栈的初始化
void MinStackInit(MinStack* minstack)
{
if(minstack == NULL)
{
//非法输入
return;
}
InitSeqStack(&minstack->norstack);//初始化正常栈
InitSeqStack(&minstack->topstack);//初始化保存最小值的栈
return;
}
最小栈的入栈操作
入栈时,分别对正常栈和保存最小值的栈入栈即可。
//入栈操作
void MinStackPush(MinStack* minstack,SeqStackType value)
{
if(minstack == NULL)
{
//非法输入
return;
}
SeqStackPush(&minstack->norstack,value);//对正常栈进行正常入栈
SeqStackType top;
SeqStackType min = value;
int ret = SeqStackTop(&minstack->topstack,&top);//在最小栈中取栈顶元素
if(ret == 0 && top < value)
{
min = top;
}
SeqStackPush(&minstack->topstack,min);
return;
}
最小栈的出栈操作
在栈中元素不为空时,分别对两个顺序栈进行出栈操作
//出栈操作
void MinStackPop(MinStack* minstack)
{
if(minstack == NULL)
{
//非法输入
return;
}
if(minstack->norstack.size == 0)
{
//空栈
return;
}
SeqStackPop(&minstack->norstack);
SeqStackPop(&minstack->topstack);
return;
}
最小栈取栈顶元素(取最小值的时间复杂度为O(1))
在栈不为空时,只需对保存最小值的栈进行取栈顶元素即可。
//取栈顶元素
int MinStackTop(MinStack* minstack,SeqStackType* value)
{
if(minstack == NULL || value == NULL)
{
//非法输入
return -1;
}
if(minstack->topstack.size == 0)
{
//空栈
return -1;
}
return SeqStackTop(&minstack->topstack,value);//对保存最小值的顺序栈去栈顶元素
}
二,两个栈实现一个队列
队列的特点是先进先出。要利用栈来实现队列,必须让先入栈的元素可以先出。但是只利用一个栈是不可以实现的,因为栈的特点是先进后出,所以必须让先入栈的元素位于栈顶,这样出栈即出队列时就可以实现先进先出。这里可以借助另一个栈实现该功能。
方法一:
入队:将新元素直接入栈到栈1中。(无论栈2是否有元素)。
出队:如果栈2不为空,则直接出栈栈顶元素;
如果栈2为空,则将栈1中的元素全部倒腾到栈2中。如果此时栈2还是为空,则说明队列为空。否则出栈栈顶元素。
取队首元素:与出栈类似。如果栈2不为空,在直接取栈顶元素;
如果栈2为空,则将栈1中的元素全部倒腾到栈2中。如果此时栈2还是为空,则说明队列为空。否则出栈栈顶元素。
利用C++来实现上述操作:
class Solution
{
public:
//无论栈2是否为空,在栈1中插入元素
void push(int node) {
stack1.push(node);
}
//出队列时:
//如果栈2的元素不为空,则直接出栈即可
//如果栈2中的元素为空,则将栈1中的元素全部倒腾到栈2中,再对栈2进行出栈
// 如果倒腾完之后的栈2还是为空,则说明该队列为空
// 最后返回出栈的元素即可
int pop() {
if(stack2.size() <= 0)
{
while(stack1.size() > 0)
{
int top = stack1.top();
stack2.push(top);
stack1.pop();
}
}
if(stack2.size() == 0)
{
printf("queue is empty\n");
}
int head = stack2.top();
stack2.pop();
return head;
}
private:
stack stack1;
stack stack2;
};
方法二:
(1)先在一个栈1中实现入队列。
(2)要出队列时,可以将栈1中所有元素在入栈到另一栈2中,根据栈先进后出的特点,在栈2中栈顶元素即为最先入队列的元素,此时便可以直接出栈即出队列了。
(3)再次入队列时,要保证该元素为最后入队的,而此时栈2中的栈顶元素相比于栈中其他元素为最先入队列的元素,所以不能直接对栈2进行入栈。因此,要再次将栈2中的所以元素入栈到栈1中,此时栈1中的栈顶元素相对于栈中其他元素是最后入队列的,所以直接对栈1进行入栈即入队列即可。
(4)之后再出队列时重复(2),出队列时重复(3)
比如要入队列:a,b,c,d。
所以,由上可知,入队列时,必须在一个栈1中进行,出队列时,必须在另一栈2中进行。如果入队时,栈1为空就将栈2中所有元素入栈到栈1中;如果出队时,栈2为空,就将栈1中所有元素入栈到栈2中。
1. 队列的结构
该队列是由两个顺序栈组成
typedef struct QueueBy2Stack
{
SeqStack input;//用于入队列的栈
SeqStack output;//用于出队列的栈
}QueueBy2Stack;
2. 队列的初始化
分别对两个栈初始化即可
//初始化
void QueueBy2StackInit(QueueBy2Stack* queue)
{
if(queue == NULL)
{
//非法输入
return;
}
InitSeqStack(&queue->input);
InitSeqStack(&queue->output);
return;
}
3. 入队列
如果栈output为空,直接对input进行入栈操作。
如果不为空,将output中的所有元素入栈到input中:(1)先取output中的栈顶元素,(2)将栈顶元素入栈到input中。(3)对output出栈。再对栈output中的其他元素进行(1)(2)(3)的操作,直到output为空。在对栈input进行入栈操作。
//入队列
void QueueBy2StackPush(QueueBy2Stack* queue,SeqStackType value)
{
if(queue == NULL)
{
//非法输入
return;
}
int ret;
SeqStackType top;
//将output中的所有元素入栈到input中,然后在对output出栈
while(1)
{
ret = SeqStackTop(&queue->output,&top);
if(ret == -1)
{
break;
}
SeqStackPush(&queue->input,top);
SeqStackPop(&queue->output);
}
//对input进行入栈即入队列操作
SeqStackPush(&queue->input,value);
return;
}
4. 出队列
在至少有一个栈不为空时:
如果栈intput为空,直接对output进行出栈操作即可
如果栈input不为空。(1)取input中的栈顶元素(2)将栈顶元素入栈到output中(3)对input出栈。重复(1)(2)(3)操作,直到input为空。再对output进行出栈操作。
//出队列
void QueueBy2StackPop(QueueBy2Stack* queue)
{
if(queue == NULL)
{
//非法输入
return;
}
if(queue->input.size == 0 && queue->output.size == 0)
{
//空队列
return;
}
int ret;
SeqStackType top;
while(1)
{
ret = SeqStackTop(&queue->input,&top);
if(ret == -1)
{
break;
}
SeqStackPush(&queue->output,top);
SeqStackPop(&queue->input);
}
SeqStackPop(&queue->output);
}
5. 取栈顶元素
取栈顶元素与出栈类似,只是将上述的最后的出栈操作改为取栈顶元素即可。
//取栈顶元素
int QueueBy2StackTop(QueueBy2Stack* queue,SeqStackType* value)
{
if(queue == NULL || value == NULL)
{
//非法输入
return -1;
}
if(queue->input.size == 0 && queue->output.size == 0)
{
//空队列
return -1;
}
int ret;
SeqStackType top;
while(1)
{
ret = SeqStackTop(&queue->input,&top);
if(ret == -1)
{
break;
}
SeqStackPush(&queue->output,top);
SeqStackPop(&queue->input);
}
SeqStackTop(&queue->output,value);
return 0;}
三,两个队列实现一个栈
栈的特点是先进后出。用队列来实现,先在一个队列中入队一些元素。
在出栈时,实际要出的是队尾的元素,而队列时先进先出的,所以要借助另一个队列来实现。将队列1中的元素入队到队列2中,只留下最后一个元素即队列1中的队尾元素。此时该队尾元素即为要出栈的元素。
在取栈顶元素时,与出栈时相同,队列1中的队尾元素即为栈顶元素。取出后,再将该元素插入队列2中,保证其入栈的顺序性。
所以,这两个队列必然在出栈和取栈顶元素前后,必然一个为空,一个不为空。
再次入队时,只需对非空队列进行入队即可。且这两个队列均可以用于入栈和出栈,分工没有那么明确。每次出入栈时只需找到非空队列即可。
1. 栈的结构
该栈可由两个顺序队列来实现
typedef struct StackBy2Queue
{
SeqQueue queue1;
SeqQueue queue2;
}StackBy2Queue;
2. 栈的初始化
分别对两个顺序队列初始化即可
//栈的初始化
void StackBy2QueueInit(StackBy2Queue* stack)
{
if(stack == NULL)
{
//非法输入
return;
}
SeqQueueInit(&stack->queue1);
SeqQueueInit(&stack->queue2);
return;
}
3. 入栈
首先要找到非空队列,找到后,直接对该队列进行入队操作即可。如果两个队列均为空,对任意一个入队即可。
//入栈
void StackBy2QueuePush(StackBy2Queue* stack,SeqQueueType value)
{
if(stack == NULL)
{
//非法输入
return;
}
SeqQueue* input;
//queue2非空
if(stack->queue1.size == 0)
{
input = &stack->queue2;
}
else//queue1非空
{
input = &stack->queue1;
}
//对非空队列入队
SeqQueuePush(input,value);
return;
}
4. 出栈
如果两个队列均为空,则出栈失败
否则,必然只有一个队列不为空。找到该非空队列后,将该队列中的元素入队到另一队列中,只剩一个元素。将该元素出队列即可。
//出栈
void StackBy2QueuePop(StackBy2Queue* stack)
{
if(stack == NULL)
{
//非法输入
return;
}
if(stack->queue1.size == 0 && stack->queue2.size == 0)
{
//空栈
return;
}
SeqQueue* from;//from为要出队的队列,此时from非空
SeqQueue* to;//to为要入队的队列,此时to为空
if(stack->queue1.size == 0)
{
to = &stack->queue1;
from = &stack->queue2;
}
else
{
to = &stack->queue2;
from = &stack->queue1;
}
SeqQueueType top;
//将from中的元素入队到to中,只剩下一个元素为止
while(1)
{
if(from->size == 1)
{
break;
}
SeqQueueTop(from,&top);
SeqQueuePush(to,top);
SeqQueuePop(from);
}
//将剩余的最后一个元素出队
SeqQueuePop(from);
return;
}
5. 取栈顶元素
与出栈类似
如果两个队列均为空,则失败
否则,必然只有一个队列不为空。找到该非空队列后,将该队列中的元素入队到另一队列中,只剩一个元素。取完该元素后,将该元素入队到另一队列中。
//取栈顶元素
int StackBy2QueueTop(StackBy2Queue* stack,SeqQueueType* value)
{
if(stack == NULL || value == NULL)
{
//非法输入
return -1;
}
if(stack->queue1.size == 0 && stack->queue2.size == 0)
{
//空栈
return -1;
}
int ret;
SeqQueueType top;
SeqQueue* to;
SeqQueue* from;
if(stack->queue1.size != 0)
{
from = &stack->queue1;
to = &stack->queue2;
}
else
{
from = &stack->queue2;
to = &stack->queue1;
}
while(1)
{
ret = SeqQueueTop(from,value);
if(ret == -1)
{
break;
}
SeqQueuePop(from);
SeqQueuePush(to,*value);
}
return 0;
}
四,判定一个字符串是否按照出栈顺序由另一字符串
比如说,字符串1“abcde”,字符串2“cbade”。分别用指针1和指针2指向串1和串2
对字符串1进行入栈操作,先入栈a;此时a不等于2中的c,在入栈1中的b;此时还不相等,再入栈1中的c;
此时相等了,就可以出栈c了,同时指针2后移至b(后面均是出栈一次,串2指针后移1个)。此时栈顶元素为b,与指针2指向相同,在出栈b。同时a也相同,在出栈a。
此时,栈为空了,在对串1入栈d,与串2相同,再次出栈。在入栈e,与串2相同,再次出栈。此时字符串1中元素全部入过栈,栈中元素全部出栈,串2字符全部遍历。就说明串2是由串1按顺序出栈而得。
比如“cabde”就不是由“abcde”按出栈顺序而得。
所以,上述判断的思路就是解题的过程:
设置指针1和指针2初始时分别指向两个字符串的起始处,再设置一个栈。以下所说的对指针入栈是指对指针所指向的内容入栈。
(1)对指针1入栈
(2)取栈顶元素
如果此时栈为空,则指针1后移,回到(1)
如果栈不为空,且栈顶元素与指针2指向的内容不相同,则指针1后移,回到(1)
如果栈不为空,且栈顶元素与指针2指向的内容相同,则出栈一个元素,指针2后移后,回到(2)。
(3)当栈中元素为空且指针2走到字符串2的结尾时,说明串2中元素可以由串1元素按出栈顺序得到。
所以上述过程可以有两个循环来实现,(1)处需要一个循环,(2)处也需要一个循环,(3)中的两个条件就是(1)(2)循环的结束条件。
//判断是否按照顺序出栈
int IsSeqPop(char src[],int size1,char dst[],int size2)
{
if(src == NULL || dst == NULL)
{
//非法输入
return -1;
}
if(size1 != size2)
{
//两串的长度不相同,一定不是按顺序出栈的
return -1;
}
SeqStack stack;
InitSeqStack(&stack);//每次要初始化栈
int src_cur = 0;
int dst_cur = 0;
//1. 原串一个一个元素循环入栈
while(src_cur != size1)//保证原串中所有元素最终均入栈
{
SeqStackPush(&stack,src[src_cur]);//入栈当前元素
//2. 取栈顶元素与目标串当前位置元素进行比较
SeqStackType top;
int ret;
while(dst_cur != size2)//保证目标串中所有元素均有对应的栈顶元素
{
ret = SeqStackTop(&stack,&top);//取栈顶元素
if(ret == -1)//栈为空
{
break;//跳出循环后,继续入栈
}
//栈顶元素与目标串当前位置元素不相同,要对原串继续入栈
if(top != dst[dst_cur])
{
break;
}
else// 栈顶元素域目标串当前元素相同
{
SeqStackPop(&stack);//出栈一个元素
dst_cur++;//目标串当前位置后移,继续比较栈顶元素域目标串当前位置元素
}
}
src_cur++;//原串后移,继续入栈
}
//5. 当原串所有元素均已入栈,此时目标串走到最后,并且栈为空时,表示按照顺序出栈
if(dst_cur == size2 && stack.size == 0)
{
return 0;
}
//6. 当原串所有元素均已入栈
//此时目标串未走到最后(目标串比原串长或不按顺序出栈),或栈不为空时(不按顺序出栈或原串比目标串长)
else
{
return -1;
}
}
五,一个数组实现共享栈
数组的两端位置是固定的,而栈的栈底位置也是固定的。所以可以将数组的两端分别作为两个栈的栈底。在对栈1入栈时数组下标往递增方向,对栈2入栈时数组下标往递减方向。如下图:
这里,我们规定栈1的栈顶元素的下一个位置下标为top1,即栈1中元素为:[0,top1)。栈2的栈顶元素所在下标为top2,即栈2中元素为:[top2,max_size),max_size为数组中元素的个数。
所以,栈1为空时,top1=0,栈2为空时,top2 = max_size。而数组满的条件是:top1 = top2
下面实现这共享栈的一些基本操作:
1. 共享栈的结构
因为共享栈是由数组实现的,所以只需要封装一个数组即可。
#define MAX_SIZE 1000//定义数组的最大长度
typedef char ShareStackType;//数组中元素类型
//共享栈的结构
typedef struct ShareStack
{
ShareStackType data[MAX_SIZE]; //数组
int top1;//栈1的栈定元素
int top2;//栈2的栈顶元素
}ShareStack;
2. 共享栈的初始化
初始时,两栈中均没有元素,所以,top1置为0,top2置为MAX_SIZE即可。
//共享栈的初始化
void ShareStackInit(ShareStack* stack)
{
if(stack == NULL)
{
//非法输入
return;
}
stack->top1 = 0;
stack->top2 = MAX_SIZE;
return;
}
3. 共享栈的入栈操作
在数组未满的前提下,栈1往数组下标递增的方向增长,栈2往数组下标递减的方向增长。
void ShareStackPush1(ShareStack* stack,ShareStackType value)
{
if(stack == NULL)
{
//非法输入
return;
}
//数组判满
if(stack->top1 == stack->top2)
{
//此时数组已满,无法入栈
return;
}
stack->data[stack->top1++] = value;
return;
}
//栈2入栈
void ShareStackPush2(ShareStack* stack,ShareStackType value)
{
if(stack == NULL)
{
//非法输入
return;
}
if(stack->top1 == stack->top2)
{
//此时数组已满
return;
}
stack->data[--stack->top2] = value;
return;
}
4. 共享栈的出栈操作
在各自栈非空的前提下,栈1往数组下标递减的方向出栈,栈2往数组下标递增的方向出栈。
//栈1出栈
void ShareStackPop1(ShareStack* stack)
{
if(stack == NULL)
{
//非法输入
return;
}
if(stack->top1 == 0)
{
//栈1为空
return;
}
--stack->top1;
return;
}
//栈2出栈
void ShareStackPop2(ShareStack* stack)
{
if(stack == NULL)
{
//非法输入
return;
}
if(stack->top2 == MAX_SIZE)
{
//栈2为空时
return;
}
++stack->top2;
return;
}
5. 共享栈取栈顶元素
在各自栈非空的前提下,下标为top1-1处的元素为栈1的栈顶元素,下标为top2的元素为栈2的栈顶元素
//栈1取栈顶元素
int ShareStackTop1(ShareStack* stack,ShareStackType* value)
{
if(stack == NULL)
{
//非法输入
return -1;
}
if(stack->top1 == 0)
{
//栈1为空时
return -1;
}
*value = stack->data[stack->top1 - 1];
return 0;
}
//栈2取栈顶元素
int ShareStackTop2(ShareStack* stack,ShareStackType* value)
{
if(stack == NULL)
{
//非法输入
return -1;
}
if(stack->top2 == MAX_SIZE)
{
//栈2为空
return -1;
}
*value = stack->data[stack->top2];
return 0;
}