0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

什么是顺序栈?什么又是链栈?

电子工程师 来源:编程学习总站 作者:写代码的牛顿 2021-06-15 10:50 次阅读

1、顺序栈

栈是一种后进先出的数据结构,栈的实现方式主要有2种,顺序栈和链栈。顺序栈则是栈的元素虚拟内存地址是连续的,链栈则是栈元素虚拟地址非连续的。在C语言里数组的元素虚拟地址是连续的但是数组大小必须在编译的时候确定,用于实现栈不够灵活。而在C语言里调用malloc申请到的一块内存虚拟地址是连续的,而且大小在运行期间确定,比较符合我们灵活的实现顺序栈的需求。先来看一下顺序栈的定义和函数声明。

#define NAN (0xFFFFFFFE) typedef struct stack{ int size; int cap; int front; int *arr; }_stack_t; extern void stack_init(_stack_t *s, int capacity); //初始化栈 extern void stack_push(_stack_t *s, int data); //入栈 extern int stack_pop(_stack_t *s); //出栈 extern int stack_size(_stack_t *s); //获取栈大小 extern bool stack_is_empty(_stack_t *s); //判断栈是否为空 extern bool stack_is_full(_stack_t *s); //判断栈是否满 extern void stack_destroy(_stack_t *s); //销毁栈

这里我们自定义了一个_stack_t类型,size是栈大小,cap是栈容量,front是栈顶,arr指针指向一块存储数据的内存,我们还通过宏定义了一个NAN值表示非法值。

栈初始化

函数实现如下:

void stack_init(_stack_t *s, int capacity){ if(s == NULL || capacity 《= 0){ return; } s-》arr = (int *)malloc(capacity * sizeof(int)); s-》front = 0; s-》size = 0; s-》cap = capacity; }

这里我们申请了一块内存用于存储数据,并保存栈容量大小。

入栈

函数实现如下:

