✨✨小新课堂开课了,欢迎欢迎~✨✨
🎈🎈养成好习惯,先赞后看哦~🎈🎈
所属专栏:数据结构与算法
小新的主页:编程版小新-CSDN博客
1.栈的定义
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。
进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。
2.栈的分类
这里对栈的分类,其实就是用不同的方式来实现栈。可以用顺序表和链表来实现。这里的链表指的是单链表,当然双向链表也能实现栈,但是选双向链表的话不如选单链表,少维护一个指针,而且单链表的实现也十分的简单。
ps:这里在实现链式栈的时候采用的头插法,因为栈的特点是后进先出,采用头插法入栈和出栈都很方便,比尾插法高效。
3.栈的声明
1.顺序栈
a指向一块空间,top指的是栈顶元素,capacity指的是栈内数据的个数。
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;//指栈顶
int capacity;//容量大小
}ST;
2.链式栈
链式栈里只需要一个结构体指针top和记录栈的大小的size即可。
typedef int STDataType;
typedef struct SListNode
{
STDataType data;
struct SListNode* next;
}STNode;
typedef struct Stack
{
STNode* top;
int size;
}Stack;
4.栈的功能
1.栈的初始化
2.检测栈是否为空
3.入栈
4.出栈
5.返回栈顶元素
6.返回栈的大小
7.打印栈
8.销毁栈
5.实现栈的功能
5.1初始化栈
1.顺序栈
//初始化
void STInit(ST* pst)
{
assert(pst);
pst->a = NULL;
//top指向栈顶数据的下一个位置
pst->top =0;
//top指向栈顶数据
//pst->top = -1;
pst->capacity = 0;
}
在初始化top时分两中情况。如果将top初始化为0,那么top就必须指的是栈顶数据的下一位置,要想top指向栈顶元素,就要将top初始化为-1。(想让top指向栈顶元素,还把top初始化为0的话,当top==0时栈内有数据还是无数据是区分不开的,因此有了这两种初始化的方式)
2.链式栈
只需将维护栈的Stack指针初始化即可。
//初始化
void StackInit(Stack* pst)
{
assert(pst);
pst->top = NULL;
pst->size = 0;
}
5.2检测栈是否为空
1.顺序栈
当top==0时就像初始化时的那样,栈内没有数据,栈为空。
//判空
bool STempty(ST* pst)
{
assert(pst);
return pst->top == 0;
}
2.链式栈
像初始化时给定的那样,当pst->top==NULL时,栈内没有数据,栈为空。
//判空
bool StackEmpty(Stack* pst)
{
assert(pst);
return (pst->top == NULL);
}
5.3入栈
1.顺序栈
入栈前要检查空间是否充足,空间不够的话要扩容。
//入栈
void STPush(ST* pst,STDataType x)
{
assert(pst);
//扩容
if (pst->top == pst->capacity)
{
int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
STDataType* tmp = (STDataType*)realloc(pst->a, newcapacity * sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc fail");
return;
}
pst->a = tmp;
pst->capacity = newcapacity;
}
pst->a[pst->top] = x;
pst->top++;
}
2.链式栈
每添加一个数据(压栈),就要创建一个节点。并且这里采用头插法的方式插入数据。(f方便出数据即出栈)
STNode* CreatNode(STDataType x)
{
STNode* newnode = (STNode*)malloc(sizeof(struct SListNode));
if (newnode == NULL)
{
perror("malloc fail");
return;
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
//入栈
void StackPush(Stack* pst, STDataType x)
{
assert(pst);
STNode* newnode = CreatNode(x);
if (pst->top == NULL)//if(Stackempty(pst))
{
pst->top = newnode;
}
else
{
newnode->next = pst->top;
pst->top = newnode;
}
pst->size++;
}
5.4出栈
1.顺序栈
出栈的时候不需要删除数据,只需让top往前走即可。
//出栈
void STPop(ST* pst)
{
assert(pst);
assert(pst->top > 0);
//assert(!STempty(pst));
pst->top--;
}
2.链式栈
pst->top指向的就是要删除的数据,只需释放头节点即可(不要忘记记住下一个节点的位置哦)
//出栈
void StackPop(Stack* pst)
{
assert(pst);
assert(!StackEmpty(pst));
STNode* next = pst->top->next;
free(pst->top);
pst->top = next;
pst->size--;
}
5.5返回栈顶元素
1.顺序栈
top初始化的时候指向栈顶元素的下一个位置,要返回栈顶元素的时候,就要让top-1,当然这是在栈内有数据的前提下。
//取栈顶数据
STDataType STTop(ST* pst)
{
assert(pst);
assert(pst->top > 0);
//assert(!STempty(pst));
return pst->a[pst->top - 1];
}
2.链式栈
//取栈顶元素
STDataType StackTop(Stack* pst)
{
assert(pst);
assert(!StackEmpty(pst));
return pst->top->data;
}
5.6返回栈的大小
1.顺序栈
这里就把顺序表想象成数组,当然顺序表的底层就是数组,这里top是采用下标访问的。top所代表的下标就是栈的大小。
//获取数据个数
int STSize(ST* pst)
{
assert(pst);
return pst->top;
}
2.链式栈
//返回栈的大小
int StackSize(Stack* pst)
{
assert(pst);
return pst->size;
}
5.7打印栈
1.顺序栈
//打印栈
void STPrint(ST* pst)
{
assert(pst);
assert(pst->top > 0);
//assert(!STempty(pst));
for (int i = pst->top - 1; i >= 0; i--)
{
printf("%d\n", pst->a[i]);
}
}
2.链式栈
//打印栈
void StackPrint(Stack* pst)
{
assert(pst);
assert(!StackEmpty(pst));
while (pst->top)
{
printf("%d\n", pst->top->data);
pst->top = pst->top->next;
}
}
5.8销毁栈
1.顺序栈
//销毁
void STDestroy(ST* pst)
{
assert(pst);
free(pst->a);
pst->a = NULL;
pst->top = pst->capacity = 0;
}
2.链式栈
//销毁栈
void StackDestroy(Stack* pst)
{
assert(pst);
while (pst->top)
{
STNode* next = pst->top->next;
free(pst->top);
pst->top = next;
}
}
6.顺序栈和链式栈的区别
在看顺序栈和链式栈的区别之前,我们先来回顾一下顺序表和单链表的区别。
6.1顺序表和链表的区别
不同点 | 顺序表 | 双链表 | |||
储存空间上 | 物理上一定连续 | 逻辑上连续,但物理上不一定连续 | |||
随机访问 | 支持O(1) | 不支持O(N) | |||
任意位置插入或删除 | 可能需要搬运元素,效率低下 | 只需修改指针指向 | |||
插入 | 动态顺序表,空间不够时需要扩容 | 没有容量的概念(按需申请释放) | |||
应用场景 | 元素高效储存+频繁访问 | 在任意位置插入和删除频繁 | |||
缓存利用率 | 高 | 低 |
6.2顺序栈和链式的区别
7.栈的实际应用
栈在现实中有很多应用,以下是一些常见的例子:
1. 网页浏览器的历史记录:每次访问一个新页面时,将当前页面压入栈中,当用户点击后退按钮时,从栈中弹出上一个页面。
2. 文本编辑器的撤销操作:每次执行一个操作(如插入、删除等)时,将操作前的状态压入栈中,当用户点击撤销按钮时,从栈中弹出上一个状态并恢复。
8.完整代码
1.顺序栈
stack.h:
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
//初始化
void STInit(ST* pst);
//入栈
void STPush(ST* pst,STDataType X);
//出栈
void STPop(ST* pst);
//取栈顶数据
STDataType STTop(ST* pst);
//判空
bool STempty(ST* pst);
//获取数据个数
int STSize(ST* pst);
//打印栈
void STPrint(ST* pst);
//销毁
void STDestroy(ST* pst);
stack.c:
#include"stack.h"
//初始化
void STInit(ST* pst)
{
assert(pst);
pst->a = NULL;
//top指向栈顶数据的下一个位置
pst->top =0;
//top指向栈顶数据
//pst->top = -1;
pst->capacity = 0;
}
//入栈
void STPush(ST* pst,STDataType x)
{
assert(pst);
//扩容
if (pst->top == pst->capacity)
{
int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
STDataType* tmp = (STDataType*)realloc(pst->a, newcapacity * sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc fail");
return;
}
pst->a = tmp;
pst->capacity = newcapacity;
}
pst->a[pst->top] = x;
pst->top++;
}
//出栈
void STPop(ST* pst)
{
assert(pst);
assert(pst->top > 0);
//assert(!STempty(pst));
pst->top--;
}
//取栈顶数据
STDataType STTop(ST* pst)
{
assert(pst);
assert(pst->top > 0);
//assert(!STempty(pst));
return pst->a[pst->top - 1];
}
//判空
bool STempty(ST* pst)
{
assert(pst);
return pst->top == 0;
}
//获取数据个数
int STSize(ST* pst)
{
assert(pst);
return pst->top;
}
//打印栈
void STPrint(ST* pst)
{
assert(pst);
assert(pst->top > 0);
//assert(!STempty(pst));
for (int i = pst->top - 1; i >= 0; i--)
{
printf("%d\n", pst->a[i]);
}
}
//销毁
void STDestroy(ST* pst)
{
assert(pst);
free(pst->a);
pst->a = NULL;
pst->top = pst->capacity = 0;
}
2.链式栈
Stack.h:
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
typedef int STDataType;
typedef struct SListNode
{
STDataType data;
struct SListNode* next;
}STNode;
typedef struct Stack
{
STNode* top;
int size;
}Stack;
//初始化
void StackInit(Stack* pst);
//判空
bool StackEmpty(Stack* pst);
//入栈
void StackPush(Stack* pst, STDataType x);
//出栈
void StackPop(Stack* pst);
//取栈顶元素
STDataType StackTop(Stack* pst);
Stack.c:
#include"stack.h"
//初始化
void StackInit(Stack* pst)
{
assert(pst);
pst->top = NULL;
pst->size = 0;
}
//判空
bool StackEmpty(Stack* pst)
{
assert(pst);
return (pst->top == NULL);
}
STNode* CreatNode(STDataType x)
{
STNode* newnode = (STNode*)malloc(sizeof(struct SListNode));
if (newnode == NULL)
{
perror("malloc fail");
return;
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
//入栈
void StackPush(Stack* pst, STDataType x)
{
assert(pst);
STNode* newnode = CreatNode(x);
if (pst->top == NULL)//if(StackEmpty(pst))
{
pst->top = newnode;
}
else
{
newnode->next = pst->top;
pst->top = newnode;
}
pst->size++;
}
//出栈
void StackPop(Stack* pst)
{
assert(pst);
assert(!StackEmpty(pst));
STNode* next = pst->top->next;
free(pst->top);
pst->top = NULL;
pst->top = next;
pst->size--;
}
//取栈顶元素
STDataType StackTop(Stack* pst)
{
assert(pst);
assert(!StackEmpty(pst));
return pst->top->data;
}
//返回栈的大小
int StackSize(Stack* pst)
{
assert(pst);
return pst->size;
}
//打印栈
void StackPrint(Stack* pst)
{
assert(pst);
assert(!StackEmpty(pst));
while (pst->top)
{
printf("%d\n", pst->top->data);
pst->top = pst->top->next;
}
}
//销毁栈
void StackDestroy(Stack* pst)
{
assert(pst);
while (pst->top)
{
STNode* next = pst->top->next;
free(pst->top);
pst->top = next;
}
}
结束了~
下次还记得来哦