链栈(操作堆空间)
1. 基本概念
栈是一种逻辑结构,是特殊的线性表。特殊在:
- 只能在固定的一端操作
只要满足上述条件,那么这种特殊的线性表就会呈现一种“后进先出”的逻辑,这种逻辑就被称为栈。栈在生活中到处可见,比如堆叠的盘子、电梯中的人们、嵌套函数的参数等等。
由于约定了只能在线性表固定的一端进行操作,于是给栈这种特殊的线性表的“插入”、“删除”,另起了下面这些特定的名称:
- 栈顶:可以进行插入删除的一端
- 栈底:栈顶的对端
- 入栈:将节点插入栈顶之上,也称为压栈,函数名通常为push()
- 出栈:将节点从栈顶剔除,也称为弹栈,函数名通常为pop()
- 取栈顶:取得栈顶元素,但不出栈,函数名通常为top()
基于这种固定一端操作的简单约定,栈获得了“后进先出”的基本特性,如下图所示,最后一个放入的元素,最先被拿出来:
2. 存储形式
栈只是一种线性数据逻辑,如何将数据存储于内存则是另一回事。一般而言,可以采用顺序存储形成顺序栈,或采用链式存储形成链式栈。
- 顺序栈
- 链式栈
链式栈的组织形式与链表无异,只不过插入删除被约束在固定的一端。为了便于操作,通常也会创建所谓管理结构体,用来存储栈顶指针、栈元素个数等信息:
3.链栈操作
3.1链栈初始化
// 链栈节点设计
typedef struct node
{
int data;
struct node *next;
} Node_t, *P_Node_t;
// 管理链栈结构体设计
typedef struct linkStack
{
struct node *top;
int size;
} linkStack;
3.2链队初始化
// @brief 管理链栈结构体初始化
/// @return 返回初始化完成的管理链栈结构体指针
linkStack *stackInit()
{
// 管理链栈结构体堆内存空间申请
linkStack *s = (linkStack *)calloc(1, sizeof(linkStack));
// 判断管理链栈结构体堆内存空间申请是否成功
if (s == NULL)
{
printf("管理链栈结构体堆内存空间申请失败.\n");
return NULL;
}
// 栈顶指针指向NULL
s->top = NULL;
// 链栈节点个数初始化为0
s->size = 0;
// 返回初始化完成的管理链栈结构体指针
return s;
}
/// @brief 链栈节点初始化
/// @param newData 新节点数据
/// @return 返回初始化完成的新节点
P_Node_t nodeInit(int newData)
{
// 新节点堆内存空间申请
P_Node_t newNode = (P_Node_t)calloc(1, sizeof(Node_t));
// 判断新节点堆内存空间是否申请成功
if (newNode == NULL)
{
printf("新节点堆内存空间申请失败.\n");
return NULL;
}
// 新节点数据初始化
newNode->data = newData;
// 新节点指向NULL
newNode->next = NULL;
// 返回初始化完成的新节点
return newNode;
}
3.3入栈(压栈)
/// @brief 压栈,即新节点入栈
/// @param s 管理链栈结构体
/// @param newNode 新节点
/// @return 压栈成功返回真,失败返回假
bool push(linkStack *s, P_Node_t newNode)
{
// 判断管理链栈结构体或者新节点是否为空
if (s == NULL || newNode == NULL)
{
printf("管理链栈结构体或者新节点为空.\n");
return false;
}
// 新节点指向栈顶
newNode->next = s->top;
// 栈顶指向新节点
s->top = newNode;
// 栈元素个数+1
s->size++;
// 入栈成功返回真
return true;
}
3.4出栈
/// @brief 出栈
/// @param s 管理链栈结构体指针
/// @param outData 出栈节点数据
/// @return 出栈成功返回真,失败返回假
bool pop(linkStack *s, int *outData)
{
// 判断链栈是否为空
if (s->size == 0)
{
printf("链栈为空.\n");
return false;
}
// tmp指向出栈节点
P_Node_t tmp = s->top;
//*outData取得出栈节点数据
*outData = tmp->data;
// 栈顶指向下一个元素
s->top = s->top->next;
// 栈元素个数-1
s->size--;
// 出栈成功返回真
return true;
}
3.5遍历链栈
/// @brief 遍历栈元素
/// @param s 管理栈结构体
void display(linkStack *s)
{
// 判断栈是否为空
if (s->size == 0)
{
printf("链栈为空.\n");
return;
}
printf("当前链栈:\n");
// tmp遍历栈
for (P_Node_t tmp = s->top; tmp != NULL; tmp = tmp->next)
{
printf("%d\n", tmp->data);
}
return;
}
3.6 销毁链栈
// 释放链队节点堆内存空间
while (q->size != 0)
{
outQueue(q, &outData);
}
// 释放管理链队结构体
free(q);
3.7代码示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
// 链栈节点设计
typedef struct node
{
int data;
struct node *next;
} Node_t, *P_Node_t;
// 管理链栈结构体设计
typedef struct linkStack
{
struct node *top;
int size;
} linkStack;
/// @brief 管理链栈结构体初始化
/// @return 返回初始化完成的管理链栈结构体指针
linkStack *stackInit()
{
// 管理链栈结构体堆内存空间申请
linkStack *s = (linkStack *)calloc(1, sizeof(linkStack));
// 判断管理链栈结构体堆内存空间申请是否成功
if (s == NULL)
{
printf("管理链栈结构体堆内存空间申请失败.\n");
return NULL;
}
// 栈顶指针指向NULL
s->top = NULL;
// 链栈节点个数初始化为0
s->size = 0;
// 返回初始化完成的管理链栈结构体指针
return s;
}
/// @brief 链栈节点初始化
/// @param newData 新节点数据
/// @return 返回初始化完成的新节点
P_Node_t nodeInit(int newData)
{
// 新节点堆内存空间申请
P_Node_t newNode = (P_Node_t)calloc(1, sizeof(Node_t));
// 判断新节点堆内存空间是否申请成功
if (newNode == NULL)
{
printf("新节点堆内存空间申请失败.\n");
return NULL;
}
// 新节点数据初始化
newNode->data = newData;
// 新节点指向NULL
newNode->next = NULL;
// 返回初始化完成的新节点
return newNode;
}
/// @brief 压栈,即新节点入栈
/// @param s 管理链栈结构体
/// @param newNode 新节点
/// @return 压栈成功返回真,失败返回假
bool push(linkStack *s, P_Node_t newNode)
{
// 判断管理链栈结构体或者新节点是否为空
if (s == NULL || newNode == NULL)
{
printf("管理链栈结构体或者新节点为空.\n");
return false;
}
// 新节点指向栈顶
newNode->next = s->top;
// 栈顶指向新节点
s->top = newNode;
// 栈元素个数+1
s->size++;
// 入栈成功返回真
return true;
}
/// @brief 出栈
/// @param s 管理链栈结构体指针
/// @param outData 出栈节点数据
/// @return 出栈成功返回真,失败返回假
bool pop(linkStack *s, int *outData)
{
// 判断链栈是否为空
if (s->size == 0)
{
printf("链栈为空.\n");
return false;
}
// tmp指向出栈节点
P_Node_t tmp = s->top;
//*outData取得出栈节点数据
*outData = tmp->data;
// 栈顶指向下一个元素
s->top = s->top->next;
// 栈元素个数-1
s->size--;
// 出栈成功返回真
return true;
}
/// @brief 遍历栈元素
/// @param s 管理栈结构体
void display(linkStack *s)
{
// 判断栈是否为空
if (s->size == 0)
{
printf("链栈为空.\n");
return;
}
printf("当前链栈:\n");
// tmp遍历栈
for (P_Node_t tmp = s->top; tmp != NULL; tmp = tmp->next)
{
printf("%d\n", tmp->data);
}
return;
}
int main(int argc, char const *argv[])
{
// 初始化管理链栈结构体
linkStack *s = stackInit();
char optMenu;
P_Node_t newNode = NULL;
int data, outData = 0, overFlag = 0;
while (1)
{
printf("输入i入栈\t输入o出栈\t输入d打印当前链栈\tt.退出\n");
scanf("%c", &optMenu);
switch (optMenu)
{
case 'i':
printf("请输入数据入栈:\n");
scanf("%d", &data);
newNode = nodeInit(data);
if (push(s, newNode))
{
printf("入栈成功.\n");
}
else
{
printf("入栈失败.\n");
}
break;
case 'o':
if (pop(s, &outData))
{
printf("出栈成功,出栈的数据为:%d\n", outData);
}
else
{
printf("出栈失败.\n");
}
break;
case 'd':
display(s);
break;
case 't':
overFlag = 1;
break;
default:
printf("输入错误,请重新输入.\n");
break;
}
if (overFlag)
{
break;
}
while (getchar() != '\n')
;
}
// 释放栈元素
while (s->size != 0)
{
// 出栈
pop(s, &outData);
}
// 释放链栈
free(s);
return 0;
}
4链栈示例:十进制转八进制、十六进制
/**
* 写一个十进制转8进制,10进制转16进制代码(链栈)
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
typedef struct node
{
char data;
struct node *next;
} Node_t, *P_Node_t;
typedef struct linkStack
{
int size;
struct node *top;
} linkStack;
linkStack *stackInit()
{
linkStack *s = (linkStack *)calloc(1, sizeof(linkStack));
if (s == NULL)
{
printf("管理链栈结构体堆内存空间申请失败.\n");
return NULL;
}
s->size = 0;
s->top = NULL;
return s;
}
P_Node_t nodeInit(char newData)
{
P_Node_t newNode = (P_Node_t)calloc(1, sizeof(Node_t));
if (newNode == NULL)
{
printf("新节点堆内存空间申请失败.\n");
return NULL;
}
newNode->data = newData;
newNode->next = NULL;
return newNode;
}
bool push(linkStack *s, P_Node_t newNode)
{
if (s == NULL || newNode == NULL)
{
printf("管理链栈结构体或者新节点地址为空.\n");
return false;
}
newNode->next = s->top;
s->top = newNode;
s->size++;
return true;
}
bool pop(linkStack *s)
{
if (s->size == 0)
{
printf("链栈为空.\n");
return false;
}
P_Node_t tmp = s->top;
s->top = s->top->next;
s->size--;
free(tmp);
return true;
}
void display(linkStack *s)
{
if (s->size == 0)
{
printf("链栈为空.\n");
return;
}
for (P_Node_t tmp = s->top; tmp != NULL; tmp = tmp->next)
{
printf("%c", tmp->data);
}
return;
}
int main(int argc, char const *argv[])
{
linkStack *s = stackInit();
char menuOpt;
int overFlag = 0;
long long num;
printf("请选择进制转换器:\n");
while (1)
{
printf("a.10进制转8进制\tb.10进制转16进制代码.\tt.退出\n");
scanf("%c", &menuOpt);
switch (menuOpt)
{
case 'a':
printf("请输入要转换的数字.\n");
while (scanf("%lld", &num) != 1 || num == -1)
{
printf("输入错误,请重新输入.\n");
while (getchar() != '\n')
;
}
while (num != 0)
{
push(s, nodeInit("01234567"[num % 8]));
num /= 8;
}
printf("转换后的八进制结果为:\n0");
display(s);
printf("\n");
while (s->size != 0)
{
pop(s);
}
break;
case 'b':
printf("请输入要转换的数字.\n");
while (scanf("%lld", &num) != 1 || num == -1)
{
printf("输入错误,请重新输入.\n");
while (getchar() != '\n')
;
}
while (num != 0)
{
push(s, nodeInit("0123456789ABCDEF"[num % 16]));
num /= 16;
}
printf("转换后的十六进制结果为:\n0x");
display(s);
printf("\n");
while (s->size != 0)
{
pop(s);
}
break;
case 't':
overFlag == 1;
break;
default:
printf("输入错误,请重新输入.\n");
break;
}
if (overFlag)
{
free(s);
break;
}
while (getchar() != '\n')
;
}
return 0;
}
5.结语
在这篇博客中,我们深入探讨了链栈的基本概念、实现方式以及其在实际应用中的优势。链栈作为一种灵活且高效的数据结构,充分利用了指针的特性,使得栈的操作能够在动态内存中进行,避免了固定大小数组的局限性。通过实现基本操作如入栈、出栈和栈顶元素访问,我们不仅巩固了对链表和栈的理解,也为在更复杂的数据结构和算法中的应用奠定了基础。
掌握链栈的原理和实现将为我们在进行深度优先搜索、解析表达式等任务时提供重要的支持。希望读者在使用链栈的过程中能够灵活应对各种挑战,提升编程能力。
感谢您的阅读,如有任何问题或建议,欢迎在评论区留言讨论!