【数据结构】顺序栈和链栈(带图详解)


在计算机科学中,栈是一种基本的数据结构,用于存储和管理数据。它的特点是后进先出(Last-In-First-Out,简称LIFO)的顺序。在本文中,我们介绍概念、用途、基本操作和实现细节。

1.什么是栈?

栈是一种线性数据结构,它的元素按照特定的顺序进行插入和删除操作。栈的插入操作称为入栈(push),删除操作称为出栈(pop)。栈可以看作是一种具有限制访问的线性表,只能在表的一端进行插入和删除操作,称为栈顶(top), 而另一端则称为栈底(bottom)。
在这里插入图片描述
在这里插入图片描述

1.1 栈的用途

栈在计算机科学中有着广泛的应用。以下是一些常见的栈的用途:

  1. 函数调用:计算机使用栈来跟踪函数调用和返回过程。当一个函数被调用时,它的局部变量和返回地址等信息被推入栈中,当函数执行结束后,这些信息被弹出栈。
  2. 表达式求值:栈可以用于解析和求值表达式,包括中缀表达式转换为后缀表达式和计算后缀表达式的值。
  3. 内存管理:栈在内存管理中起着重要的作用,用于管理函数调用和局部变量的内存分配。
  4. 浏览器历史记录:栈可以用于实现浏览器的历史记录功能,当用户浏览网页时,每个访问的URL被推入栈中,用户可以通过后退按钮依次访问之前的网页。
  5. 撤销操作:栈可以被用于实现撤销操作,每次操作的状态被推入栈中,用户可以通过撤销操作逐步恢复到之前的状态。

2.栈的结构与基本操作

栈的两种结构

1.栈的顺序存储(数组实现):

  • 通过数组来实现栈时,通常需要一个指针来标记栈顶的位置。栈的插入操作(入栈)就是将元素添加到数组[top+1]的位置,然后将栈顶指针top++。删除操作(出栈)就是从数组[top]位置删除元素,然后将栈顶指针top–

  • 数组实现的栈在访问元素上具有很高的效率,因为可以直接通过下标来访问。但是它的缺点是容量固定,插入和删除操作可能需要移动大量元素

  • 在使用数组实现栈时,需要注意栈的溢出问题。即当栈空间已满时,继续进行入栈操作会导致栈溢出。

在这里插入图片描述

typedef int STDatatype;
typedef struct Stack
{
	STDatatype* a;
	int capacity;
	int top;   // 初始为0,表示栈顶位置下一个位置下标
}ST;

2.链式存储结构(链表实现):

  • 通过链表来实现栈时,每个节点包含数据元素和一个指向下一个节点的指针。栈顶指针指向链表的头部

  • 链表实现的栈不需要事先指定最大容量,插入和删除元素的效率比较高,因为不需要移动元素。但是访问指定位置的元素的效率比数组实现要低

  • 链表实现的栈不需要事先指定最大容量,插入和删除元素的效率比较高,因为不需要移动元素。但是访问指定位置的元素的效率比数组实现要低。

在这里插入图片描述

typedef int STDataType;
typedef struct StackNode
{
struct StackNode* next; // 下一个节点的指针
STDataType data; // 节点存储的数据
} STNode;

typedef struct Stack
{
STNode* top; // 栈顶指针
} ST;


栈支持以下几种基本操作:

  1. 入栈(push):将元素插入到栈顶。
  2. 出栈(pop):从栈顶删除元素,并返回被删除的元素。
  3. 栈顶元素(top):返回栈顶的元素,但不对栈进行修改。
  4. 判空(isEmpty):检查栈是否为空,如果为空则返回真,否则返回假。
  5. 判满(isFull):检查栈是否已满,如果已满则返回真,否则返回假。

3.栈的实现细节

3.1 顺序栈

3.1.1 栈的初始化

用于初始化栈,将栈的数组成员置空,栈顶索引为0,栈的容量为0。然后动态申请一块大小为4的内存空间作为栈的数组,如果申请失败则输出错误信息并退出程序

void StackInit(ST* ps)
{
assert(ps); // 断言:ps 不为 NULL

// 初始化栈数据成员
ps->a = NULL; // 栈的数组成员为空
ps->top = 0; // 栈顶索引为 0
ps->capacity = 0; // 栈的容量为 0

// 重新分配空间,容量为 4
ps->a = (STDatatype*)malloc(sizeof(STDatatype)*4);
if (ps->a == NULL) // 如果分配失败,给出错误提示并退出程序
{
	perror("malloc fail");
	exit(-1);
}

// 更新栈数据成员
ps->top = 0; // 栈顶索引为 0
ps->capacity = 4; // 栈的容量为 4
}

3.1.2 元素入栈

将元素x入栈。如果栈已满(即栈的元素个数等于栈的容量),则重新分配一块大小为原来两倍的内存空间,并将原有的数据复制进来。然后将新元素放入栈顶(栈顶索引加一),更新栈顶索引

void StackPush(ST* ps, STDatatype x)
{
assert(ps); // 断言:ps 不为 NULL

// 如果栈满,需要重新分配更大的数组空间
if (ps->top == ps->capacity) 
{
	// 重新分配空间,并将原有数据复制进去
	STDatatype* tmp = (STDatatype*)realloc(ps->a, ps->capacity * 2 * sizeof(STDatatype));
	if (tmp == NULL) // 如果分配失败,给出错误提示并退出程序
	{
		perror("realloc fail");
		exit(-1);
	}

	// 重新分配空间成功后,更新栈数据成员
	ps->a = tmp;
	ps->capacity *= 2;
}

// 将新元素放入栈顶,更新栈顶索引
ps->a[ps->top] = x;
ps->top++;
}

