我们之前已经学习过顺序表,单向链表,单向循环链表,双向循环链表,下一个要学习的就是栈了。
1、基本概念
栈式一种逻辑结构,是特殊的线性表,特殊在:只能在固定的一段操作
只要满足上述条件,那么这种特殊的线性表就会呈现一种“先进后出”的逻辑,这种逻辑就是被称为栈。栈在生活中到处可见,比如堆叠的盘子、电梯中的人们等。
由于约定了只能在线性表固定的一段进行操作,于是给栈这种特殊的线性表的“插入”、“删除”,起了下面这些特定的名称:
- 栈顶:可以进行插入删除的一端
- 栈底:栈顶的对端
- 入栈:将节点插入栈顶之上,也称为压栈,函数名通常为push()
- 出栈:将节点从栈顶剔除,也称为弹栈,函数名通常为pop()
- 取栈顶:取得栈顶元素,但不出栈,函数名通常为top()
基于这种固定一端的操作的简单约定,栈获得了”后进先出“的基本特性,最后一个放入的元素,最先被拿出来:
2、顺序栈存储形式
栈只是一种数据逻辑,如何将数据存储于内存则是另一回事。一般而言,可以采用顺序存储形成顺序栈,或采用链式存储形成链式栈。
顺序栈:
顺序栈存储意味着开辟一块连续的内存来存储数据节点,一般而言,管理栈数据除了需要一块连续的内存之外,还需要记录栈的总容量、当前栈的元素个数、当前栈顶元素位置,如果有多线程还需要配互斥锁和信号量等信息,为了便于管理,通常将这些信息统一在一个管理结构体之中,其示意图为:
//管理结构体的定义
typedef struct seqStack
{
datatype* data; //顺序栈入口,通用数据类型
int size; //顺序栈总容量
int top; //顺序栈栈顶元素下标
}seqStack;
3、 顺序栈的代码实现
先需要初始化一个顺序栈,在执行入(压)栈动作或出(弹)栈动作之前,需要对顺序栈进行栈的空后者满进行一次判断,栈空则无法进行出栈,栈满则无法进行入栈。
//初始化空栈
seqStack* init(int size) //size为顺序栈的总容量大小
{
seqStack* s = malloc(sizeof(seqStack));
if(s != NULL)
{
s->data = malloc(sizeof(datatype) * size);
if(s->data == NULL)
{
free(s);
return NULL;
}
s->size = size;
s->top = -1; //栈顶元素的下标应该为0
}
return s;
}
//判断栈是否为空
bool isEmpty(seqStack* s)
{
return s->top == -1; //栈顶元素的下标为-1时则证明栈空
}
//判断栈是否为满
bool isFull(seqStack* s)
{
return s->top == s->size - 1; //如果栈顶元素的下标等于栈的总容量-1则证明栈满(栈的首个元素的下标为0)
}
//入栈(压栈)
bool push(seqStack* s,datatype data)
{
//判断栈是否为满
if(isFull(s))
{
return false;
}
//++top保证出栈的数据放在栈顶
s->data[++s->top] = data;
return true;
}
//出栈(弹栈)
bool pop(seqStack* s,datatype data)
{
//判断栈是否为空
if(isEmpty(s))
{
return false;
}
//栈顶元素下标-1,最后一个元素出来的时候栈顶元素下标为-1
s->top--;
return true;
}
但是,在入栈和出栈动作执行完之后需要检验自己的动作是否执行成功,所以就很有必须必要写一个取栈顶元素的功能函数
//取栈顶元素
bool top(seqStack* s,datatype* pm)
{
//判断栈是否为空
if(isEmpty(s))
{
return false;
}
//取栈顶元素下标所在的数据
*pm = s->data[s->top];
return true;
}
4、顺序栈的主函数测试代码
这里的主函数只是给予一种测试的思路,具体测试需要自己调试
int main()
{
//初始化一个空的顺序栈
seqStack* s = initStack(10);
if (s == NULL)
{
printf("初始化顺序栈失败!\n");
}
else
printf("初始化顺序栈成功!\n");
push(s, 3);
push(s, 4);
int m;
top(s, &m);
printf("当前的栈顶元素是:%d\n",m);
push(s, 5);
while (!isEmpty(s))
{
int m;
pop(s, &m);
printf("%d\n", m);
}
return 0;
}
5、链栈存储形式
链式栈:
链式栈的组织形式与链表无异,只不过插入删除被约束在固定的一段。为了便于操作,通常也会创建所谓管理结构体,用来存储栈顶指针、栈元素个数等信息
//链式栈节点
typedef struct node
{
//数据域
datatype data;
//指针域
struct node* next;
}node;
//链式栈管理结构体
typedef struct linkStack
{
node* top; //链式栈栈顶指针
int size; //链式栈当前元素个数
}linkStack;
6、链栈代码实现
需要的基本函数接口和顺序栈差不多
//初始化空栈
linkStack* initStack()
{
linkStack* s = malloc(sizeof(linkStack));
if(s != NULL)
{
s->size = 0;
s->top = NULL;
}
return ture;
}
//判断栈是否为空
bool isEmpty(linkStack* s)
{
return s->size == 0;
}
//创建栈的新节点
node* newNode(datatype data)
{
node* new = calloc(1,sizeof(node));
if(new != NULL)
{
new->data = data;
new->next = NULL;
}
return new;
}
//入栈
bool push(linkStack* s,node* new)
{
new->next = s->top; //新节点成为栈顶元素,原来的栈顶元素作为新节点的下一个节点
s->top = new; //新节点成为栈顶元素
s->size++; //栈的元素个数+1
}
//出栈
node* pop(linkStack* s)
{
node* p = top(s); //获取栈顶元素
if(isEmpty(s))
{
return false;
}
//更新栈
s->top = s->top->next;
s->size--;
p->next = NULL;
return p;
}
//取栈顶元素
bool top(linkStack* s,datatype* pm)
{
//判断栈是否为空
if(isEmpty(s))
{
return false;
}
//pm保存栈顶元素的数据域里面的数据
*pm = s->top->data;
return true;
}
7、链栈主函数测试代码
这里的主函数只是给予一种测试的思路,具体测试需要自己调试
int main()
{
//初始化一个空的链式栈
linkstack* s = initStack();
if (s)
{
printf("初始化空链栈成功!\n");
}
push(s, newNode(3));
push(s, newNode(4));
push(s, newNode(5));
node* p;
if(p = top(s))
printf("%d\n",p->data);
p = pop(s);
printf("%d\n", p->data);
free(p);
printf("%d\n",top(s)->data);
return 0;
}