栈的实现之链式栈

本文详细介绍了链式栈的实现原理,包括栈的特性(后进先出原则),以及如何通过链表结构实现栈的四种基本操作:入栈、出栈、访问栈顶元素和判空。作者通过实例演示了栈帧的创建、栈操作函数的编写,以及栈的测试过程。
摘要由CSDN通过智能技术生成

栈的实现之链式栈

相比较于数组和链表,栈是一种操作受限的线性结构。

这种操作受限体现在:

  1. 栈只能在同一端添加、删除以及访问元素(栈顶)
  2. 另一端无法执行任何操作(栈底),栈底的元素既不能直接增删,也不能直接访问。

你可以把栈想象成一个杯子,杯底相当于栈底,无法直接进行任何操作。杯口就相当于栈顶,是唯一可以进行添加、删除和访问操作的地方。

在栈中,最先添加到栈中的元素总是最后被删除的,遵循**后进先出(LIFO, Last In First Out)**的原则。

如下图所示:

栈示意图

栈这种数据结构的基本操作主要包括以下几种:

  1. 入栈/压栈(push):在栈顶添加一个元素,成为新的栈顶元素,其余元素则被压入栈底。时间复杂度O(1)
  2. 出栈/弹栈(pop):删除栈顶元素,下一个元素成为新的栈顶元素。时间复杂度O(1)
  3. 访问栈顶元素(peek):返回栈顶元素的值。时间复杂度O(1)
  4. 判空(is_empty):检查栈中是否有任何元素。时间复杂度O(1),因为只需要检查一下栈顶元素即可。

说白了,栈就是一个线性结构(数组或链表都可以),然后只提供栈顶的增删访问操作。

1. 创建栈帧结点

1.1链式栈模型

以链表为基础实现一个栈,首先要基于以下结构体:

typedef int E;

// 栈帧结点
typedef struct node {
  E data;
  struct node *next;
} StackFrame;

我们现在先在头文件my_stack.h头文件里声明结构体,实现创建栈帧结点。

这个链式栈的模型如下图所示:

链式栈模型图

一般而言,我可以再定义一个结构体用于表示整个栈,但这里我们直接在main函数中使用以下语句创建一个栈:

StackFrame *stack = NULL; // stack指针指向栈顶,代指整个栈。NULL表示栈为空

于是栈的四个基本操作,就可以变为链表的四个操作:

  1. 入栈:也就是头插法在链表中插入一个结点
  2. 出栈:删除链表的第一个结点
  3. 访问栈顶元素:访问链表的第一个结点
  4. 判断:判断链表的第一个结点是不是NULL

于是我们就可以把这四个操作转换为四个函数声明在头文件中:

// 入栈
bool stack_push(StackFrame** stack_p, E data);

// 出栈
E stack_pop(StackFrame** stack_p);

// 访问栈顶元素
E stack_peek(const StackFrame* stack);

// 判空
bool stack_is_empty(const StackFrame* stack);

在实现这四个函数功能的.c文件里面需要注意:

  1. 入栈和出栈的函数需要对传入的栈顶指针做出修改,所以需要传入栈顶指针的二级指针!!
  2. 访问和判空则不需要修改栈帧指针,直接传入指针即可。可以使用const修饰它,这是一个好习惯。

2. 实现函数功能my_stack.c

2.1 实现判空

判空是很容易实现的,而且判空对后续某些操作有一定作用,所以我们可以最先实现它。参考代码如下:

// 判空
bool stack_is_empty(const StackFrame* stack) {
  return stack == NULL;
}

2.2 实现入栈

单向链表是由一系列结点组成的线性数据结构,其中每个结点包含数据和一个指向下一个结点的指针。如下图所示:

单链表结构

在很多时候,我们往往还会定义一个LinkedList结构体类型以表示单链表结构,但这里我们先不用。主要练习二级指针的使用。

只在需要创建单向链表的地方,用下列代码创建一个NULL结点指针,表示此时的链表一个结点没有,是一个空链表。

StackFrame *top = NULL; // stack指针指向栈顶,代指整个栈。NULL表示栈为空

所谓入栈,也就是在链表的头插法插入一个结点,这里只需要注意下二级指针就可以了,参考代码如下:

// 入栈
bool stack_push(StackFrame** stack_p, E data) {
  // 1.创建新栈帧
  StackFrame *new_frame = malloc(sizeof(StackFrame));
  if (new_frame == NULL){
    // 分配失败处理
    printf("malloc failed in stack_push.\n");
    return false;
  }
  // 2.初始化新栈帧
  new_frame->data = data;
  new_frame->next = *stack_p;

  // 3.更新栈顶指针
  *stack_p = new_frame;
  return true;
}

首先,我们需要创建一个新的栈帧(StackFrame),栈帧是栈中的每个元素。我们通过调用malloc函数来分配内存空间,用来存储新的栈帧。如果分配失败,即malloc返回NULL,说明内存分配失败,我们打印错误信息,并返回false表示入栈失败。

接着,我们初始化新的栈帧。将传入的数据(data)赋值给新栈帧的data成员,这样新栈帧就存储了我们要入栈的数据。然后,将新栈帧的next成员指向原来的栈顶指针,这样新栈帧就连接到了原来的栈顶元素之上。

最后,我们更新栈顶指针。将栈顶指针(stack_p)指向新栈帧,这样新栈帧就成为了新的栈顶元素。

最后,返回true表示入栈成功。

2.3 实现出栈

所谓出栈,也就是删除链表的第一个结点,这个逻辑我们也很熟悉了,参考代码如下:

// 出栈
E stack_pop(StackFrame** stack_p) {
  if (stack_is_empty(*stack_p)){
    // 栈为空,出栈失败
    printf("error: stack is EMPTY!\n");
    exit(1);
  }

  // 初始化一个top指针指向栈顶,用于删除栈顶元素
  StackFrame* top = *stack_p;

  *stack_p = top->next;
  int data = top->data;
  free(top);
  return data;
}

2.4 实现访问栈顶元素

// 访问栈顶元素
E stack_peek(const StackFrame* stack)
{
	if (stack_is_empty(stack)) //判空
	{
		printf("error: stack is empty.\n");
		exit(1);
	}
	return stack->data;//返回栈顶元素值
}

3. 测试

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include "my_stack.h"

/**********************************************************************
 *                          链式栈之测试                              *
 **********************************************************************/

int main(void) {
	// 表示创建一个空的栈,这是一个指向栈顶元素的指针
	StackFrame* top = NULL;

	stack_push(&top, 1);
	stack_push(&top, 2);
	printf("%d\n",stack_peek(top));
	stack_push(&top, 3);
	stack_push(&top, 4);
	stack_push(&top, 5);

	printf("%d\n", stack_pop(&top));
	printf("%d\n", stack_pop(&top));
	printf("%d\n", stack_pop(&top));
	printf("%d\n", stack_pop(&top));
	printf("%d\n", stack_pop(&top));
	printf("%d\n", stack_pop(&top));

	return 0;
}

在这里插入图片描述

  • 17
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

如是我闻艺

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

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

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

打赏作者

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

抵扣说明:

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

余额充值