目录
一、链栈结点定义
//结构体:定义栈的每一个结点
typedef struct Node
{
//数据域
int data;
//指针域
Node* next;
//重命名,将struct Node 重命名为Node
}Node;
二、链栈定义
//结构体:定义链栈
typedef struct Stack
{
//链栈里只需要存放头结点就可以
Node* head;
//重命名,将struct Stack 重命名为Stack
}Stack;
三、栈的初始化
//函数:栈的初始化
//定义了结构体,我们首先需要一个函数来初始化链栈
//主要用途就是分配空间
//返回值自然是一个栈指针Stack*,以便我们能找到这个初始化的栈
//传参为空,因为不需要任何参数,我们直接在函数里初始化
Stack* initialStack()
{
//为栈分配空间
Stack* s = (Stack*)malloc(sizeof(Stack));
//初始化头结点,将头结点指向空
Node* ret = (Node*)malloc(sizeof(Node));
ret->next = NULL;
s->head = ret;
//返回栈指针
return s;
}
四、栈的插入操作——push
//函数:栈的插入操作
//很明显需要用头插法
//因为头插法,头结点指向的始终是栈顶元素,
//找到栈顶元素的时间复杂度为O(1),与栈的大小无关
//如果用尾插法,就麻烦了,找到栈顶元素的时间复杂度为O(N)
//或者使用双向链表,但都很麻烦
//返回值:void,因为我们是对栈进行操作,不需要返回值
//参数:传入要操作的栈的指针即可
void push(Stack* s)
{
//首先创建一个结点指针,并分配内存空间
Node* cur = (Node*)malloc(sizeof(Node));
//为其赋值
int value;
scanf("%d", &value);
cur->data = value;
//有了值还不够,因为指针域还未初始化
//所以需要进行指针域的初始化
//先让这个指针的next指向头结点的next
cur->next = s->head->next;
//再让头结点的next指向他
s->head->next = cur;
//这样就完成了
}
五、栈的判空
//函数:栈的判空
//判断一个栈是否为空,返回类型需要是一个布尔类型
//为空:true 非空:false
//传入的参数需要是一个具体的栈,且必须是指针,因为是链栈
bool isStackEmpty(Stack* s)
{
//如果头结点的下一个结点,也就是栈顶元素指针,是NULL
//说明此栈为空
if (s->head->next == nullptr)
{
return true;
}
//如果栈顶元素指针非空,说明栈中至少存在一个元素
//说明非空
else
{
return false;
}
}
六、找到栈顶元素
//函数:找到栈顶元素
//因为我们要找到栈顶元素
//所以返回值肯定是一个结点指针,也就是Node*类型
//传入的参数需要是一个具体的栈,且必须是指针,因为是链栈
Node* findTop(Stack* s)
{
//首先进行判空
//如果栈为空
if (isStackEmpty(s))
{
//说明栈中没有任何一个元素,
//自然也不存在栈顶元素,
//直接返回NULL
return nullptr;
}
//经过上面的步骤,肯定说明栈不为空
//所以要开始找栈顶元素了
//创造一个栈顶指针,并令其为栈顶元素
Node* top = s->head->next;
//返回栈顶元素指针即可
return top;
}
七、栈的删除操作——pop
//函数:删除栈顶元素
//有了上述封装好的函数,我们就可以直接用了
void pop(Stack* s)
{
//首先进行安全性检查:判空
//如果栈为空,直接返回
if (isStackEmpty(s))
{
return;
}
//找到栈顶元素
Node* top = findTop(s);
//删除栈顶元素
s->head->next = top->next;
//释放栈顶元素
free(top);
top = nullptr;
}
八、栈的销毁
//函数:销毁栈
//有始有终,防止内存泄露
void deleteStack(Stack* s)
{
//不断进行pop操作,直到栈为空
while (!isStackEmpty(s))
{
pop(s);
}
//此时,除了头结点,所有结点已经释放干净
//接下来释放头结点
free(s->head);
s->head = nullptr;
//最后释放栈指针空间,并避免悬空指针
free(s);
s = nullptr;
}
九、测试
//主函数里进行操作
int main()
{
//创建一个栈
printf("执行创建栈操作\n");
Stack* myStack = initialStack();
//检测判空操作
printf("执行判空操作\n");
if (isStackEmpty(myStack))
{
printf("栈为空!\n");
}
else
{
printf("栈非空!\n");
}
//检测push操作
printf("执行push操作:\n");
push(myStack);
printf("执行push操作:\n");
push(myStack);
printf("执行push操作:\n");
push(myStack);
//再次检测判空操作
printf("执行判空操作\n");
if (isStackEmpty(myStack))
{
printf("栈为空!\n");
}
else
{
printf("栈非空!\n");
}
//检测top操作
Node* top = findTop(myStack);
printf("执行top操作:\n");
printf("栈顶元素为:");
printf("%d\n",top->data);
//检测pop操作
printf("执行pop操作\n");
pop(myStack);
//检测top操作
top = findTop(myStack);
printf("执行top操作:\n");
printf("栈顶元素为:\n");
printf("%d", top->data);
//检测deleteStack操作
printf("执行销毁操作:\n");
deleteStack(myStack);
if (myStack == nullptr)
{
printf("栈已释放完毕!\n");
}
else
{
printf("栈未释放完毕!\n");
}
return 0;
}
十、源码
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <iostream>
//结构体:定义栈的每一个结点
typedef struct Node
{
//数据域
int data;
//指针域
Node* next;
//重命名,将struct Node 重命名为Node
}Node;
//结构体:定义链栈
typedef struct Stack
{
//链栈里只需要存放头结点就可以
Node* head;
//重命名,将struct Stack 重命名为Stack
}Stack;
//函数:栈的初始化
//定义了结构体,我们首先需要一个函数来初始化链栈
//主要用途就是分配空间
//返回值自然是一个栈指针Stack*,以便我们能找到这个初始化的栈
//传参为空,因为不需要任何参数,我们直接在函数里初始化
Stack* initialStack()
{
//为栈分配空间
Stack* s = (Stack*)malloc(sizeof(Stack));
//初始化头结点,将头结点指向空
Node* ret = (Node*)malloc(sizeof(Node));
ret->next = NULL;
s->head = ret;
//返回栈指针
return s;
}
//函数:栈的插入操作
//很明显需要用头插法
//因为头插法,头结点指向的始终是栈顶元素,
//找到栈顶元素的时间复杂度为O(1),与栈的大小无关
//如果用尾插法,就麻烦了,找到栈顶元素的时间复杂度为O(N)
//或者使用双向链表,但都很麻烦
//返回值:void,因为我们是对栈进行操作,不需要返回值
//参数:传入要操作的栈的指针即可
void push(Stack* s)
{
//首先创建一个结点指针,并分配内存空间
Node* cur = (Node*)malloc(sizeof(Node));
//为其赋值
int value;
scanf("%d", &value);
cur->data = value;
//有了值还不够,因为指针域还未初始化
//所以需要进行指针域的初始化
//先让这个指针的next指向头结点的next
cur->next = s->head->next;
//再让头结点的next指向他
s->head->next = cur;
//这样就完成了
}
//函数:栈的判空
//判断一个栈是否为空,返回类型需要是一个布尔类型
//为空:true 非空:false
//传入的参数需要是一个具体的栈,且必须是指针,因为是链栈
bool isStackEmpty(Stack* s)
{
//如果头结点的下一个结点,也就是栈顶元素指针,是NULL
//说明此栈为空
if (s->head->next == nullptr)
{
return true;
}
//如果栈顶元素指针非空,说明栈中至少存在一个元素
//说明非空
else
{
return false;
}
}
//函数:找到栈顶元素
//因为我们要找到栈顶元素
//所以返回值肯定是一个结点指针,也就是Node*类型
//传入的参数需要是一个具体的栈,且必须是指针,因为是链栈
Node* findTop(Stack* s)
{
//首先进行判空
//如果栈为空
if (isStackEmpty(s))
{
//说明栈中没有任何一个元素,
//自然也不存在栈顶元素,
//直接返回NULL
return nullptr;
}
//经过上面的步骤,肯定说明栈不为空
//所以要开始找栈顶元素了
//创造一个栈顶指针,并令其为栈顶元素
Node* top = s->head->next;
//返回栈顶元素指针即可
return top;
}
//函数:删除栈顶元素
//有了上述封装好的函数,我们就可以直接用了
void pop(Stack* s)
{
//首先进行安全性检查:判空
//如果栈为空,直接返回
if (isStackEmpty(s))
{
return;
}
//找到栈顶元素
Node* top = findTop(s);
//删除栈顶元素
s->head->next = top->next;
//释放栈顶元素
free(top);
top = nullptr;
}
//函数:销毁栈
//有始有终,防止内存泄露
void deleteStack(Stack* s)
{
//不断进行pop操作,直到栈为空
while (!isStackEmpty(s))
{
pop(s);
}
//此时,除了头结点,所有结点已经释放干净
//接下来释放头结点
free(s->head);
s->head = nullptr;
//最后释放栈指针空间,并避免悬空指针
free(s);
s = nullptr;
}
//主函数里进行操作
int main()
{
//创建一个栈
printf("执行创建栈操作\n");
Stack* myStack = initialStack();
//检测判空操作
printf("执行判空操作\n");
if (isStackEmpty(myStack))
{
printf("栈为空!\n");
}
else
{
printf("栈非空!\n");
}
//检测push操作
printf("执行push操作:\n");
push(myStack);
printf("执行push操作:\n");
push(myStack);
printf("执行push操作:\n");
push(myStack);
//再次检测判空操作
printf("执行判空操作\n");
if (isStackEmpty(myStack))
{
printf("栈为空!\n");
}
else
{
printf("栈非空!\n");
}
//检测top操作
Node* top = findTop(myStack);
printf("执行top操作:\n");
printf("栈顶元素为:");
printf("%d\n",top->data);
//检测pop操作
printf("执行pop操作\n");
pop(myStack);
//检测top操作
top = findTop(myStack);
printf("执行top操作:\n");
printf("栈顶元素为:\n");
printf("%d", top->data);
//检测deleteStack操作
printf("执行销毁操作:\n");
deleteStack(myStack);
if (myStack == nullptr)
{
printf("栈已释放完毕!\n");
}
else
{
printf("栈未释放完毕!\n");
}
return 0;
}