目录
1. 栈
1.1 栈的概念
2. 栈的实现
3. 顺序栈的实现
3.1 顺序栈的声明
3.2 顺序栈的初始化
3.3 顺序栈的入栈
3.4 顺序栈的出栈
3.5 顺序栈获取栈顶元素
3.6 顺序栈获取栈内有效数据个数
3.7 顺序栈判断栈是否为空
3.8 顺序栈打印栈内元素
3.9 顺序栈销毁栈
3.10 完整代码
4. 链式栈的实现
4.1 链式栈的声明
4.2 链式栈的初始化
4.3 链式栈的入栈
4.4 链式栈的出栈
4.5 链式栈获取栈顶元素
4.6 链式栈获取栈内有效数据个数
4.7 链式栈判断栈是否为空
4.8 链式栈打印栈内元素
4.9 链式栈销毁栈
5. 疑问
1. 栈
1.1 栈的概念
栈:是一种特殊的线性表,其只允许在固定的一端进行插入和删除元素的操作。进行数据插入和删除操作的一段称为栈顶,另一端称为栈底。栈中的元素遵守后进先出LIFO(Last In First Out)的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。
2. 栈的实现
我们了解了栈的概念,我们就可以发现,栈其实可以用顺序表和链表来实现,我们根据它的实现方式,可以把它分为顺序栈与链式栈。
3. 顺序栈的实现
Stack.h
//需要用到的库函数的头文件
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int STDataType;
typedef struct Stack
{
STDataType* arr;//指向动态开辟的一块空间
int top;//指向栈顶元素的下一个位置
int capacity;//栈的容量
}st;
//初始化栈
void STInit(st* pst);
//入栈
void STPush(st* pst,STDataType x);
//出栈
void STPop(st* pst);
//获取栈顶元素
STDataType STTop(st* pst);
//获取栈内有效数据个数
int STSize(st* pst);
//判断栈是否为空
bool STEmpty(st* pst);
//打印栈内元素
void STPrint(st* pst);
//销毁栈
void STDestroy(st* pst);
3.1 顺序栈的声明
Stack.h
typedef int STDataType;
typedef struct Stack
{
STDataType* arr;//指向动态开辟的一块空间
int top;//指向栈顶元素的下一个位置
int capacity;//栈的容量
}st;
这里我们在声明顺序栈的结构的时候,这个top可以是指向栈顶元素的,那我们在进行栈的初始化就需要将他初始化为-1,如果top是指向栈顶元素的下一个,那我们就需要将他的初始化设置为0。两种方法均可行,这里可以根据自己的习惯进行设置。
3.2 顺序栈的初始化
Stack.c
void STInit(st* pst)
{
//判断pst是否为空指针
assert(pst);
pst->arr = NULL;
pst->capacity = pst->top = 0;
}
3.3 顺序栈的入栈
Stack.c
void STPush(st* pst, STDataType x)
{
//判断pst是否为空指针
assert(pst);
//检查空间大小,是否需要增容
if (pst->capacity == pst->top)
{
int newcapacity = pst->capacity == 0 ? 4 :2 * pst->capacity;
STDataType* tmp = (STDataType*)realloc(pst->arr,newcapacity * sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc");
exit(1);
}
pst->arr = tmp;
pst->capacity = newcapacity;
}
//入数据
pst->arr[pst->top++] = x;
}
3.4 顺序栈的出栈
Stack.c
void STPop(st* pst)
{
//判断pst是否为空指针
assert(pst);
//判断栈是否为空
assert(pst->top > 0);
pst->top--;
}
3.5 顺序栈获取栈顶元素
Stack.c
STDataType STTop(st* pst)
{
//判断pst是否为空指针
assert(pst);
//判断栈是否为空
assert(pst->top > 0);
//这里top指向栈顶元素的下一个位置,-1刚好指向栈顶元素
return pst->arr[pst->top - 1];
}
3.6 顺序栈获取栈内有效数据个数
Stack.c
int STSize(st* pst)
{
assert(pst);
return pst->top;
}
3.7 顺序栈判断栈是否为空
Stack.c
bool STEmpty(st* pst)
{
assert(pst);
return pst->top == 0;
}
3.8 顺序栈打印栈内元素
Stack.c
void STPrint(st* pst)
{
assert(pst);
while(!STEmpty(pst))
{
printf("%d\n", STTop(pst));
STPop(pst);
}
}
3.9 顺序栈销毁栈
Stack.c
void STDestroy(st* pst)
{
assert(pst);
free(pst->arr);
pst->arr = NULL;
pst->top = pst->capacity = 0;
}
3.10 完整代码
Stack.h
//需要用到的库函数的头文件
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int STDataType;
typedef struct Stack
{
STDataType* arr;//指向动态开辟的一块空间
int top;//指向栈顶元素的下一个位置
int capacity;//栈的容量
}st;
//初始化栈
void STInit(st* pst);
//入栈
void STPush(st* pst,STDataType x);
//出栈
void STPop(st* pst);
//获取栈顶元素
STDataType STTop(st* pst);
//获取栈内有效数据个数
int STSize(st* pst);
//判断栈是否为空
bool STEmpty(st* pst);
//打印栈内元素
void STPrint(st* pst);
//销毁栈
void STDestroy(st* pst);
Stack.c
#include"Stack.h"
void STInit(st* pst)
{
//判断pst是否为空指针
assert(pst);
pst->arr = NULL;
pst->capacity = pst->top = 0;
}
void STPush(st* pst, STDataType x)
{
//判断pst是否为空指针
assert(pst);
//检查空间大小,是否需要增容
if (pst->capacity == pst->top)
{
int newcapacity = pst->capacity == 0 ? 4 :2 * pst->capacity;
STDataType* tmp = (STDataType*)realloc(pst->arr,newcapacity * sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc");
exit(1);
}
pst->arr = tmp;
pst->capacity = newcapacity;
}
//入数据
pst->arr[pst->top++] = x;
}
void STPop(st* pst)
{
//判断pst是否为空指针
assert(pst);
//判断栈是否为空
assert(pst->top > 0);
pst->top--;
}
STDataType STTop(st* pst)
{
//判断pst是否为空指针
assert(pst);
//判断栈是否为空
assert(pst->top > 0);
//这里top指向栈顶元素的下一个位置,-1刚好指向栈顶元素
return pst->arr[pst->top - 1];
}
int STSize(st* pst)
{
assert(pst);
return pst->top;
}
bool STEmpty(st* pst)
{
assert(pst);
return pst->top == 0;
}
void STPrint(st* pst)
{
assert(pst);
while(!STEmpty(pst))
{
printf("%d\n", STTop(pst));
STPop(pst);
}
}
void STDestroy(st* pst)
{
assert(pst);
free(pst->arr);
pst->arr = NULL;
pst->top = pst->capacity = 0;
}
4. 链式栈的实现
Stack.h
//需要用到的库函数的头文件
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int STDataType;
typedef struct StackNode //定义节点结构
{
STDataType data;
struct StackNode* next;
}stnode;
typedef struct Stack //定义栈结构
{
//记录栈顶
stnode* top;
//记录栈内有效数据个数
int size;
}st;
//初始化栈
void STInit(st* pst);
//入栈
void STPush(st* pst, STDataType x);
//出栈
void STPop(st* pst);
//获取栈顶元素
STDataType STTop(st* pst);
//获取栈内有效数据个数
int STSize(st* pst);
//判断栈是否为空
bool STEmpty(st* pst);
//打印栈内元素
void STPrint(st* pst);
//销毁栈
void STDestroy(st* pst);
4.1 链式栈的声明
Stack.h
typedef int STDataType;
typedef struct StackNode //定义节点结构
{
STDataType data;
struct StackNode* next;
}stnode;
typedef struct Stack //定义栈结构
{
//记录栈顶
stnode* top;
//记录栈内有效数据个数
int size;
}st;
这里如果我们用单链表的形式实现栈的话,单链表头插的时间复杂度为O(1),尾插的时间复杂度为O(N),所以这里我们需要把单链表的第一个有效节点设置为栈顶,如果想把链表的尾设置尾栈顶的话,那我们这里可能就需要使用双向链表来实现了,本章主要使用单链表来实现栈。
我们在声明链式栈的时候,我们这里使用了两个结构体,其中一个结构体声明链式栈里的数据节点,另一个结构体声明栈的结构,其中里面有个指向栈顶指针,还有一个记录栈内有效数据个数的size,这样我们在获取栈内有效数据个数的时候不需要遍历链表,时间复杂度从O(N)变成O(1)
4.2 链式栈的初始化
Stack.c
void STInit(st* pst)
{
assert(pst);
pst->top = NULL;
pst->size = 0;
}
4.3 链式栈的入栈
Stack.c
void STPush(st* pst, STDataType x)
{
assert(pst);
stnode* newnode = (stnode*)malloc(sizeof(stnode));
if (newnode == NULL)
{
perror("malloc");
exit(1);
}
newnode->data = x;
newnode->next = NULL;
if (pst->top== NULL)
{
pst->top = newnode;
}
else
{
newnode->next = pst->top;
pst->top = newnode;
}
pst->size++;
}
4.4 链式栈的出栈
Stack.c
void STPop(st* pst)
{
assert(pst);
assert(pst->size > 0);
stnode* next = pst->top->next;
free(pst->top);
pst->top = next;
pst->size--;
}
4.5 链式栈获取栈顶元素
Stack.c
STDataType STTop(st* pst)
{
assert(pst);
assert(pst->size > 0);
return pst->top->data;
}
4.6 链式栈获取栈内有效数据个数
Stack.c
int STSize(st* pst)
{
assert(pst);
return pst->size;
}
4.7 链式栈判断栈是否为空
Stack.c
bool STEmpty(st* pst)
{
assert(pst);
return pst->size == 0;
}
4.8 链式栈打印栈内元素
Stack.c
void STPrint(st* pst)
{
assert(pst);
while (!STEmpty(pst))
{
printf("%d\n",STTop(pst));
STPop(pst);
}
}
4.9 链式栈销毁栈
Stack.c
void STDestroy(st* pst)
{
assert(pst);
stnode* pcur = pst->top;
while (pcur)
{
stnode* next = pcur->next;
free(pcur);
pcur = next;
}
pst->top = NULL;
pst->size = 0;
}
4.10 完整代码
Stack.h
//需要用到的库函数的头文件
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int STDataType;
typedef struct StackNode //定义节点结构
{
STDataType data;
struct StackNode* next;
}stnode;
typedef struct Stack //定义栈结构
{
//记录栈顶
stnode* top;
//记录栈内有效数据个数
int size;
}st;
//初始化栈
void STInit(st* pst);
//入栈
void STPush(st* pst, STDataType x);
//出栈
void STPop(st* pst);
//获取栈顶元素
STDataType STTop(st* pst);
//获取栈内有效数据个数
int STSize(st* pst);
//判断栈是否为空
bool STEmpty(st* pst);
//打印栈内元素
void STPrint(st* pst);
//销毁栈
void STDestroy(st* pst);
Stack.c
void STInit(st* pst)
{
assert(pst);
pst->top = NULL;
pst->size = 0;
}
void STPush(st* pst, STDataType x)
{
assert(pst);
stnode* newnode = (stnode*)malloc(sizeof(stnode));
if (newnode == NULL)
{
perror("malloc");
exit(1);
}
newnode->data = x;
newnode->next = NULL;
if (pst->top== NULL)
{
pst->top = newnode;
}
else
{
newnode->next = pst->top;
pst->top = newnode;
}
pst->size++;
}
void STPop(st* pst)
{
assert(pst);
assert(pst->size > 0);
stnode* next = pst->top->next;
free(pst->top);
pst->top = next;
pst->size--;
}
STDataType STTop(st* pst)
{
assert(pst);
assert(pst->size > 0);
return pst->top->data;
}
int STSize(st* pst)
{
assert(pst);
return pst->size;
}
bool STEmpty(st* pst)
{
assert(pst);
return pst->size == 0;
}
void STPrint(st* pst)
{
assert(pst);
while (!STEmpty(pst))
{
printf("%d\n",STTop(pst));
STPop(pst);
}
}
void STDestroy(st* pst)
{
assert(pst);
stnode* pcur = pst->top;
while (pcur)
{
stnode* next = pcur->next;
free(pcur);
pcur = next;
}
pst->top = NULL;
pst->size = 0;
}
5. 疑问
我们发现,我们很多的接口函数只有几行代码,有必要单独写成一个函数吗?比如说出栈,我们直接通过访问结构体进行删除。
其实有必要的,为了规范一定的写法,保持一致性,我们可以通过接口来调用它的函数,不建议自行访问,因为我们不知道设计者在设计top的时候,top是指向栈顶元素还是栈顶元素的下一个位置。
所以,不管再简单,数据结构里面不要直接访问数据,建议调用它的接口方法。