3.1.3 元素出栈

将栈顶元素出栈。如果栈为空(栈的元素个数为0),则不进行操作。否则,将栈顶索引减一,即将栈顶元素出栈。

void StackPop(ST* ps)
{
assert(ps); // 断言:ps 不为 NULL
assert(!StackEmpty(ps)); // 断言:栈不为空

ps->top--; // 栈顶索引减一相当于将栈顶元素出栈
}

3.1.4 获取栈顶元素

获取栈顶元素的值。如果栈为空(栈的元素个数为0),则不进行操作。否则,返回栈顶元素的值(即栈顶索引减一对应的数组元素)

STDatatype StackTop(ST* ps)
{
assert(ps); // 断言:ps 不为 NULL
assert(!StackEmpty(ps)); // 断言:栈不为空

return ps->a[ps->top - 1]; // 返回栈顶元素
}

3.1.5 判断栈是否为空

bool StackEmpty(ST* ps)
{
assert(ps); // 断言:ps 不为 NULL

// 如果栈顶索引为 0,代表栈为空,否则不为空
return ps->top == 0;
}

3.1.6 获取栈的元素数量

int StackSize(ST* ps)
{
assert(ps); // 断言:ps 不为 NULL
return ps->top; // 返回栈的元素数量,即栈顶索引
}

3.1.7 获取栈的元素数量

// 销毁栈
void StackDestroy(ST* ps)
{
assert(ps); // 断言:ps 不为 NULL
free(ps->a); // 释放动态分配的栈数组
ps->a = NULL; // 栈数组成员为空
ps->top = ps->capacity = 0; // 栈顶索引和栈的容量都为 0
}

3.2 链栈

栈是一种后进先出(LIFO)的数据结构,我们可以将其视为一个容器,可以在栈顶上面进行插入和删除操作,并且只能访问栈顶的元素。

在这里插入图片描述

typedef int STDataType;

typedef struct StackNode
{
    struct StackNode* next; // 下一个节点的指针
    STDataType data; // 节点存储的数据
} STNode;

typedef struct Stack
{
    STNode* top; // 栈顶指针
} ST;

3.2.1 初始化栈

void StackInit(ST* ps)
{
    ps->top = NULL; // 初始化栈顶指针为NULL
}

3.2.2 销毁栈

void StackDestroy(ST* ps)
{
    STNode* cur = ps->top;
    while (cur != NULL)
    {
        STNode* next = cur->next;
        free(cur);
        cur = next;
    }
    ps->top = NULL; // 销毁栈后,将栈顶指针置空
}

3.2.3 创建新的节点

STNode* BuyStackNode(STDataType x)
{
    STNode* pnode = (STNode*)malloc(sizeof(STNode));
    if (pnode == NULL)
    {
        perror("malloc fail");
        exit(-1);
    }
    pnode->data = x; // 初始化节点的数据
    pnode->next = NULL; // 初始化节点的下一个指针为NULL

    return pnode;
}

3.2.3 将元素压入栈中

首先创建一个新的节点用于存储要压入的元素;其次将新节点连接到当前栈顶后面;最后将栈顶指针更新为新节点指针,使新节点成为新的栈顶元素。整个过程需要注意内存管理和各种边界条件。
在这里插入图片描述

void StackPush(ST* ps, STDataType x)
{
    STNode* newnode = BuyStackNode(x); // 创建新的节点
    newnode->next = ps->top; // 将新节点的下一个指针指向当前栈顶节点
    ps->top = newnode; // 更新栈顶指针为新的节点
}

3.2.4 将栈顶元素弹出

将栈顶指针向前移动一位以及更新栈顶指针。在执行出栈操作之前,应该检查链栈是否为空,以防止出现错误或异常。
在这里插入图片描述

void StackPop(ST* ps)
{
    if (ps->top == NULL)
    {
        return;
    }
    STNode* next = ps->top->next; // 获取当前栈顶节点的下一个节点指针
    free(ps->top); // 释放当前栈顶节点
    ps->top = next; // 更新栈顶指针为下一个节点
}

3.2.5 获取栈顶元素的值

STDataType StackTop(ST* ps)
{
    if (ps->top == NULL)
    {
        return -1;
    }
    return ps->top->data; // 返回栈顶节点的数据值
}

3.2.6 判断栈是否为空

bool StackEmpty(ST* ps)
{
    return ps->top == NULL; // 判断栈是否为空,根据栈顶指针是否为NULL
}

3.2.7 获取栈的大小

int StackSize(ST* ps)
{
    int count = 0;
    STNode* pcur = ps->top;
    while (pcur != NULL)
    {
        ++count;
        pcur = pcur->next;
    }
    return count; // 遍历栈,统计节点数量即为栈的大小
}

总结

栈是计算机科学中非常重要的数据结构,它具有简单且高效的特点。我们深入了解了栈的概念、用途、基本操作和实现细节。通过掌握栈的原理和应用,我们可以更好地理解和使用它,从而在解决实际问题时发挥其优势。

  • 5
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SuhyOvO

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值