栈(stack)是一种运算受限的线性表。其受限是指进行插入和删除操作仅仅发生在该线性表的尾部。
Table of Contents
线性栈
栈的定义:
- 是一种只能在一端(一般是尾部)进行插入和删除操作的特殊线性表。
- 按照先进后出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶;需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。
- 栈也称为后进先出(List In First Out)的线性表,简称LIFO结构。
- 它是一个线性表,也即,栈元素具有线性关系,即前驱后继关系。
- 只不过它是一种特殊的线性表而已,进行插入和删除操作的只能在称为栈顶(top)的一端进行。另一端为栈底(bottom)。
什么是线性栈:
- 用一组地址连续的存储单元(即数组)依次存放栈底到栈顶的数据元素,栈底位置固定不变,栈顶位置随着入栈和出栈操作而变化。
- 线性栈的理解:立起来的数组,操作都在数组的最后元素位进行
线性栈的代码定义
#define MAXSIZE 100
typedef struct
{
int data[MAXSIZE];
int top;
}SeqStack;
特性:
- 栈中元素个数为零时称为空栈。
- 栈的插入操作,一般称为进栈(PUSH),也称压栈、入栈。删除则称为出栈(POP),也称弹栈。
- 它的特殊之处在于限制了这个线性表的插入和删除的位置,它始终只在栈顶进行。这也就使得:栈底是固定的,最先进栈的只能在栈底。
- 例如,递归时需要用到栈来记录断点;在浏览器的前进后退页也是利用栈来存储读取每个网页信息。
第一个线性栈
第一个线性栈
#include <stdio.h>
#define MAX 100
typedef struct
{
int data[MAX];
int top;
}stack;
int main()
{
stack MyStack;
//初始化栈
MyStack.data[MAX] = 0;
MyStack.top = -1;
//进栈操作
MyStack.data[++MyStack.top] = 100; //栈顶指针往上移
MyStack.data[++MyStack.top] = 200;
//取数据
printf("栈顶元素为:%d\n", MyStack.data[MyStack.top]);
//出栈操作
MyStack.top--;
printf("栈顶元素为:%d\n", MyStack.data[MyStack.top]);
return 0;
}
动态线性栈
为什么要引入动态线性栈?
- 在C语言中的数组要求在编译时必须确定数组的大小。
- 在利用数组做为线性栈的实现时,也会遇到数组容量不可以动态拓展的问题。
- 可以利用动态分配内存解决这个问题,即动态一维数组。
什么是动态线性栈?
- 动态的一维数组 + 栈的思想
- 动态申请一块内存,将栈中元素放入其中。当元素个数超过设定的最大长度时,可以在再动态申请更大的内存来存放元素。
- 通过动态内存申请返回的指针来以数组的形式访问栈中元素。
如何实现动态线性栈?
- 使用malloc函数申请内存,使用realloc函数扩大内存;
第一个动态线性栈
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef struct
{
int* data;
int top;
}stack;
int main()
{
int size = 5;
stack* MyStack = (stack*)malloc(sizeof(stack)); //为结构体分配内存
assert(MyStack);
//初始化
MyStack->data = (int*)malloc(sizeof(int) * size); //为动态线性栈分配内存
assert(MyStack->data != NULL);
MyStack->top = -1;
//压栈
MyStack->data[++MyStack->top] = 100;
MyStack->data[++MyStack->top] = 200;
MyStack->data[++MyStack->top] = 300;
//出栈
MyStack->top--;
printf("栈顶元素为:%d\n", MyStack->data[MyStack->top]);
//重新分配大小
size *= 2;
MyStack->data = (int*)realloc(MyStack->data, size); //重新分配内存
assert(MyStack->data != NULL); //注意,这将可能会导致原始的MyStack->data块内存泄露;这里仅作为演示,实际开发时慎用!
MyStack->data[++MyStack->top] = 400;
printf("栈顶元素为:%d\n", MyStack->data[MyStack->top]);
return 0;
}
链式栈
什么是链式栈:
- 链式栈 = 单链表 + 栈的思想
- 链式栈的本质:倒立竖着的单链表!
- 每次入栈一个元素,向链表中添加一个节点(相当于头插法),出栈一个元素,释放一个节点。
链式栈的本质就是一个“竖着”的链表!
特性:
- 利用单链表的头插法,可以避免每次在链表的尾部进行插入和删除,就要遍历整个链表来找到尾节点。
- 在头部进行插入和删除时,只需根据头指针即可找到链表的首元素结点。而无需遍历链表。所以链式栈的出,入栈通过对链表进行头删和头插来实现。
- 单链表中常用的的头结点也就失去了意义,不再需要头结点的。
链式栈的代码定义
typedef struct node
{
int data;
struct node* next;
}StackNode;
第一个链式栈
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef struct node
{
int data;
struct node* next;
}stack;
int main()
{
stack* top = NULL; //设置一个栈顶指针,方便出栈、取栈操作
stack* bottom = (stack*)malloc(sizeof(stack));
assert(bottom != NULL);
bottom->data = 100;
bottom->next = NULL;
top = bottom;
stack* NodeAa = (stack*)malloc(sizeof(stack));
assert(NodeAa != NULL);
NodeAa->data = 200;
NodeAa->next = bottom; //插入到bottom节点的前面,即头插法
top = NodeAa;
stack* NodeBb = (stack*)malloc(sizeof(stack));
assert(NodeBb != NULL);
NodeBb->data = 300;
NodeBb->next = NodeAa;
top = NodeBb;
printf("栈顶元素:%d\n", top->data);
//出栈
stack* t = top;
top = top->next;
free(t);
printf("栈顶元素:%d\n", top->data);
return 0;
}