目录
[1. 栈](#1. 栈)
[1.1 栈的概念](#1.1 栈的概念)
[2. 栈的实现](#2. 栈的实现)
[3. 顺序栈的实现](#3. 顺序栈的实现)
[3.1 顺序栈的声明](#3.1 顺序栈的声明)
[3.2 顺序栈的初始化](#3.2 顺序栈的初始化)
[3.3 顺序栈的入栈](#3.3 顺序栈的入栈)
[3.4 顺序栈的出栈](#3.4 顺序栈的出栈)
[3.5 顺序栈获取栈顶元素](#3.5 顺序栈获取栈顶元素)
[3.6 顺序栈获取栈内有效数据个数](#3.6 顺序栈获取栈内有效数据个数)
[3.7 顺序栈判断栈是否为空](#3.7 顺序栈判断栈是否为空)
[3.8 顺序栈打印栈内元素](#3.8 顺序栈打印栈内元素)
[3.9 顺序栈销毁栈](#3.9 顺序栈销毁栈)
[3.10 完整代码](#3.10 完整代码)
[4. 链式栈的实现](#4. 链式栈的实现)
[4.1 链式栈的声明](#4.1 链式栈的声明)
[4.2 链式栈的初始化](#4.2 链式栈的初始化)
[4.3 链式栈的入栈](#4.3 链式栈的入栈)
[4.4 链式栈的出栈](#4.4 链式栈的出栈)
[4.5 链式栈获取栈顶元素](#4.5 链式栈获取栈顶元素)
[4.6 链式栈获取栈内有效数据个数](#4.6 链式栈获取栈内有效数据个数)
[4.7 链式栈判断栈是否为空](#4.7 链式栈判断栈是否为空)
[4.8 链式栈打印栈内元素](#4.8 链式栈打印栈内元素)
[4.9 链式栈销毁栈](#4.9 链式栈销毁栈)
[5. 疑问](#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是指向栈顶元素还是栈顶元素的下一个位置。
所以,不管再简单,数据结构里面不要直接访问数据,建议调用它的接口方法。