目录
前言
栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
栈类似于子弹弹夹,入栈就像是装弹,出栈就像是退弹。
因为其是受到限制的线性表,则可以有两种实现方式,顺序表实现和链表实现,以下为实现方式:
一、顺序栈
1.顺序栈头文件及函数声明
我们可以新建头文件"stack.h"对顺序表函数声明进行保存,方便我们后期查看及使用
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#define INIT_SIZE 10
typedef int Element;
typedef struct Stack
{
Element* base;
int top; //存在 Element *top 类型通过指针指向栈顶元素 ,此处int类型是通过数组访问
int stackSize;
}Stack,*PStack;
//初始化栈
void Init_Stack(PStack ps);
//入栈
bool Push(PStack ps, Element val);
//出栈(如果出栈成功,需要告诉我出栈的值是多少,如果出栈失败,则无所谓)
bool Pop(PStack ps, Element* rtval);//rtval 是输出参数 帮助函数返回其他信息
//获取栈顶元素
bool Top(PStack ps, Element* rtval);
//获取有效元素个数
int GetSize(PStack ps);
//判空
bool IsEmpty(PStack ps);
//判满
bool IsFull(PStack ps);
//扩容函数
void Inc(PStack ps);
//清空
void Clear(PStack ps);
//销毁
void Destroy(PStack ps);
//打印
void Show(PStack ps);
2.初始化
我们需要一个栈的表头来帮助我们储存相关信息:
1.一个指向顺序栈的指针;
2.栈顶指针(实际为顺序表中下一个元素的插入位置,也是栈中有效值个数)
3.栈空间的总大小(可帮助我们扩容)
那么我们可以定义如下的一个结构体:
#define INIT_SIZE 10
typedef int Element;
typedef struct Stack
{
Element* base;
int top; //存在 Element *top 类型通过指针指向栈顶元素 ,此处int类型是通过数组访问
int stackSize;
}Stack,*PStack;
接着我们实现一个初始化函数来对以上内容进行初始化:
//初始化栈
void Init_Stack(PStack ps)
{
assert(ps != NULL);
//为栈空间申请内存
ps->base = (Element*)malloc(INIT_SIZE * sizeof(Element));
if (ps->base == NULL)
{
exit(1);
}
ps->top = 0;
ps->stackSize = INIT_SIZE;
}
完成以上操作,我们就得到一个简单的空的顺序栈,接下来可以对其进行相关操作。
3.扩容
当我们对更多数据进行存储时,我们就需要扩充栈的大小,来进一步保存数据。
在这里以3/2倍扩容。
//扩容函数
void Inc(PStack ps)
{
assert(ps != NULL && ps->base != NULL);
ps->base = (Element*)realloc(ps->base, sizeof(Element) * ps->stackSize * 2);
if (ps->base == NULL)
{
exit(1);
}
ps->stackSize = ps->stackSize * 3 / 2;
}
封装扩容函数,可方便对数据的保存,减少为了扩容产生代码冗余。
4.入栈
对数据进行入栈,即是直接对栈中顺序表下一个空位置进行赋值操作。
//入栈
bool Push(PStack ps, Element val)
{
assert(ps != NULL);
if (ps == NULL)
{
return false;
}
if (IsFull(ps))//栈满需扩容
{
Inc(ps);
}
//入栈
ps->base[ps->top] = val;
ps->top++;//或者添加至上一步,这里因为后期理解方便
return true;
}
5.获取栈顶元素
这里与出栈基本相同,但是无需删除数据。
rtval是输出参数,帮助获取栈顶元素。
//获取栈顶元素 与出栈不同的是 不用减top
bool Top(PStack ps, Element* rtval)
{
assert(ps != NULL);
if (IsEmpty(ps))
{
return false;
}
*rtval = ps->base[ps->top - 1];
return true;
}
6.出栈
rtval是输出参数,帮助获取出栈元素。
//出栈(如果出栈成功,需要告诉我出栈的值是多少,如果出栈失败,则无所谓)
bool Pop(PStack ps, Element* rtval)//rtval 是输出参数 帮助函数返回其他信息
{
assert(ps != NULL);
if (IsEmpty(ps))
{
return false;
}
ps->top--;
*rtval = ps->base[ps->top];
return true;
}
7.清空与销毁
1.清空
当栈中数据无需使用,我们可以对栈进行清空操作,实际即是将栈顶指针置为0;
//清空
void Clear(PStack ps)
{
assert(ps != NULL);
ps->top = 0;
}
2.销毁
销毁相当于进行初始化相反的操作,将向计算机申请的内存释放,将容量置为0。
//销毁
void Destroy(PStack ps)
{
free(ps->base);
ps->base = NULL;
ps->stackSize = ps->top = 0;
}
8.顺序栈源文件及整体函数实现
源文件:"stack.c"
#include "stack.h"
//初始化栈
void Init_Stack(PStack ps)
{
assert(ps != NULL);
ps->base = (Element*)malloc(INIT_SIZE * sizeof(Element));
if (ps->base == NULL)
{
exit(1);
}
ps->top = 0;
ps->stackSize = INIT_SIZE;
}
//入栈
bool Push(PStack ps, Element val)
{
assert(ps != NULL);
if (ps == NULL)
{
return false;
}
if (IsFull(ps))//栈满需扩容
{
Inc(ps);
}
//入栈
ps->base[ps->top] = val;
ps->top++;//或者添加至上一步,这里因为后期理解方便
return true;
}
//出栈(如果出栈成功,需要告诉我出栈的值是多少,如果出栈失败,则无所谓)
bool Pop(PStack ps, Element* rtval)//rtval 是输出参数 帮助函数返回其他信息
{
assert(ps != NULL);
if (IsEmpty(ps))
{
return false;
}
ps->top--;
*rtval = ps->base[ps->top];
return true;
}
//获取栈顶元素 与出栈不同的是 不用减top
bool Top(PStack ps, Element* rtval)
{
assert(ps != NULL);
if (IsEmpty(ps))
{
return false;
}
*rtval = ps->base[ps->top - 1];
return true;
}
//获取有效元素个数
int GetSize(PStack ps)
{
return ps->top;
}
//判空
bool IsEmpty(PStack ps)
{
return ps->top == 0;
}
//判满
bool IsFull(PStack ps)
{
assert(ps != NULL);
return ps->stackSize == ps->top;
}
//扩容函数
void Inc(PStack ps)
{
assert(ps != NULL && ps->base != NULL);
ps->base = (Element*)realloc(ps->base, sizeof(Element) * ps->stackSize * 2);
if (ps->base == NULL)
{
exit(1);
}
ps->stackSize = ps->stackSize * 3 / 2;
}
//清空
void Clear(PStack ps)
{
assert(ps != NULL);
ps->top = 0;
}
//销毁
void Destroy(PStack ps)
{
free(ps->base);
ps->base = NULL;
ps->stackSize = ps->top = 0;
}
void Show(PStack ps)
{
assert(ps != NULL);
for (int i = 0; i < ps->top; i++)
{
printf("%d ", ps->base[i]);
}
printf("\n");
}
二、链栈
1.链栈头文件及函数声明
我们可以新建头文件"lstack.h"对顺序表函数声明进行保存,方便我们后期查看及使用
#pragma once
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>
typedef int Element;
typedef struct LStack
{
Element data;//数据域
struct LStack* next;// 指针域
}LStack,*PLStack;
//初始化链栈
void Init_LStack(PLStack ps);
//购买新结点
PLStack BuyNode();
//释放结点
void FreeNode(PLStack p);
//入栈
bool Push(PLStack ps, Element val);
//出栈
bool Pop(PLStack ps, Element* rtval);
//获取栈顶元素
bool Top(PLStack ps, Element* rtval);
//获取有效数据个数
int GetSize(PLStack ps);
//判空
bool IsEmpty(PLStack ps);
//清空
void Clear(PLStack ps);
//销毁
void Destroy(PLStack ps);
//打印
void Show(PLStack ps);
2.初始化
此处链栈的基础结点与单链表基本相同,但只是相较单链表来说,其权限被限制了一部分。
typedef int Element;
typedef struct LStack
{
Element data;//数据域
struct LStack* next;// 指针域
}LStack,*PLStack;
接着我们实现一个初始化函数来对以上内容进行初始化:
//初始化链栈
void Init_LStack(PLStack ps)
{
assert(ps != NULL);
ps->next = NULL;
}
完成以上操作,我们就得到一个简单的空的链栈,接下来可以对其进行相关操作。
3.结点申请
因为链表的结构特性,我们需要对结点进行多次申请,为了方便进行其他函数操作,并且为了优化代码,我们将结点的申请封装于一个函数
//购买新结点
PLStack BuyNode()
{
PLStack node = (PLStack)calloc(1, sizeof(LStack));
if (node == NULL)
{
exit(1);
}
return node;
}
4.入栈
因为单链表的特性,要实现对栈顶的操作,即是直接对链表进行头部操作即可
对数据进行入栈,即是直接对栈链表进行头插操作。
//入栈
bool Push(PLStack ps, Element val)
{
assert(ps != NULL);
if (ps == NULL)
{
return false;
}
PLStack newnode = BuyNode();
newnode->data = val;
newnode->next = ps->next;
ps->next = newnode;
return true;
}
5.获取栈顶元素
这里与出栈基本相同,但是无需删除数据。
rtval是输出参数,帮助获取栈顶元素。
//获取栈顶元素
bool Top(PLStack ps, Element* rtval)
{
assert(ps != NULL);
if (IsEmpty(ps))
{
return false;
}
*rtval = ps->next->data;
return true;
}
6.出栈
rtval是输出参数,帮助获取出栈元素。
//出栈
bool Pop(PLStack ps, Element* rtval)
{
assert(ps != NULL);
if (IsEmpty(ps))
{
return false;
}
*rtval = ps->next->data;
PLStack delnode = ps->next;
ps->next = delnode->next;
FreeNode(delnode);
return true;
}
7.清空与销毁
1.清空
当栈中数据无需使用,我们可以对栈进行清空操作,此处使用不断出栈的操作;
//清空
void Clear(PLStack ps)
{
assert(ps != NULL);
int rtval;
while (!IsEmpty(ps))
{
Pop(ps, &rtval);
}
}
2.销毁
销毁相当于进行初始化相反的操作,将向计算机申请的内存释放清空,并将链表头置为NULL。
//销毁
void Destroy(PLStack* ps)
{
Clear(*ps);
*ps = NULL;
}
8.顺序栈源文件及整体函数实现
#include "lstack.h"
//初始化链栈
void Init_LStack(PLStack ps)
{
assert(ps != NULL);
ps->next = NULL;
}
//购买新结点
PLStack BuyNode()
{
PLStack node = (PLStack)calloc(1, sizeof(LStack));
if (node == NULL)
{
exit(1);
}
return node;
}
//释放结点
void FreeNode(PLStack p)
{
free(p);
p = NULL;
}
//入栈
bool Push(PLStack ps, Element val)
{
assert(ps != NULL);
if (ps == NULL)
{
return false;
}
PLStack newnode = BuyNode();
newnode->data = val;
newnode->next = ps->next;
ps->next = newnode;
return true;
}
//出栈
bool Pop(PLStack ps, Element* rtval)
{
assert(ps != NULL);
if (IsEmpty(ps))
{
return false;
}
*rtval = ps->next->data;
PLStack delnode = ps->next;
ps->next = delnode->next;
FreeNode(delnode);
return true;
}
//获取栈顶元素
bool Top(PLStack ps, Element* rtval)
{
assert(ps != NULL);
if (IsEmpty(ps))
{
return false;
}
*rtval = ps->next->data;
return true;
}
//获取有效数据个数
int GetSize(PLStack ps)
{
assert(ps != NULL);
int num = 0;
PLStack node = ps->next;
while (node != NULL)
{
num++;
node = node->next;
}
return num;
}
//判空
bool IsEmpty(PLStack ps)
{
assert(ps != NULL);
return ps->next == NULL;
}
//清空
void Clear(PLStack ps)
{
assert(ps != NULL);
int rtval;
while (!IsEmpty(ps))
{
Pop(ps, &rtval);
}
}
//销毁
void Destroy(PLStack ps)
{
Clear(ps);
ps = NULL;
}
//打印
void Show(PLStack ps)
{
assert(ps != NULL);
PLStack node = ps->next;
for (int i = 0; i < GetSize(ps); i++)
{
printf("%d ", node->data);
node = node->next;
}
printf("\n");
}
总结
以上就是今天要讲解的内容,本文简单介绍了栈的代码实现(包括链栈与顺序栈),栈为我们提供了数据结构的基础思想,能使我们快速便捷地处理数据的函数和方法。