以下有关栈的知识请见这篇博客:https://blog.youkuaiyun.com/sandmm112/article/details/79860236
以下有关队列的知识请见这篇博客:https://blog.youkuaiyun.com/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