关于本系列
如果你能够在阅读本文之后对该知识有一个初步的了解,知道基本的概念,那就足够了。
这就是本系列的创作初衷。
希望能帮助到正在学习这个知识点的你。
同时,为了不让编程语言成为阅读的障碍,本系列的文章都会给出C,Java,Python的代码(Python的代码在有空之后会补上)。但是本人是菜鸡,基本上是一边查语法一边进行写作,肯定会有诸多不足,请大家多多担待。
欢迎大家的任何意见,由衷感激。
前言
这天,小明带领他的好朋友小A,小B,小C来到了森林之中探险。在探险的过程中,他们发现了一个仅能通过一个人的山洞,大家都无一例外的 想到了《桃花源记》 想进去探险,小明自告奋勇决定第一个进去,小A,小B,小C则紧随其后。
他们进去之后发现,这个山洞和他们想象的完全不一样,根本没有 小桥流水人家 通道,进去之后发现这个山洞是走不通的,他们感到很气馁,但也没办法,走不下去了,只能出去。
小明作为领头大哥,肯定要第一个出去,所以小明就让小A让一下,让他先出去。
小A想侧身让大哥先出去,但是他也没办法动,因为位置太小了,他也没有办法做侧身的动作,他只能让小B赶紧先出去。
小B也很无奈呀,他也动不了,于是只能喊小C快点出去。
小C听到催促,发现确实是不太好侧身,于是他直接自己就走出去了,然后喊他们快点出来。
小B看到小C出去了,寻思着他也没办法转身,所以也直接出去了,然后喊小A快出来,不要挡到大哥小明。
小A看现在这个情况,好像也只能先出去,不然小明也没办法动,所以他也出去了。
小明一看他们都出去,山洞里面终于没有人阻挡他,于是他终于能出去了。
小明成功从山洞出来之后说道,这破山洞,以后都不会再来了。
可是真的会这样吗?小明之后可能会经常遇到类似这个山洞的事情,也许会想起这一次的探险吧。
栈
像前言故事中所述,那一个阻挡小明探索步伐的山洞,其实就是一个栈。
在前文中提到了线性表。而栈,其实是一种受到一定操作约束的线性表,因为,它只允许在一端进行插入和删除操作,如同故事中的山洞,小明一伙人只能从山洞口进去,从山洞口出来。
像这种只能从山洞口进去,从山洞口出来的操作,也就是我们所说的栈的压入和弹出。也就是压入(插入)数据,弹出(删除)数据
经过上面的故事来看,小明是第一个进去的,但是却是最后一个出来的;同样,小C是最后一个进去的,但是是第一个出来的。所以我们发现,栈的特点就是LIFO(Last in, First out)——先入后出,后入先出。
书接上文,我们知道 (不知道也无所谓,后续可能会出一个线性表的专题,大概?) ,线性表是可以有两种存储结构的:
- 顺序存储结构,是将数据依次存储在连续的整块内存空间之中,比较直观浅显的说就是用数组来存储
- 链式存储结构,是将数据分散存储在内存空间之中,通过''指针''来建立链接的联系,浅显的说就如同上一篇文章中的链表,通过节点来将数据互相连接起来。
在实现栈之前,补充一个调用栈的概念,方便大家理解为什么本文选用顺序存储结构来实现栈,当然,用链式存储结构来做也是没有问题的,各有利弊,只是我的选择带有比较强的主观因素。
调用栈
我们先简单了解一下什么是调用栈。
假设我们的main()方法(也可以叫函数,之后都用方法代替)中调用了a()方法,a()方法中调用了b()方法,然后b()方法中调用了a()方法,结果会发生什么呢?
首先执行main()方法,该方法入栈。
main()方法先执行,调用a()方法,a()方法入栈。
a()方法又调用b()方法,b()方法接着入栈。
b()方法又继续调用a()方法..形成循环调用,如果一直循环下去,那么会怎么样呢?
当然是boom! Stack Overflow! (栈溢出)
因为我们的每个方法的调用都会需要内存,如果我们不对调用栈进行限制,像我们上面的操作,不断循环调用a(),b(),a(),b(),那么会导致内存不断被侵占,这种情况是很恐怖的。因此我们必须要对涉及栈的操作进行限制,设置一定的上限,超出设定的上限就爆栈,保护内存安全。
在此考虑下,我选择用顺序存储的结构来简单实现一个栈。
实现一个栈
本着从简,易理解的目的,所以关于一些泛型和扩容之类的操作会进行省略,只实现最基础的功能 压入(push) 和 弹出(pop) 。
C
//设置栈上限
#define MAXSIZE 64
//顺序存储结构实现
typedef struct
{
int data[MAXSIZE];
int top; //用于指向栈顶的指针
}Stack;
//建立一个空栈
Stack* createStack(){
//分配地址
Stack* stack = (Stack*)malloc(sizeof(Stack));
//初始化栈顶指针
stack->top = 0; //top=0表示空栈
return stack;
}
Java
//顺序存储结构实现
public class Stack {
//设置栈上限
private final int MAXSIZE = 64;
private int[] data;
//栈顶指针
private int top;
//无参构造,初始化
public Stack(){
data = new int[MAXSIZE];
top = 0;
}
}
以Java的代码来分析吧,
-
MAXSIZE: 设置了栈的上限。
-
data: 具体存放数据的数组,用作模拟栈。
-
top: 用来指向栈顶,同时作为下标来使用,进行存放数据。
初始化之后,我们就得到了一个空栈,该栈的一共有64层,也可以说栈的深度是64。
此时,我们的top指向下标0,代表下标0是没有元素的,也就是当前为空栈。
压入(psuh)
压入操作也可以叫做插入元素。
C
//压入 :添加元素到栈顶
void push(Stack* stack,int parameter){
//先检查是否超过栈的上限
if( stack->top==MAXSIZE?1:0 ){
//在控制台打印爆栈消息并且结束方法
printf("Stack Overflow!\n");
return ;
}
//先设置当前数组下标位置的值为parameter,然后指向下一个位置
stack->data[stack->top++] = parameter;
}
Java
//检查是否超过栈的上限
private boolean isOverFlow(){
//如果top==MAXSIZE,则代表栈满了
return top==MAXSIZE?true:false;
}
//压入 :添加元素到栈顶
public void push(int parameter){
//先检查是否超过栈的上限 下限:Underflow
if( isOverFlow() ){
//在控制台打印爆栈消息并且结束方法
System.out.println("Stack Overflow!");
return ;
}
//先设置当前数组下标位置的值为parameter,然后指向下一个位置
data[top++] = parameter;
}
执行插入操作的时候必须检查索引是否合法,也就是先检查当前的栈也没有满,在没有满的情况下才能执行插入操作。
data[top++] = parameter 的意思是,先将当前位置放入元素,然后top指针指向上一层,这里我们假设放入一个5。
top的作用在兼具下标的同时指向当前栈的栈顶,上面我们加入了一个元素,那么我们当前的栈顶就是下标为1的位置,但是我们栈里面还是只有一个1元素,也就是top指针永远指向为空的上一层,保证栈的压入和弹出操作。
当top指向最后一个下标63的上面,就代表当前的栈满了,不能再进行操作了。
弹出(pop)
弹出也叫做删除操作,对栈来说,就是删除栈顶元素。
C
//弹出 :删除栈顶元素
void pop(Stack* stack){
//检查是否超过栈的下限
if( stack->top==0?1:0 ){
//在控制台打印爆栈消息并且结束方法
printf("Stack Overflow!\n");
return ;
}
//因为添加元素之后指针会指向下一个元素,那么我们实际存储的元素是指针前一个
//也就是我们需要先将指针指回上一个元素,令该元素为0,代表当前位置是没有元素了的
stack->data[--stack->top] = 0;
}
Java
//检查是否越界
private boolean isUnderflow(){
//如果top为0,则代表当前栈没有元素
return top==0?true:false;
}
//弹出 :删除栈顶元素
public void pop(){
if( isUnderflow() ){
//在控制台打印爆栈消息并且结束方法
System.out.println("Stack Overflow!");
return ;
}
//因为添加元素之后指针会指向下一个元素,那么我们实际存储的元素是指针前一个
//也就是我们需要先将指针指回上一个元素,令该元素为0,代表当前位置是没有元素了的
data[--top] = 0;
}
执行删除的操作的时候同样要注意检查索引,也就是需要判断top>0,因为当top=0的时候,此时的栈是空栈,里面是没有元素的。
然后就是区别与压入的操作,压入是先将元素放到top的位置,然后top++;
弹出的话需要先top--,再将对应位置的元素删除,令当前位置为空,这样就完成了弹出,同时top保持指向栈顶。
先让top自减,指向当前的栈顶元素。
然后删除了该元素,就成功弹出了一个元素,并且top指针还是保持指向栈顶。
然后再模拟弹出至空栈的情况。
1. 当前栈内的情况:
2. top自减,此时top为0:
3. 删除掉当前下标为0处的元素,这样栈就为空了:
完整代码
如果有写错的地方,欢迎大家指出,由衷感激。
C
#include <stdio.h>
//设置栈上限
#define MAXSIZE 64
//顺序存储结构实现
typedef struct
{
int data[MAXSIZE];
int top; //用于栈顶指针
}Stack;
//建立一个空栈
Stack* createStack(){
//分配地址
Stack* stack = (Stack*)malloc(sizeof(Stack));
//初始化栈顶指针
stack->top = 0; //表示空栈
return stack;
}
//压入 :添加元素到栈顶
void push(Stack* stack,int parameter){
//先检查是否超过栈的上限
if( stack->top==MAXSIZE?1:0 ){
//在控制台打印爆栈消息并且结束方法
printf("Stack Overflow!\n");
return ;
}
//先设置当前数组下标位置的值为parameter,然后指向下一个位置
stack->data[stack->top++] = parameter;
}
//弹出 :删除栈顶元素
void pop(Stack* stack){
//检查是否超过栈的下限
if( stack->top==0?1:0 ){
//在控制台打印爆栈消息并且结束方法
printf("Stack Overflow!\n");
return ;
}
//因为添加元素之后指针会指向下一个元素,那么我们实际存储的元素是指针前一个
//也就是我们需要先将指针指回上一个元素,令该元素为0,代表当前位置是没有元素了的
stack->data[--stack->top] = 0;
}
//检查当前栈顶元素
int peek(Stack* stack){
int top = stack->top;
//先判断索引是否合法
if( top <= MAXSIZE && top > 0){
return stack->data[top-1];
}
return -1;
}
//写了比较简陋的测试方法
int main(){
Stack* stack = createStack();
int i;
for(i = 0;i<70;i++){
push(stack,i);
}
printf("当前栈顶元素为:%d\n",peek(stack));
for(i = 0;i<62;i++){
pop(stack);
}
printf("删除之后栈顶元素为:%d\n",peek(stack));
pop(stack);
printf("删除之后栈顶元素为:%d\n",peek(stack));
pop(stack);
printf("删除之后栈顶元素为:%d\n",peek(stack));
return 0;
}
Java
//顺序存储结构实现
public class Stack {
//设置栈上限
private final int MAXSIZE = 64;
private int[] data;
//栈顶指针
private int top;
//无参构造,初始化
public Stack(){
data = new int[MAXSIZE];
top = 0;
}
//检查是否超过栈的上限
private boolean isOverFlow(){
//如果top==MAXSIZE,则代表栈满了
return top==MAXSIZE?true:false;
}
//压入 :添加元素到栈顶
public void push(int parameter){
//先检查是否超过栈的上限 下限:Underflow
if( isOverFlow() ){
//在控制台打印爆栈消息并且结束方法
System.out.println("Stack Overflow!");
return ;
}
//先设置当前数组下标位置的值为parameter,然后指向下一个位置
data[top++] = parameter;
}
//检查是否越界
private boolean isUnderflow(){
//如果top为0,则代表当前栈没有元素
return top==0?true:false;
}
//弹出 :删除栈顶元素
public void pop(){
if( isUnderflow() ){
//在控制台打印爆栈消息并且结束方法
System.out.println("Stack Overflow!");
return ;
}
//因为添加元素之后指针会指向下一个元素,那么我们实际存储的元素是指针前一个
//也就是我们需要先将指针指回上一个元素,令该元素为0,代表当前位置是没有元素了的
data[--top] = 0;
}
//检查当前栈顶元素
public int peek(){
//先判断索引是否合法
if( top <= MAXSIZE && top > 0){
return data[top-1];
}
return -1;
}
}