何为栈
栈(stack)是只允许在一段进行插入和删除数据的操作的线性表。所以栈是一种操作受限的线性表。栈中有一下三个概念。
栈顶(Top):线性表允许进行操作的那一端(插入、删除)
栈底(Bottom):固定的,不允许进行操作的一端
空栈:不含任何元素的空表
压栈:向栈中插入数据
出栈:删除栈顶数据
栈的操作就像一个箱子,我们放入数据的时候只能层层堆叠,取数据的时候只能从最上面的数据开始。这种栈的操作特性可以简单的概括为后进先出(Last In First Out)
所以对于栈来讲:他属于线性表,所以逻辑结构依然是线性结构,当然存储结构也可以使用顺序存储和链式存储。
栈的数学性质: n各不同元素进栈,出栈元素不同排列个数为
1
n
+
1
C
2
n
n
\frac{1}{n+1}C_{2n}^n
n+11C2nn
栈的顺序存储
采用顺序存储的栈也成为顺序栈,类似于顺序表,,使用一块连续的存储单元存放数据,并设置一个指针(top),指示当前栈顶元素。
顺序栈的实现
#include<stdio.h>
#include<stdlib.h>
typedef struct stack{
int *data; // 数据区域
int size, top, cmp; //size 栈大小 top栈顶指针 cmp栈容量大小
}stack;
其实我们不需要设置size,top本可很好的表示,但是为了观察和解释方便设置了size,如果是静态的话,我们也不需要设置cmp,我们实现的是动态的。所以在我的实现中,为了避免功能重复,top我就直接变成了栈顶数据。
stack *initStack(int cmp){
stack *s = (stack *)malloc(sizeof(stack));
s->data = (int *)malloc(sizeof(int) * cmp);
s->cmp = cmp;
s->size = 0;
s->top = -1;
return s;
}
void deleteStack(stack *s){
free(s->data);
free(s);
}
首先依旧是初始化工作和销毁工作,当然销毁的时候,为了避免内存泄漏要先释放数据区,然后释放栈。
void getTop(stack *s){
printf("the top :%d\n",s->top);
}
void pushStack(stack *s, int val){
if(s->size == s->cmp){
s->cmp *= 2;
s->data = (int *)realloc(s->data, sizeof(int) * s->cmp);
printf("exp!\n");
}
s->data[s->size] = val;
s->top = s->data[s->size];
s->size++;
}
int top(stack *s){
if(s->size == 0){
s->top = -1;
return 0;
}
s->size--;
s->top = s->data[s->size - 1];
return 1;
}
在这里呢我只实现了三个栈操作,查看栈顶元素,压栈和出栈,因为我们数据区是动态的所以需要考虑扩容的工作,但是基于栈的性质,扩容工作很好实现(在循环队列的时候就会比较麻烦)。
void showStack(stack *s){
int a = s->size;
while(a--){
printf("%d ",s->data[a]);
}
printf("\n");
}
当然在最好还要再写一个输出功能来检验我们的实现成果
int main(){
stack *s = initStack(3);
int a,val;
while(1){
scanf("%d", &a);
if(a == 1){
scanf("%d",&val);
pushStack(s ,val );
}
else if(a == 2){
top(s);
}
else if(a == 3){
getTop(s);
continue;
}
else{
deleteStack(s);
}
showStack(s);
printf("---------------------------------------------\n");
}
return 0;
}
主函数部分,进行测试。测试结果如下
这样就基本实现了顺序栈的功能。
共享栈
可以看到栈的特点是,栈顶始终不变,栈顶进行插入和弹出,如果我们开辟了一个很大的一位数组空间,但是如果栈的大小很小的话,那么就造成了资源浪费。所以这个时候我么可以让两个栈公用一片空间,基于栈的特性。一个栈底设在数组低位0,一个栈底设在数组高位array_max。两个栈顶往中间延伸,可以更充分的利用资源空间。
当然,当两个栈的栈顶指针满足
t
o
p
1
−
t
o
p
2
=
1
top_1 -top_2 = 1
top1−top2=1的时候,就是栈满的情况。可能发生数据上溢。但是对于两个栈的存取没有任何影响。
栈的链式存储结构
采用链式存储结构的栈称为链栈,相较于顺序栈,链栈的优点就是有多个栈共享存储空间和提高其效率,且不存在栈满上溢的情况,通常用单链表实现。
#include<stdio.h>
#include<stdlib.h>
typedef struct node{ //节点
int val;
node *next;
}node;
typedef struct stack{ //栈
node *head;
int size, top;
}stack;
node *newNode(int val){ //创建新节点的方法
node *n = (node *)malloc(sizeof(node));
n->val = val;
n->next = NULL;
return n;
}
相较于顺序栈功能并无变动,只是随着存储结构的改变进行的调整,但思路一致
stack *initStack(){ //初始化栈
stack *s = (stack *)malloc(sizeof(stack));
s->head = (node *)malloc(sizeof(node));
s->head->val = -1;
s->head->next = NULL;
s->size = 0;
s->top = 0;
return s;
}
void deleteStack(stack *s){ //销毁栈
node *t1 = s->head;
while(t1){
node *t2 = t1->next;
free(t1);
t1 = t2;
}
free(s);
}
int getTop(stack *s){ //获取栈顶元素
printf("%d\n",s->top);
return s->top;
}
void pushStack(stack *s, int val){ //压栈
node *n = newNode(val);
n->next = s->head->next;
s->head->next = n;
s->size++;
s->top = s->head->next->val;
}
void top(stack *s){ //出栈
node *n;
n = s->head->next;
s->head->next = s->head->next->next;
s->top = s->head->next->val;
free(n);
}
void showStack(stack *s){ //输出栈
node *n = s->head;
while(n->next){
n = n->next;
printf("%d ", n->val);
}
printf("\n");
}
同样是初始化,删除,压栈,出栈,查看栈顶元素以及输出栈的五种方法,还是需要注意的是,因为数据是以链式结构存储,所以在销毁栈之前需要遍历释放节点再释放栈。
int main(){
stack *s = initStack();
int a,val;
while(1){
scanf("%d", &a);
if(a == 1){
scanf("%d",&val);
pushStack(s ,val );
showStack(s);
}
else if(a == 2){
top(s);
showStack(s);
}
else if(a == 3){
getTop(s);
}
else{
deleteStack(s);
}
printf("---------------------------------------------\n");
}
return 0;
}
主函数运行,查看运行结果
小结
栈是一种操作受限制的线性表,所以他的逻辑结构依然是线性。
所以也有链式和顺序两种存储结构
顺序栈中有一个特殊的栈就是共享栈,一块连续的空间存放两个,栈底分别放在高位和低位
链栈相较于顺序栈的最大有点就是不会出现栈满上溢的情况
栈有一些基本操作,初始化栈,销毁栈,入栈、出栈、获取栈顶、判断栈孔空等
栈只能通过栈顶进行数据增加和删除,所以栈的最大特点就是LIFO后进先出。
另外栈有一个数学性质:就是n个元素进栈,出栈元素不同排列的个数为 1 n + 1 C 2 n n \frac{1}{n+1}C_{2n}^n n+11C2nn删除线格式