数据结构之通用栈(链表实现)
栈简介
栈是一种特殊的一维线性数据结构,它满足的特点是FILO(先进后出),这样一种操作顺序在日常的生活中比比皆是。比如我们厨房里放置盘子和取盘子,最先放置的盘子一定是放在较底下的地方,要取得其中一个盘子也要先把其上边的盘子拿走先。在计算机中也是满足这样的关系,比如说我们的函数调用就满足着栈的关系。
可以看到主函数调用时,在栈区对主函数main的栈桢进行了圧栈,随后的函数调用func_1、fun_2等函数也依次进行了圧栈,等函数运行结束后还会逆序地进行出栈。
所以说栈的调用在计算机和日常生活中非常的常见,我们形象的把满足如下关系的数据结构叫做栈:
栈的功能
栈的功能是什么?
栈最大的功能就是记忆!不管是函数调用的恢复,还是算法里状态的回溯,还是只能手机视图层级的切换,无一不是利用了栈的记忆特性,在后续数据结构二叉树的讲解中,我们也会看到关于栈的大量使用。
栈的实现
关于栈的实现我们采用了两种不同的方式,因为栈归根结底是一种一维线性的数据结构,只要给予一维线性数据结构相关操作的约束就可达到目标。我们这里采用对通用双端链表和通用动态数组两种数据结构的封装,从而实现通用栈。本篇文章首先介绍给大家如何使用通用双端链表封装通用栈。
stack实现的文件包含如下:
首先我们给出通用栈的结构设计和接口声明,该内容在stack.h文件中:
//通用栈的结构设计和接口声明
#ifndef _STACK_H_
#define _STACK_H_
#include "dlist.h"
#include "tools.h"
typedef struct Stack{
Dlist *dlist; //用双端链表的控制信息去模仿栈的控制信息
}Stack;
//关于栈的接口
Stack *init_stack(void) ; //初始化栈
void destroy_stack(Stack **stack) ; //销毁栈
Boolean is_stack_empty(Stack *stack) ; //判空
void push(Stack *stack, void *value) ; //入栈
Boolean pop(Stack *stack) ; //出栈
Boolean get_top(Stack *stack, void **value) ; //得到栈顶元素
int get_stack_count(Stack *stack) ; //得到栈的个数
#endif
我们虽然定义了栈对数据结构,但是其中只是对通用双端链表的控制信息尽心了简单的封装。剩下的部分就是对于栈的接口声明,后续我们要实现栈的接口:
//栈的接口实现
#include "stack.h"
//栈的接口实现
Stack *init_stack(void) //初始化栈
{
Stack *stack = (Stack *)Malloc(sizeof(Stack));
stack->dlist = (Dlist *)malloc(sizeof(Dlist));
if(stack->dlist == NULL){
free(stack);
fprintf(stderr, "the memory is full!\n");
exit(1);
}
return stack;
}
void destroy_stack(Stack **stack) //销毁栈
{
if(stack == NULL || *stack == NULL){
return ;
}
//先释放stack里的dlist,再释放stack
destroy_dlist(&((*stack)->dlist));
free(*stack);
*stack = NULL;
}
Boolean is_stack_empty(Stack *stack) //判空
{
return get_stack_count(stack) == ZERO;
}
void push(Stack *stack, void *value) //入栈
{
if(stack == NULL || value == NULL){
return ;
}
push_front(stack->dlist, value); //入栈操作封装了双端链表的头部插入
}
Boolean pop(Stack *stack) //出栈
{
if(stack == NULL || is_stack_empty(stack)){
return FALSE;
}
return pop_front(stack->dlist);
}
Boolean get_top(Stack *stack, void **value) //得到栈顶元素
{
if(stack == NULL || is_stac