栈
我们在用浏览器打开网页的时候,时常会点击页面上的某个链接跳转到其它页面浏览,又会在新的页面上,点击链接跳转到另一个新页面。另外,如果想回到上一个页面,就会点击浏览器的“返回”按钮,再点击一下又会返回上一个页面,而且每次点击“返回”只能返回上一级,浏览器的这个功能是怎么实现的呢?
浏览器的这个功能可以用 栈 来实现,当前浏览的页面我们叫它为栈顶元素,跳转到一个新页面我们叫元素入栈,点击“返回”按钮我们叫元素出栈,当然出栈前提是栈里要有元素,比如在浏览器里,如果已经返回到了最开始的页面,那就无法返回了。
栈有一个重要的性质,就是 先进后出 ,First In Last Out(FILO)。
什么是先进后出呢?在浏览器的例子里,先打开的页面的一定是到后面才会被返回到,后面打开的页面一定是先被返回到的。这个过程,我们可以用栈这种数据结构来模拟实现。
我们已经介绍了栈的基本性质。接下来,我们还是要去学习如何在代码中实现栈和它的一些基本操作。在此之前,我们还是先介绍栈的相关操作的算法流程。
同学们还记得我们在实现队列的时候是怎么做的吗?我们借助两个标记队尾和队首的变量来维护队列的结构。而在维护栈的结构时,我们同样也是用一个变量来标记栈顶元素。在构造函数中,我们会把栈顶标记初始化为 −1。
当我们要向栈中插入元素时,只需要让栈顶标记后移一位,之后把新元素插入到相应的位置。
栈插入操作的实现方法如下:
1. 判断栈是否已满,能否继续插入元素。
2. 栈顶标记后移一位。
3. 把新元素插入到当前栈顶标记的位置。
我们用一个具体的例子来演示入栈过程:
当前的栈中有 1、 3、 5 三个元素,栈顶标记为 2。我们要让一个元素 4 入栈。
此时栈未满,可以执行入栈操作。我们把栈顶标记加 1。
之后直接将新元素放入就完成了整个入栈过程,此时栈顶所在位置为 3。
出栈操作就更加简单了,只需要使栈顶标记减 1 就能实现栈顶元素的出栈了。当然,在此之前别忘了判断栈是否为空,如果栈已经是空的,则返回 false。
还是借助刚刚的栈,我们希望把刚刚插入的新元素删除。
很简单,当前栈顶位置为 3,我们直接把栈顶标记减 1。
现在 5 成为了新的栈顶,出栈操作完成。
借助栈先进后出的特性可以给我们带来很多便利,之后我们还会如何用栈来实现一个数列的反转。
用栈实现数列反转的算法流程如下:
1. 将一个数列的元素依次压入到栈中。
2. 将栈顶元素出栈。
3. 判断栈是否为空,不为空则回到步骤 2。
我们还可以同时构造多个栈来完成更多的任务,比如可以用两个栈实现表达式求值。
用栈实现表达式求值的算法流程如下:
1. 使用两个栈分别存储数值和运算符。
2. 读取表达式字符,数值存入数值栈,运算符和栈顶运算符比较优先级。
3. 通过运算符优先级不同选择将它压入栈或取出数值栈中两个元素进行计算,计算结果入栈。
4. 返回步骤 2,直至表达式全部读完。
5. 弹出一个运算符和两个数值进行运算,计算结果存储数值栈。
6. 当运算符栈不为空时,返回步骤 5,否则数值栈中剩余的最后一个元素就是表达式求值结果。
创建一个栈
首先我们先定义一个结构体Stack, 这一步只需写好结构体定义可以。
2. 接下来我们来定义一个int 类型的指针变量 elements, 用来存储栈的数据。
3. 接下来我们来定义两个int 类型的变量max_size 和 top_index. max_size 表示栈里最多可以有max_size 个元素,top_index 表示栈顶元素的位置。
4. 接下来我们来定义初始化函数init, 参数有两个,一个是Stack 类型的指针变量s, 另一个是int 类型的length_input ,length_input 代表栈的最多元素个数,没有返回值。
5. 现在我们来实现初始化函数
这里我们只需要完成三个操作,首先要给elements 数组分配 length_input 个 int 类型的内存,这里我们使用malloc , 然后将 length_input 的值赋值给max_size, 最后让 top_index 的 初始化为 -1.
6. 接下来我们来看看, 怎么在主函数里创建栈的一个实例。首先在主函数里定义一个 int 类型的变量n, 然后程序输出n, n 表示设定的栈最多元素数量。
7. 接下来我们来定义一个 Stack 的指针变量 stack, 然后分配一个 Stack 类型大小的内存。 之后调用初始化函数 init 完成栈的初始化,参数分别为stack 和 n , 表示预设栈里最多可以有n 个 元素。
8. 在函数结束前 我们还应该 释放所占用的内存空间,我们在一个函数 clear 完成 这些工作。
首先,在主函数前把clear 函数定义好,函数没有返回值,参数为 一个 Stack 类型的指针变量s.
9. 在clear 函数中, 我们要释放s->elements, 以及指针s。我们可以用free 来实现它。
10. 在clear 函数中,我们要释放s->elements, 以及指针s.。我们可以用free 来实现它。
#include <stdio.h>
#include <stdlib.h>
// 请在下面定义结构体栈 Stack
typedef struct Stack {
int *elements;
int max_size, top_index;
} Stack;
// 请在下面实现初始化函数 init
void init(Stack *s, int length_input){
s->elements = (int *)malloc(sizeof(int) * length_input);
s->max_size = length_input;
s->top_index = -1;
}
// 请在下面实现 clear 函数
void clear(Stack *s) {
free(s->elements);
free(s);
}
int main() {
int n;
scanf("%d", &n);
Stack *stack = (Stack *)malloc(sizeof(Stack));
init(stack, n);
clear(stack);
return 0;
}
出栈操作
-
首先我们先在合适的地方定义返回值为 int 类型,参数为Stack 类型的指针参数s 的函数 pop . 还是一样,只要先些定义即可,稍后我们再来实现。
-
然后我们考虑下特殊情况,还记得之前讲的内容吗?栈的删除操作前提是栈里有至少一个元素,如果栈里没有元素则不能进行删除。
这里我们可以判断栈s 的 top_index 是否小于0, 确定栈是不是为空,如果为空那就返回ERROR 结束函数。 -
如果栈不为空,那我们就可以删除栈顶元素。
-
我们把栈s 的 top_index 减1 就可以认为删除栈顶元素了,删除成功后记得返回OK来结束函数。
-
这样我们就完成了删除栈顶元素的函数,接下来我们来看看怎么 在主函数里调用它。 还记得前面的规定吧,如果编号opr 等于 1 则表示 删除栈顶元素的操作。 我们在 opr 等于 0 的条件后面继续写,还是先把 else if 语句写好吧,稍后我们再来写全它。
-
接下来我们就来调用删除栈顶元素函数 pop 删除栈 stack 的栈顶元素, 如果函数返回值为1, 那么我们就输出 pop success! 提示下,然后再输出个换行。
-
如果函数返回值不是1, 那么我们就输出 pop failed! 用来提示,最后还是一样输出个换行。
-
请对应上面的 if 语句写好 else, 不要忘记了花括号。
#include <stdio.h>
#include <stdlib.h>
#define ERROR 0
#define OK 1
typedef struct Stack {
int *elements;
int max_size, top_index;
} Stack;
void init(Stack *s, int length_input) {
s->elements = (int *)malloc(sizeof(int) * length_input);
s->max_size = length_input;
s->top_index = -1;
}
int push(Stack *s, int element) {
if (s->top_index >= s->max_size - 1) {
return ERROR;
}
s->top_index++;
s->elements[s->top_index] = element;
return OK;
}
// 请在下面实现出栈函数 pop
int pop(Stack *s) {
if(s->top_index < 0) {
return ERROR;
}
s->top_index--;
return OK;
}
void clear(Stack *s) {
free(s->elements);
free(s);
}
int main() {
int n, m;
scanf("%d %d", &n, &m);
Stack *stack = (Stack *)malloc(sizeof(Stack));
init(stack, n);
for (int i = 1; i <= m; i++) {
int opr;
scanf("%d", &opr);
if (opr == 0) {
int element;
scanf("%d", &element);
if (push(stack, element)) {
printf("push success!\n");
} else {
printf("push failed!\n");
}
} else if (opr == 1) {
if(pop(stack)) {
printf("pop success!\n");
} else {
printf("pop failed!\n");
}
}
}
clear(stack);
return 0;
}
输出栈
#include <stdio.h>
#include <stdlib.h>
#define ERROR 0
#define OK 1
typedef struct Stack {
int *elements;
int max_size, top_index;
}Stack;
void init(Stack *s, int length_input) {
s->elements = (int *)malloc(sizeof(int) * length_input);
s->max_size = length_input;
s->top_index = -1;
}
int push(Stack *s, int element) {
if (s->top_index >= s->max_size - 1) {
return ERROR;
}
s->top_index++;
s->elements[s->top_index] = element;
return OK;
}
int pop(Stack *s) {
if (s->top_index < 0) {
return ERROR;
}
s->top_index--;
return OK;
}
// 请在下面实现输出栈顶函数 top
// 首先,我们写好函数的定义,请在合适的地方写一个 返回值 int 类型, 只有一个 Stack 类型的指针参数 s 的函数 top. 这一步完成函数的定义
// 栈顶元素即是栈 s 的 top_index 所指向的元素,所以栈顶元素应该是 编号 top_index 的元素,我们把它返回就好了。
// 这样我们就把访问栈顶元素的函数写好了, 接下来我们看看 怎么在 主函数里 调用该函数吧。
int top(Stack *s) {
return s->elements[s->top_index];
}
void clear(Stack *s) {
free(s->elements);
free(s);
}
int main() {
int n, m;
scanf("%d %d", &n, &m);
Stack *stack = (Stack *)malloc(sizeof(Stack));
init(stack, n);
for (int i = 1; i <= m; i++) {
int opr;
scanf("%d", &opr);
if (opr == 0) {
int element;
scanf("%d", &element);
if (push(stack, element)) {
printf("push success!\n");
} else {
printf("push failed!\n");
}
} else if (opr == 1) {
if (pop(stack)) {
printf("pop success!\n");
} else {
printf("pop failed!\n");
}
// 还记得之前的规定吧,opr 等于2 表示访问栈顶元素, 我们用 else if 语句接着之前的写即可
// 接下来我们就调用访问栈顶元素函数 top () 访问栈 stack 的栈顶元素,并且把结果输出,在输出结果后再输出一个换行吧 。
} else if (opr == 2) {
printf("%d\n",top(stack));
}
}
clear(stack);
return 0;
}