void stack_push(_stack_t *s, int data){ if(s == NULL){ return; } if(stack_is_full(s)){ return; } s-》size++; //栈使用大小增1 s-》arr[s-》front++] = data; //保存数据后栈顶指针往后移 }

由于栈容量有限,每次将数据压入栈之前先判断一下栈是否满,栈未满才能继续往里压入数据。

出栈

每次出栈是后面入栈的数据先出,前面入栈的数据后出。函数实现如下:

int stack_pop(_stack_t *s){ if(s == NULL){ return NAN; } //判断栈是否空 if(stack_is_empty(s)){ return NAN; } s-》size--; //栈使用量减1 return s-》arr[--s-》front]; //先递减栈顶指针,获取栈顶数据 }

栈为空时说明栈里没有数据则返回一个非法值,否则获取栈顶数据并返回。

获取栈大小

函数实现如下:

int stack_size(_stack_t *s){ if(s == NULL){ return 0; } return s-》size; }

判断栈是否为空

函数实现如下:

bool stack_is_empty(_stack_t *s){ if(s == NULL){ return true; } return s-》size 》 0 ? false : true; }

判断栈是否满

函数实现如下:

bool stack_is_full(_stack_t *s){ if(s == NULL){ return false; } return s-》size == s-》cap ? true : false; }

销毁栈

函数实现如下:

void stack_destroy(_stack_t *s){ if(s == NULL){ return; } if(s-》arr){ free(s-》arr); } s-》arr = NULL; s-》cap = 0; s-》size = 0; s-》front = 0; }

销毁栈操作主要是释放内存,并初始化成员变量。

2、链栈

在前面的文章中我们讲解了单链表,在文中我们采用头插法插入结点到链表,由于头插法每次将最新的数据插入到链表头,如果依次遍历链表获取链表结点的数据,就是标准的栈弹出数据的操作。现在我们用前面文章实现的单链表实现一个链栈,顾名思义链栈就是用链式数据结构实现栈。我们自定义一个栈数据类型并声明一些操作函数。

typedef list_t stack_linked_t; //自定义栈数据类型 extern stack_linked_t *new_stack_linked_node(int data); //新建一个栈结点 extern void stack_linked_push(stack_linked_t **s, int data); //入栈 extern int stack_linked_pop(stack_linked_t **s); //出栈 extern int stack_linked_size(stack_linked_t *s); //获取栈大小 extern bool stack_linked_is_empty(stack_linked_t *s); //判断栈是否为空 extern void stack_linked_destroy(stack_linked_t **s); //销毁栈

这里我们将list_t自定义成stack_linked_t,看似多此一举,实际上更直观了,我们虽然使用链表实现栈,但是如果将数据类型声明为list_t反而更迷惑。由于链栈是链式结构不存在栈是否满的情况,除非已经无法申请到内存。

新建栈结点

函数实现如下:

stack_linked_t *new_stack_linked_node(int data){ return new_list_node(data); }

这里我们直接对新建链表结点函数进行封装,后面我们也会大量用到链表操作函数,差不多都是类似的封装。

入栈

函数实现如下:

void stack_linked_push(stack_linked_t **s, int data){ //这里一定要注意分开两个if,因为或运算符的特性 if(s == NULL){ return; } if(*s == NULL){ return; } //采用头插法插入链表 *s = list_add(*s, data); }

这里重点注意由于我们传入的是一个二级指针if(s == NULL)和if(*s == NULL)一定要分开处理,不能使用||运算进行处理,因为||运算符会执行第二个判断,如果s == NULL成立那么在执行第二个判断时由于使用了空指针程序会奔溃。

出栈

为了获取链表头结点,我们定义了一个获取链表头结点函数,函数实现如下:

list_t *get_list_head(list_t **list){ if(list == NULL){ return NULL; } if(*list == NULL){ return NULL; } list_t *head = *list; //链表只有一个结点 if((*list)-》next == NULL){ *list = NULL; return head; } //链表长度大于1则保存头结点,新头结点是原头结点的下一个结点 *list = (*list)-》next; head-》next = NULL; //原头结点一定要将next指针置为NULL return head; }

出栈函数实现如下:

int stack_linked_pop(stack_linked_t **s){ //这里一定要注意分开两个if,因为或运算符的特性 if(s == NULL){ return NAN; } if(*s == NULL){ return NAN; } stack_linked_t *stack_node = get_list_head(s); int data = stack_node-》data; free(stack_node); return data; }

获取链表头结点数据并释放内存。

获取栈大小

获取栈大小其实就是获取链表长度,因此我们定义了一个获取链表长度函数,函数实现如下:

//获取链表长度 int list_length(list_t *list){ if(list == NULL){ return 0; } int length = 0; while(list != NULL){ length++; list = list-》next; } return length; }

获取栈大小实现函数如下:

int stack_linked_size(stack_linked_t *s){ if(s == NULL){ return 0; } return list_length(s); }

判断栈是否为空

函数实现如下:

bool stack_linked_is_empty(stack_linked_t *s){ if(s == NULL){ return true; } return list_length(s) 》 0 ? false : true; }

链表长度为0则链表为空,非0则有数据。

销毁栈

函数实现如下:

void stack_linked_destroy(stack_linked_t **s){ if(s == NULL){ return; } if(*s == NULL){ return; } list_destroy(*s); *s = NULL; }

3、验证测试

最后我们写一个小程序验证一下我们实现的栈是否正确,代码如下:

#include 《stdio.h》 #include “stack.h” int main() { _stack_t my_stack; int i = 0; stack_init(&my_stack, 5); //初始化栈 printf(“进栈顺序 ”); for(i = 0; i 《 5; i++){ printf(“%d, ”, i); stack_push(&my_stack, i); //将数据压入栈 } printf(“ ”); if(stack_is_full(&my_stack)){ printf(“栈已满 ”); }else{ printf(“栈未满 ”); } printf(“栈的大小是:%d ”, stack_size(&my_stack)); printf(“出栈顺序是 ”); for(i = 0; i 《 5; i++){ printf(“%d ,”, stack_pop(&my_stack)); } printf(“ ”); if(stack_is_empty(&my_stack)){ printf(“栈为空 ”); }else{ printf(“栈未空 ”); } stack_destroy(&my_stack); //销毁栈 printf(“ ”); printf(“用链表实现栈 ”); printf(“入栈顺序 ”); printf(“%d ,”, 0); stack_linked_t *my_stack2 = new_stack_linked_node(0); for(i = 0; i 《 5; i++){ printf(“%d ,”, i + 1); stack_linked_push(&my_stack2, i + 1); } printf(“ ”); printf(“栈大小是: %d ”, stack_linked_size(my_stack2)); printf(“出栈顺序是 ”); for(i = 0; i 《 6; i++){ printf(“%d ,”, stack_linked_pop(&my_stack2)); } printf(“ ”); if(stack_linked_is_empty(my_stack2)){ printf(“链栈为空 ”); }else{ printf(“链栈非空 ”); } stack_linked_destroy(&my_stack2); return 0; }

编译运行输出:

进栈顺序 0, 1, 2, 3, 4, 栈已满 栈的大小是:5 出栈顺序是 4 ,3 ,2 ,1 ,0 , 栈为空 用链表实现栈 入栈顺序 0 ,1 ,2 ,3 ,4 ,5 , 栈大小是: 6 出栈顺序是 5 ,4 ,3 ,2 ,1 ,0 , 链栈为空

输出完全正确。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • C语言
    +关注

    关注

    180

    文章

    7534

    浏览量

    129333

原文标题:数据结构与算法篇-栈

文章出处:【微信号:AndroidPush,微信公众号:Android编程精选】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    求助,关于cortex-M3的压问题求解

    我们都知道cortex-m3中断时是硬件自动压的,这样可以减少中断响应和恢复时间。中断硬件压的寄存器为xPSR, PC, LR, R12, R0-R3,为什么其他寄存器不需要压呢?
    发表于 04-28 08:18

    STM32启动文件中大小根据什么设置的呢?

    大神们,STM32启动文件中大小根据什么设置的呢?我每次都是设置一个大概,但是这样总感觉不安全有没有高手指点一二,感激不尽。
    发表于 04-24 08:01

    如何解决蓝牙协议被锁问题?

    如何解决蓝牙协议被锁问题
    发表于 03-21 08:21

    SimpliciTI这个无线协议的性能咋样?能接多少个节点?

    SimpliciTI这个无线协议的性能咋样? 能接多少个节点,多节点并发,这个延迟多少????
    发表于 11-10 08:07

    单片机两个指针msp,psp分别在什么情况下使用?

    单片机两个指针 msp,psp 分别在什么情况下使用
    发表于 11-01 07:39

    51单片机初始化之后SP值指向顶还是底?

    51单片机初始化之后SP值指向顶还是底。51单片机是升还是降
    发表于 10-30 07:43

    HC-05k蓝牙模块怎么才能作为主使用?

    HC-05k蓝牙模块怎么才能作为主使用
    发表于 10-12 07:45

    SPI在通信的过程中是用什么来区别主和从的?

    SPI在通信的过程中是用什么来区别主和从
    发表于 10-10 07:15

    软件中的堆栈,堆和是不同的东西吗?

    堆区和区有什么不同的地方
    发表于 10-10 07:12

    SPI的总线通信的时候MOSI和MISO哪一个才是主的输出?

    到底MOSI还是MISO才是主的输出信号
    发表于 10-10 06:24

    IIC协议中是怎么确定主和从的?

    是通过什么方式来判断一个设备是主还是从
    发表于 10-10 06:01

    STM32WB产品详解及FUS无线协议升级

    STM32WB产品详解及FUS无线协议升级2.4GHz无线双核STM32WB, 采用SoC单芯片设计,支持多协议射频。
    发表于 09-06 06:35

    如何查看入时保存的寄存器的值?

    我想通过调试,看一下线程切换时线程、主堆栈、各个寄存器的变化,但是不知道怎么操作,谢谢。 此时,进入异常时的自动压使用的是进程堆栈,进入异常 handler 后才自动改为 MSP,退出异常时切换
    发表于 09-03 11:04

    rt_vsnprintf函数内部数组分配为什么不在内?

    rt_vsnprintf包的函数print_integer,buf的地址是 0x20000a68 为何没有分配在内?怀疑是此处的问题 map文件的设置如下 .stack 0x20000f680x800 load
    发表于 08-20 11:45

    STM32WB BLE协议编程指南

    本文档的主要目的是为开发人员提供有关如何使用 STM32WB BLE 协议 API 和相关事件回调开发低功耗蓝牙(BLE)应用的一些参考编程指南。本文档介绍了允许访问 STM32WB 片上系统所提
    发表于 08-14 10:13