什么是栈?
栈(Stack)是一种基本的数据结构,它是一种后进先出(LIFO)的数据结构,它允许在一端插入和删除元素。栈的应用非常广泛,例如在编程语言中用于存储函数调用和返回地址,以及在操作系统中用于管理进程的内存分配等。
栈的实现方式
栈可以使用数组或链表来实现。使用数组实现栈时,需要定义一个固定大小的数组,并使用一个指针来跟踪栈顶。pushstack 操作将元素添加到数组的末尾,然后将指针向上移动一位,而 popstack 操作则将指针向下移动一位并返回数组的最后一个元素。使用链表实现栈时,每个节点都包含一个数据元素和一个指向下一个节点的指针。pushstack 操作将一个新节点添加到链表的头部,而 popstack 操作则将头节点删除并返回其数据元素。
下面我们分别来看一下使用数组和链表实现栈的代码。
使用数组实现栈
以下是使用数组实现栈的示例代码:
#include <stdio.h>
#include <stdlib.h>
//构造顺序栈结构体
struct seqstack
{
int data[20];
int size;
int top;//栈顶指针
};
//创建顺序栈
struct seqstack * create()
{
//顺序栈创建
struct seqstack *p = malloc(sizeof(struct seqstack));
//初始化顺序栈
p->size = 20;
p->top = 0;
return p;
}
//入栈
void pushstack(struct seqstack *stack,int data)
{
//栈满,不入栈
if(stack->top == stack->size)
{
printf("栈满\n");
return;
}
//入栈
//先插入数据
stack->data[stack->top] = data;
//top栈顶进行移动
stack->top++;
}
//出栈
int popsack(struct seqstack *stack)
{
//栈空,不出栈
if(stack->top == 0)
{
printf("栈空\n");
return -1;
}
//出栈
//先把栈顶指针往前一个位置移动
stack->top--;
//删除(由于是顺序栈即数组连续空间,数据覆盖即删除)
return stack->data[stack->top];
}
//遍历
void show(struct seqstack * stack)
{
for(int i = stack->top;i >= 0;i--)
{
printf("%d ",stack->data[i]);
}
printf("\n");
}
//主函数
int main()
{
struct seqstack * stack = create();
show(stack);
popstack(stack);
pushstack(stack,1);
pushstack(stack,2);
pushstack(stack,3);
pushstack(stack,4);
show(stack);
printf("当前出栈 %d\n",popstack(stack));
printf("当前出栈 %d\n",popstack(stack));
pushstack(stack,10);
pushstack(stack,20);
show(stack);
return 0;
}
在这个示例代码中,我们使用一个数组 data来存储栈中的元素,使用变量 top
来跟踪栈顶的位置。在 pushstack 操作中,我们首先检查栈是否已满,如果已满则输出错误信息。如果栈未满,则将元素添加到栈顶,并将 top 指针上移一位。在 popstack 操作中,我们首先检查栈是否为空,如果为空则输出错误信息并返回 -1。如果栈非空,则将栈顶元素弹出并返回。在show操作中,则是遍历顺序栈的所有元素。最后调用了主函数来进行了简单的测试顺序栈的插入、删除、遍历功能。
使用链表实现栈
以下是使用链表实现栈(头结点为栈顶)的示例代码:
#include <stdio.h>
#include <stdlib.h>
//构造结点
struct Node
{
int data;
struct linkstack *next;
};
//创建链表栈,要得到头结点的地址(存储了栈顶位置)
struct Node *create()
{
//创建空栈
//创建头结点,存储栈顶的地址
struct Node *head = malloc(sizeof(struct Node));
head->next = NULL;
return head;
}
//入栈
void pushstack(struct Node *head,int data)
{
//创建一个新结点
struct Node *new = malloc(sizeof(struct Node));
new->data = data;
//设置新结点存储当前栈顶
new->next = head->next;
//设置新结点为栈顶
head->next = new;
}
//出栈
void popstack(struct Node *head)
{
//判断是否为空
if(head->next == NULL)
{
printf("空栈\n");
return;
}
//出栈
struct Node *p = head->next;
head->next = p->next;
printf("出栈元素:%d\n",p->data);
free(p);
}
//遍历
void show(struct Node *head)
{
struct Node *p = head;
while(p->next != NULL)
{
p = p->next;
printf("%d ",p->data);
}
printf("\n");
}
//主函数
int main()
{
struct Node * stack = create();
popstack(stack);
pushstack(stack,1);
popstack(stack);
popstack(stack);
pushstack(stack,10);
pushstack(stack,20);
pushstack(stack,30);
show(stack);
popstack(stack);
popstack(stack);
return 0;
}
在这个示例代码中,我们使用一个链表来实现栈。每个节点包含一个数据元素和一个指向下一个节点的指针。在 pushstack 操作中,我们创建一个新节点,并将其头结点作为 top 节点。然后在此位置进行元素的插入,始终将入栈元素插入到头结点后面。在 popstack 操作中,我们首先检查栈是否为空,如果为空则输出为空信息。如果栈非空,则定义一个新结点来储存头结点后一个结点(出栈元素)后进行删除操作,输出当前节点的数据元素,并释放该结点。在show操作中,则是遍历顺序栈的所有元素。最后调用了主函数来进行了简单的测试顺序栈的插入、删除、遍历功能。
栈的应用场景
栈是一种非常有用的数据结构,它可以用于许多不同的应用程序。以下是一些常见的应用场景:
- 函数调用和返回地址的存储:在编程语言中,栈用于存储函数调用和返回地址。每当一个函数被调用时,其返回地址被压入栈中。当函数返回时,返回地址被弹出栈。
- 表达式求值:在编译器中,栈用于求解表达式。当遇到一个运算符时,需要将其压入栈中。当遇到一个操作数时,需要将其弹出栈,执行相应的操作,并将结果压入栈中。
- 内存分配:在操作系统中,栈用于管理进程的内存分配。每当一个进程被创建时,其栈被分配一定的内存空间。当进程调用函数或分配局部变量时,栈的大小会相应地增加。
- 浏览器历史记录:在浏览器中,栈用于存储浏览器历史记录。每当用户浏览一个新页面时,其 URL 被压入栈中。当用户点击“后退”按钮时,栈顶元素被弹出栈,并返回到上一个页面。
总之,栈是一种非常有用的数据结构,它可以用于许多不同的应用程序。无论是使用数组还是链表实现栈,都需要注意一些常见的问题和错误,以确保代码的正确性和可靠性。