链式栈是栈的一种实现方式,它利用链表的特性来动态管理栈中的元素。与基于数组实现的栈相比,链式栈具有不同的内部结构和操作机制。
一.链式栈的原理及优缺点
原理
链式栈通常由一系列节点构成,每个节点包含两部分:
- 数据域:用于存储实际的数据。
- 指针域:用于指向栈中的下一个节点。
栈顶(Top)是一个指向链表中最后一个被添加节点的指针。在链式栈中,所有的插入和删除操作都发生在栈顶位置,即链表的头部。这意味着每次入栈操作都会创建一个新的节点,并将其链接到当前栈顶节点之前,然后更新栈顶指针;而出栈操作则会移除栈顶节点,并将栈顶指针指向下一个节点。
优点
- 动态扩展:链式栈可以根据需要动态地增加或减少节点,不需要预先确定栈的大小,避免了数组栈可能出现的空间浪费问题。
- 空间效率:链式栈可以更高效地利用内存,因为节点是在需要时才创建的,而不会像数组那样预先分配固定大小的空间。
- 快速操作:链式栈的入栈和出栈操作时间复杂度为O(1),因为这些操作只涉及到修改指针,而不需要移动大量数据。
缺点
- 额外的开销:链式栈中的每个节点都需要额外的指针空间来存储指向下一个节点的引用,这可能会导致空间上的额外开销。
- 访问限制:链式栈不支持随机访问,因为要访问链表中的某个特定节点,必须从栈顶开始遍历整个链表,这在最坏情况下可能需要O(n)的时间复杂度。
- 性能问题:虽然基本的入栈和出栈操作快,但如果栈的深度非常大,那么大量的指针跳转可能会稍微影响性能。
总的来说,链式栈非常适合于不知道数据量大小、需要频繁进行入栈和出栈操作的场景,但不太适合需要频繁访问栈内任意位置元素的应用。
二.链式栈的相关函数
2.1定义链式栈节点
typedef int data_t;
typedef struct node {
data_t data;
struct node *next;
} linknode,* linkstack;
2.2 创建链式栈
/**
* 创建一个链栈
*
* 链栈是一种特殊的线性表,其插入和删除操作都在表的同一端进行,这个端点称为栈顶。
* 本函数用于动态地创建一个链栈。链栈由链表实现,每个节点包含一个数据域和一个指针域。
*
* @return 创建的链栈的头指针。如果创建失败,返回NULL。
*/
linkstack stack_create(){
// 分配内存空间用于创建链栈的头节点
linkstack s;
s = (linkstack)malloc(sizeof(linknode));
// 检查内存分配是否成功
if(s == NULL){
printf("malloc error\n");
return NULL;
}
// 初始化链栈,设置头节点的下一个节点为NULL,数据域为0
s->next = NULL;
s->data = 0;
return s;
}
2.3 入栈
/**
* 向链栈中压入一个新元素
* @param s 链栈的头节点指针
* @param val 要压入的新元素的值
* @return 如果操作成功,返回0;如果链栈为空或内存分配失败,返回-1。
*/
int stack_push(linkstack s, data_t val)
{
linkstack P; /* 新建一个节点指针P,用于存放新压入的元素 */
/* 检查链栈是否为空,如果为空则打印错误信息并返回-1 */
if(s == NULL){
printf("s is NULL\n");
return -1;
}
/* 为新节点分配内存,如果内存分配失败则打印错误信息并返回-1 */
P = (linknode *)malloc(sizeof(linknode));
if(P == NULL){
printf("malloc error\n");
return -1;
}
/* 设置新节点的数据值和下一个节点的指针 */
P->data = val;
P->next = s->next;
/* 将新节点链接到链栈的头部 */
s->next = P;
/* 操作成功,返回0 */
return 0;
}
2.4 出栈
/**
* 从链栈中弹出一个元素
* @param s 链栈的头节点指针
* @return 被弹出元素的值。如果链栈为空,则返回-1。
*/
data_t stack_pop(linkstack s)
{
linkstack P; /* 用于暂存待删除节点的指针 */
data_t val; /* 用于存储弹出节点的数据 */
/* 检查链栈是否为空,为空则提示并返回错误码 */
if(s == NULL){
printf("s is NULL\n");
return -1;
}
/* 将P指向当前链栈的顶部节点 */
P = s->next;
/* 更新链栈头节点指针,将顶部节点从链栈中移除 */
s->next = P->next;
/* 从顶部节点获取数据 */
val = P->data;
/* 释放顶部节点的内存 */
free(P);
/* 将P置为空指针,避免悬挂指针 */
P = NULL;
/* 返回弹出的元素值 */
return val;
}
2.5 是否为空栈
/**
* 检查链接栈是否为空
*
* @param s 链接栈的指针
* @return 如果栈为空,则返回1;如果栈不为空,则返回0;如果s为NULL,则返回-1。
*
* 该函数首先检查链接栈的指针s是否为NULL,如果是,则打印提示信息并返回-1。
* 如果s不为NULL,则检查栈顶指针next是否为NULL,如果是,则表示栈为空,返回1;
* 如果next不为NULL,则表示栈不为空,返回0。
*/
int stack_empty(linkstack s)
{
if(s == NULL)
{
printf("s is NULL\n");
return -1;
}
return (s->next == NULL ? 1 : 0);
}
2.6 获取栈顶元素的值
/**
* 获取栈顶元素的值
*
* 本函数用于从链接栈中获取顶部元素的数据值。栈是一种后进先出(LIFO)的数据结构,
* 本函数不修改栈的状态,只是简单地返回栈顶元素的值。
*
* @param s 链接栈的指针。链接栈是一个特定类型的数据结构,包含指向栈顶元素的指针。
* @return 返回栈顶元素的值。如果栈为空,则行为未定义。
*/
data_t stack_top(linkstack s)
{
return (s->next->data);
}
2.7 销毁栈
/**
* 销毁链栈
* @param s 链栈指针
* @return 返回空链栈指针
*
* 该函数用于销毁一个链栈,释放链栈中所有节点的内存空间。
* 首先检查链栈是否为空,如果是,则直接返回空指针。
* 如果链栈不为空,则遍历链栈,逐个释放节点的内存,并将链栈指针更新为下一个节点。
* 最后,返回空指针,表示链栈已被销毁。
*/
linkstack stack_free(linkstack s)
{
linkstack P; // 用于暂存链栈节点的指针
// 检查链栈是否为空
if (s == NULL)
{
printf("s is NULL\n");
return NULL;
}
// 遍历链栈,释放节点内存
while (s->next)
{
P = s; // 暂存当前节点
s = s->next; // 移动到下一个节点
free(P); // 释放暂存的节点内存
P = NULL; // 清空指针,避免悬挂指针
}
return NULL;
}
三.linkstack.h linkstack.c
linkstack.h
#ifndef _LINKSTACK_H_
#define _LINKSTACK_H_
typedef int data_t;
typedef struct node {
data_t data;
struct node *next;
} linknode,* linkstack;
linkstack stack_create();
int stack_push(linkstack s, data_t val);
data_t stack_pop(linkstack s);
int stack_empty(linkstack s);
data_t stack_top(linkstack s);
linkstack stack_free(linkstack s);
#endif
linkstack.c
#include "linkstack.h"
#include <stdio.h>
#include <stdlib.h>
/**
* 创建一个链栈
*
* 链栈是一种特殊的线性表,其插入和删除操作都在表的同一端进行,这个端点称为栈顶。
* 本函数用于动态地创建一个链栈。链栈由链表实现,每个节点包含一个数据域和一个指针域。
*
* @return 创建的链栈的头指针。如果创建失败,返回NULL。
*/
linkstack stack_create()
{
// 分配内存空间用于创建链栈的头节点
linkstack s;
s = (linkstack)malloc(sizeof(linknode));
// 检查内存分配是否成功
if (s == NULL)
{
printf("malloc error\n");
return NULL;
}
// 初始化链栈,设置头节点的下一个节点为NULL,数据域为0
s->next = NULL;
s->data = 0;
return s;
}
/**
* 向链栈中压入一个新元素
* @param s 链栈的头节点指针
* @param val 要压入的新元素的值
* @return 如果操作成功,返回0;如果链栈为空或内存分配失败,返回-1。
*/
int stack_push(linkstack s, data_t val)
{
linkstack P; /* 新建一个节点指针P,用于存放新压入的元素 */
/* 检查链栈是否为空,如果为空则打印错误信息并返回-1 */
if (s == NULL)
{
printf("s is NULL\n");
return -1;
}
/* 为新节点分配内存,如果内存分配失败则打印错误信息并返回-1 */
P = (linknode *)malloc(sizeof(linknode));
if (P == NULL)
{
printf("malloc error\n");
return -1;
}
/* 设置新节点的数据值和下一个节点的指针 */
P->data = val;
P->next = s->next;
/* 将新节点链接到链栈的头部 */
s->next = P;
/* 操作成功,返回0 */
return 0;
}
/**
* 从链栈中弹出一个元素
* @param s 链栈的头节点指针
* @return 被弹出元素的值。如果链栈为空,则返回-1。
*/
data_t stack_pop(linkstack s)
{
linkstack P; /* 用于暂存待删除节点的指针 */
data_t val; /* 用于存储弹出节点的数据 */
/* 检查链栈是否为空,为空则提示并返回错误码 */
if (s == NULL)
{
printf("s is NULL\n");
return -1;
}
/* 将P指向当前链栈的顶部节点 */
P = s->next;
/* 更新链栈头节点指针,将顶部节点从链栈中移除 */
s->next = P->next;
/* 从顶部节点获取数据 */
val = P->data;
/* 释放顶部节点的内存 */
free(P);
/* 将P置为空指针,避免悬挂指针 */
P = NULL;
/* 返回弹出的元素值 */
return val;
}
/**
* 检查链接栈是否为空
*
* @param s 链接栈的指针
* @return 如果栈为空,则返回1;如果栈不为空,则返回0;如果s为NULL,则返回-1。
*
* 该函数首先检查链接栈的指针s是否为NULL,如果是,则打印提示信息并返回-1。
* 如果s不为NULL,则检查栈顶指针next是否为NULL,如果是,则表示栈为空,返回1;
* 如果next不为NULL,则表示栈不为空,返回0。
*/
int stack_empty(linkstack s)
{
if (s == NULL)
{
printf("s is NULL\n");
return -1;
}
return (s->next == NULL ? 1 : 0);
}
/**
* 获取栈顶元素的值
*
* 本函数用于从链接栈中获取顶部元素的数据值。栈是一种后进先出(LIFO)的数据结构,
* 本函数不修改栈的状态,只是简单地返回栈顶元素的值。
*
* @param s 链接栈的指针。链接栈是一个特定类型的数据结构,包含指向栈顶元素的指针。
* @return 返回栈顶元素的值。如果栈为空,则行为未定义。
*/
data_t stack_top(linkstack s)
{
return (s->next->data);
}
/**
* 销毁链栈
* @param s 链栈指针
* @return 返回空链栈指针
*
* 该函数用于销毁一个链栈,释放链栈中所有节点的内存空间。
* 首先检查链栈是否为空,如果是,则直接返回空指针。
* 如果链栈不为空,则遍历链栈,逐个释放节点的内存,并将链栈指针更新为下一个节点。
* 最后,返回空指针,表示链栈已被销毁。
*/
linkstack stack_free(linkstack s)
{
linkstack P; // 用于暂存链栈节点的指针
// 检查链栈是否为空
if (s == NULL)
{
printf("s is NULL\n");
return NULL;
}
// 遍历链栈,释放节点内存
while (s->next)
{
P = s; // 暂存当前节点
s = s->next; // 移动到下一个节点
free(P); // 释放暂存的节点内存
P = NULL; // 清空指针,避免悬挂指针
}
return NULL;
}