设计一个包含min()函数的栈
要求:
1.定义栈的数据结构,要求添加一个min 函数,能够得到栈的最小元素;
2.函数min、push 以及pop 的时间复杂度都是O(1);
下面我们就来看看如何实现这个栈。语言方面,我们选择C语言~
首先来详细学习一下栈的定义,让我们全方位的了解本题的相关知识。
一.栈的定义
栈(stack)是限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表。允许插入和删除的一端,为变化的一端,称为栈顶(Top),另一端为固定的一端,称为栈底(Bottom)。
(1)通常称插入、删除的这一端为栈顶(Top),另一端称为栈底(Bottom)。
(2)当表中没有元素时称为空栈。
(3)栈为后进先出(Last In First Out)的线性表,简称为LIFO表。
栈的修改是按后进先出的原则进行。每次删除(退栈)的总是当前栈中"最新"的元素,即最后插入(进栈)的元素,而最先插入的是被放在栈的底部,要到最后才能删除。
二.题目解析
通过上面的分析我们看到,其实,基本栈的push和pop 操作就是O(1)的时间复杂度的。
因为我们的的入栈(push)操作,是将数据复制给栈顶(top),同时栈顶指针自增,或者说:这个顺序栈的 top++。那么这个操作显然是与栈的大小无关,仅一步完成,所以该操作为常量时间复杂度O(1)。同理 pop操作也是这样的。
那么现在问题的关键是这个min操作是什么样的,它如何来实现复杂度为O(1)呢?
所以现在我们再来回看这个min方法,即从一个栈中找到最小的数据。那么最传统的想法是:首先将栈顶top下面的数据设为最小值 min_data ,遍历这个栈,使得每一个元素与最小值进行比较。若 current_data < min_data ,那么将这个 current_data 附给 min_data ,即 min_data = current_data 。但是这样一来使得该操作的时间复杂度就为O(n)了 (假设栈的深度为n),那是因为一共要进行n次比较选出那个最小值。所以显然不符合要求。
于是,我们换个角度来想,pop和push都是O(1)的操作,那我们能不能把min操作也实现为这种不用遍历,只要在固定位置取出最小值即可的操作呢?或者说要是那个最小值每次都在栈顶top下面那个位置出现,我们只要去哪里取就可以了。所以我们将对传统的栈元素进行小小的改造。
下面的就是存储在栈中的每个元素的数据结构:由一个data即要存放的数据本身(假设是个int类型的数据),和一个我们期望在栈顶下面找到的最小元素。
typedef struct StackElement
{
int data;
int min; //这个min代表的就是我们期望在栈顶下面找到的那个最小值
} StackElement;
接下来我们将给出栈的基本样子,你会发现它其实就是一个普通的栈。所以上面这个数据结构和之后的方法才是解决问题的关键~
栈的样子如下:
typedef struct Stack
{
StackElement* data;
int top;
int size;
} Stack;
下面就将给出这个栈的几个基本操作,当然是只与题目相关的函数了。
Stack* StackInit( int max_size ); //初始化栈
void StackFree( Stack stack ); //栈释放,因为栈中存在一个指针StackElement,需要释放指针所指的空间
void StackPush( Stack* stack, int d );
int StackPop( Stack* stack );
int StackFindMin( Stack stack ); //这个方法就是寻找最小值的函数
一个一个来看这些函数的功能和实现:
1.Stack* StackInit( int max_size )的实现如下,讲解将在注释中体现~
Stack* StackInit(int max_size)
{
//第一步:在函数内初始化一个指针,使她指向一块内存空间,这块内存空间是通过malloc在堆中动态分配的
Stack* stack = (Stack*)malloc( sizeof(Stack) );
//第二步:初始化结构体内的各个变量
stack->top = 0;
stack->size = max_size;
stack->data = (StackElement*)malloc( sizeof(StackElement) * max_size );
//第三步:返回这个指针,将指针控制权移交使用者个函数的对象。即Stack* a = StackInit(5);那么a将指向stack分
// 配的那块内存空间,而stack是个局部变量,存放在栈中(这个栈是虚拟内存中的栈,此栈非彼栈,别迷糊),随
// 着离开作用域{}而被释放。
return stack;
}
2.void StackFree(Stack stack)
void StackFree(Stack* stack)
{
//这里释放的是栈里的那个 StackElemnt* data 指针,不然的话内存泄漏了~
free(stack->data);
}
3.void StackPush(Stack* stack, int d) 这个函数是关键,是让我们能够在min中实现O(1)!
void StackPush(Stack* stack, int d)
{
//第一步:判断栈顶是否已经满了,top从0开始,每压入一个元素top就+1直到达到到size
// (就是我们在初始化的是侯分配的栈的空间大小,即一个栈能放几个StackElement)
// 不够就报个错
if ( stack->top == stack->size )
{
perror("out of space");
}
//第二步:创建一个指针指向栈顶,即将栈顶地址附给p,同时将p指向的那个对象给赋值
// 要注意p->min这里,如果栈顶top == 0,那么min设为d,也就是说空栈的时候
// 最小值就是p->data。否则min为stack->data[(stack->top) - 1]这个StackElement
// 的min。至于为什么要这样?请看第三步。
StackElement* p = &(stack->data[stack->top]);
p->data = d;
p->min = ( stack->top == 0 ? d : stack->data[(stack->top) - 1].min );
//第三步:这里比较了一下我们现在的最小值和d的关系,若d更小一些,那么新添加进来的这个元素的
// min就是d了。这下明白了吧。也就是在push里加了这样的一个判断使得每次都将栈中的最小值
// 附给刚进栈的元素的min,所以每次只要在栈顶下面的那个StackElement中的min就是栈中最小
if ( p->min > d )
{
p->min = d;
}
//第四步:栈顶自增,记录栈顶的位置
stack->top++;
}
4.int StackPop(Stack* stack) 这个pop没什么好说的,看看就好
int StackPop(Stack* stack)
{
if ( stack->top == 0 )
{
perror("This stack is empty");
}
return stack->data[--stack->top].data;
}
5.int StackFindMin(Stack* stack) 前面讲了这个函数其实就是去栈顶下面的那个元素取值就行啦
int StackFindMin(Stack* stack)
{
//不用if判断,用个assert判断一下栈顶是否为空也行~就是不提示直接报错了:)
assert( stack->top );
return stack->data[stack->top - 1].min;
}
最后,再来个main()函数就ok啦。
int main()
{
Stack* _test_stack = StackInit(7); //
StackPush(_test_stack, 10);
StackPush(_test_stack, 3);
StackPush(_test_stack, 4);
StackPush(_test_stack, 6);
StackPush(_test_stack, 12);
int min = StackFindMin(_test_stack);
printf( "%d\n", min );
StackFree(_test_stack);
free(_test_stack);
return 0;
}
看完了这些分开的代码片段,我们再从头看一下完整的程序:
/*************************************************
*
*作者:钟凌霄
*时间:2014.1.5
*题目:设计包含min 函数的栈
*
*************************************************/
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
//*************************************************
typedef struct StackElement
{
int data;
int min;
} StackElement;
//*************************************************
typedef struct Stack
{
StackElement* data;
int top;
int size;
} Stack;
//**************************************************
Stack* StackInit(int max_size)
{
Stack* stack = (Stack*)malloc( sizeof(Stack) );
stack->top = 0;
stack->size = max_size;
stack->data = (StackElement*)malloc( sizeof(StackElement) * max_size );
return stack;
}
//**************************************************
void StackFree(Stack* stack)
{
free(stack->data);
}
//**************************************************
void StackPush(Stack* stack, int d)
{
if ( stack->top == stack->size )
{
perror("out of space");
}
StackElement* p = &(stack->data[stack->top]);
p->data = d;
p->min = ( stack->top == 0 ? d : stack->data[(stack->top) - 1].min );
if ( p->min > d )
{
p->min = d;
}
stack->top++;
}
//***************************************************
int StackPop(Stack* stack)
{
if ( stack->top == 0 )
{
perror("This stack is empty");
}
return stack->data[--stack->top].data;
}
//****************************************************
int StackFindMin(Stack* stack)
{
assert( stack->top );
return stack->data[stack->top - 1].min;
}
//**********************main**************************
int main()
{
Stack* _test_stack = StackInit(7);
StackPush(_test_stack, 10);
StackPush(_test_stack, 3);
StackPush(_test_stack, 4);
StackPush(_test_stack, 6);
StackPush(_test_stack, 12);
int min = StackFindMin(_test_stack);
printf( "%d\n", min );
StackFree(_test_stack);
free(_test_stack);
return 0;
}
三.总结
这部分的知识大家可以搜索栈的相关知识以便更深层次的去理解题目。