目录
一、什么是栈
栈是一种特殊的线性表,只允许在固定的一端进行插入和删除操作。
进行数据插入和删除的一端叫做栈顶,另外一端称为栈底。
入栈:向栈中进行插入的操作称为入栈/进栈/压栈,在栈顶入数据。
出栈:向栈中进行删除的操作称为出栈,在栈顶出数据。顶部元素不出去,下面的元素是出不去的
可以看一下下列几道选择题,看看对栈是否理解透彻~
1.一个栈的初始状态为空。现将元素1、2、3、4、5、A、B、C、D、E依次入栈,然后再依次出栈,则元素出栈的顺序是( )。
> A 12345ABCDE
> B EDCBA54321
> C ABCDE12345
> D 54321EDCBA
这道题答案很显然是B
2.若进栈序列为 1,2,3,4 ,进栈过程中可以出栈,则下列不可能的一个出栈序列是()
> A 1,4,3,2
> B 2,3,4,1
> C 3,1,4,2
> D 3,4,2,1
注意,栈是可以边入边出的,我入1之后,可以立刻把1出去。知道这个之后,对每一个选项进行模拟即可。
A选项:入1之后,立刻出1,然后入2、3、4,之后出栈,4被出出去,在出栈,3被出出去,在出栈,2被出出去,因此A是正确的
B选项:入1之后,入2,在立刻出2,然后入3出3,入4出4,最后出1,因此B也是正确的
C选项:入1、2之后,入3然后立刻出3,此时栈顶是2,要出栈只能把2出出去,不可能出到1,因此,答案选C
D选项:入1、2,之后入3出3,入4出4,在继续出栈,2、1被出出去。因此D也是正确的
二、栈的实现
我们可以用一个顺序表,将顺序表的起始位置当做栈底,最后一个元素位置就是栈顶,入数据的时候就是尾插,出数据的时候就是尾删。
同样,我们也可以用链表,链表的第一个节点就是栈底,最后一个节点就是栈顶,入栈的时候直接尾插,出栈的时候直接尾删。但是,问题来了,链表的尾插是不是要找尾呢?是的呀,我们可以定义一个tail,记录尾部节点,这样尾插就好办了,但还有尾删的问题呀,尾删要找尾的前一个节点,怎么办呢?要用循环链表?是不是太麻烦了。但是顺序表呢?顺序表的尾插和尾删非常香。
因此,我们还是用顺序表来实现即可。
1、栈的结构
既然使用顺序表,那肯定是要有个数组的,数组肯定要有容量的,这里可以实现静态的也可以实现动态的。静态的就是数组的大小给死,但是很多情况下我们是不知道这个栈要存多少数据的,因此我们还是实现动态扩容版本的,既然是动态扩容,那我们扩容的依据是什么呢?是不是数据个数和容量大小,如果数据个数 >= 容量的大小,说明数组满了,此时就要扩容,因此我们还要定义两个变量,一个是当前数据个数,一个是容量大小,既然有多个变量,我们可以用结构体存起来。
typedef int StDataType;
typedef struct stack
{
StDataType* a;
int capicity; // 当前栈的空间大小
int top; //表示栈顶元素的下一个位置,同时也是当前栈的元素个数
}stack;
2、栈的初始化
定义了结构之后,首先要对它进行初始化,要做的工作就是给数组a开一点空间,给capicity和top赋予初始值。可以先用malloc给数组a开上4个空间,capicity就是4,这里top就表示栈顶元素的下一个位置,此时栈是空的,栈顶元素的下一个位置就是0,同时它还是当前栈的元素个数。空栈时,top就是0。
void StackInit(stack* st)
{
st->a = (StDataType*)malloc(sizeof(StDataType) * 4);
if (st->a == NULL)
{
perror("stack init fail");
return;
}
st->capicity = 4;
st->top = 0;
}
3、栈的入栈
入栈就是在尾部插入数据。
函数头
插入数据,你要告诉我,你要对哪一个栈进行插入,你要插入哪一个数 据,因此要有两个参数,要改变结构体使用结构体的指针即可,因此第一个参数是一个结构体指针,第二个参数就是要插入的数据,不需要返回值。
函数体
- 有没有可能我给你一个栈的指针是空,让你插入元素呢?有可能呀,栈指针都为空,我怎么插入数据呢?因此要先断言栈的指针不为空。
- 插入数据,元素个数是会增长的,因此空间容量就有可能不够,就需要扩容,因此要进行扩容的判断,如果当前元素个数等于容量,就用realloc进行扩容,更新capiticy。
- 判断完前两部之后就可以进行插入,插入到哪一个位置呢?插入到尾部呀?尾部是哪里呀,是不是就是top呀?top就是栈顶元素的下一个位置。之后再让top++即可
void StPush(stack* st, StDataType x)
{
assert(st);
// 扩容
if (st->top == st->capicity)
{
StDataType* tmp = realloc(st->a, sizeof(StDataType) * st->capicity * 2);
if (tmp == NULL)
{
perror("扩容失败");
return;
}
st->a = tmp;
st->capicity = st->capicity * 2;
}
st->a[st->top] = x;
st->top++;
}
4、栈的出栈
出栈就是在尾部删除数据。
函数头
告诉我要出哪一个栈,因此要有栈指针,需不需要有返回值呢?可以有也可以没有,如果有,返回值是什么呢?既然是出栈,那当然就是栈顶元素咯。这里我们就不需要返回值了,因为C++的栈是没有返回值的。就跟C++一致吧,其实有返回值是挺好用的。
函数体
函数体就在简单不过了,同样要进行断言,如果栈指针是空,或者栈里没有元素,那我出啥呢?顺序表尾删太easy了,直接将top--即可,访问不到就完事了。
5、返回栈顶元素
函数头
同样告诉我返回哪一个栈的栈顶元素,不需要返回值。
函数体
同样需要断言,如果栈为空,或者栈指针是空,根本返回不了栈顶元素,直接报错,top是栈顶元素的下一个位置,要返回栈顶直接返回top-1位置的元素即可。
StDataType StTop(stack* st)
{
assert(st);
assert(!StEmpty(st));
return st->a[st->top - 1];
}
6、栈是否为空
如果栈的top为0,就是空
bool StEmpty(stack* st)
{
assert(st);
return st->top == 0;
}
7、返回栈的元素个数
top即为栈顶元素个数
int StSize(stack* st)
{
assert(st);
return st->top;
}
8、栈的打印
我们要如何打印一下栈里面的元素呢?由于栈的特性,因此我们只能从栈顶先获取栈顶元素,然后在把栈顶元素出栈,重复这个操作,直到栈为空。
while (!StEmpty(&st))
{
printf("%d\n", StTop(&st));
StPop(&st);
}