栈是一种重要的数据结构。它是一种特殊的线性表,特殊在它只允许在表的一端进行插入和删除操作,这一端被称为栈顶,相对的,另一端被称为栈底。在这篇博客中,我们将详细地介绍栈的概念,包括顺序栈和链栈的实现。
栈是一种线性数据结构,它遵循 "先入后出"(Last-In-First-Out,LIFO)的原则。这意味着最后插入栈的元素将首先被移除,而最早插入的元素将最后被移除。就像如果1先进入了栈底,你想再拿到它,那么就要先把 6 5 4 3 2依次拿出来才能拿到 1。
顺序栈是栈的一种实现方式,它是用一组连续的存储单元存储栈中的元素,并需要一个栈顶指针 *top指出栈顶元素,初始化为-1,栈的大小 size则决定了最多可以放多少数据。
顺序栈结构体
#include
#include
#include
typedef int Datatype;
//顺序栈结构体
typedef struct Sequent_stack
{
Datatype *stack; //存放数据的位置
int size; //栈的大小
int top; //栈顶偏移
}S_stack;
//初始化顺序栈
S_stack *init_stack(int size)
{
S_stack *s1= malloc(sizeof(S_stack));
if (s1 != NULL)
{
s1->stack = calloc(size, sizeof(Datatype));
s1->size = size;
s1->top = -1;
}
return s1;
}
//是否栈空
bool isempty(S_stack *s)
{
return (s->top == -1);
}
//是否栈满
bool isfull(S_stack *s)
{
return (s->top == s->size-1);
}
//压栈
bool push(S_stack *s, Datatype data)
{
if (isfull(s))
{
return false;
}
//将栈顶往上偏移一个
s->top++;
//将数据,放到栈顶指向的位置
s->stack[s->top] = data; //*(s->stack+s->top) = data;
return true;
}
//弹栈
bool pop(S_stack *s, Datatype *data)
{
if (isempty(s))
{
return false;
}
//先拿到栈顶指向的数据
*data = s->stack[s->top];//data = *(s->stack+s->top);
//栈顶在往下偏移一个
s->top--;
return true;
}
//遍历栈
void display(S_stack *s)
{
for (int i = 0; i <= s->top; ++i)
{
printf("%d ", s->stack[i]);
}
printf("\n");
}
输入一个大于零的十进制数,转化为八进制,转化过程中使用顺序栈结构来实现。输入: 123 输出 :转化为八进制是 0173。
完整代码:
#include
#include
#include
typedef int Datatype;
//顺序栈结构体
typedef struct Secqune_stack
{
Datatype *stack; //存放数据的位置
int size; //栈的大小
int top; //栈顶偏移
}S_stack;
//初始化顺序栈
S_stack *init_stack(int size)
{
S_stack *s1= malloc(sizeof(S_stack));
if (s1 != NULL)
{
s1->stack = calloc(size, sizeof(Datatype));
s1->size = size;
s1->top = -1;
}
return s1;
}
//栈空
bool isempty(S_stack *s)
{
return (s->top == -1);
}
//栈满
bool isfull(S_stack *s)
{
return (s->top == s->size-1);
}
//压栈(入栈)
bool push(S_stack *s, Datatype data)
{
if (isfull(s))
{
return false;
}
//将栈顶往上偏移一个
s->top++;
//将数据,放到栈顶指向的位置
s->stack[s->top] = data; //*(s->stack+s->top) = data;
return true;
}
//弹栈(出栈)
bool pop(S_stack *s, Datatype *data)
{
if (isempty(s))
{
return false;
}
//先拿到栈顶指向的数据
*data = s->stack[s->top];//data = *(s->stack+s->top);
//栈顶在往下偏移一个
s->top--;
return true;
}
int main(int argc, char const *argv[])
{
//初始化顺序栈
S_stack *s = init_stack(22);
int num;
scanf("%d", &num);
while(num)
{
push(s, num%8);
num/=8;
}
printf("十进制数转为八进制数等于:0");
int data;
while(!isempty(s))
{
pop(s, &data);
printf("%d", data);
}
printf("\n");
return 0;
}
链式栈是栈的另一种常见实现方式,它通过链表来表示栈的结构。链式栈相对于顺序栈的一个主要优势是它可以动态地调整大小,适用于栈容量需求不确定或需要频繁的插入和删除操作的情况。与之相对应的是链式栈也需要更多的内存空间用于存储节点指针。
在具体的指针操作方面与单项链表相似。
typedef int Datatype;
typedef struct Node
{
Datatype data; //数据
struct Node *prev; //指向上一个节点
}node;
//链式栈的节点
typedef struct List_stack
{
node *stack; //每一个节点的数据
int size; //长度
}Lstack;
//初始化链式栈
Lstack *init_list_stack()
{
Lstack *s = malloc(sizeof(Lstack));
if (s!=NULL)
{
s->stack = NULL;
s->size = 0;
}
return s;
}
由于链式栈独特的结构使之不会出现栈满的情况,而是根据需要随时动态调整栈容量,因此我们无需判断链式栈是否栈满。
//链式栈的栈空判断
bool isempty(Lstack *s)
{
return (s->size == 0);
}
//压栈(入栈)
bool push(Lstack *s, Datatype data)
{
node *new = malloc(sizeof(node));
if (new != NULL)
{
//如果new不为空
new->data = data;
//新节点的prev指向栈原来的尾部
new->prev = s->stack;
//新节点现在是不是就变为现在链式栈的尾部
s->stack = new;
//栈的大小+1
s->size++;
return true;
}
else
{
return false;
}
}
//弹栈(出栈)
bool pop(Lstack *s, Datatype *data)
{
//栈空
if (isempty(s))
{
return false;
}
//先将栈的尾部(栈顶)的节点的地址拿到
*data = s->stack->data;
//将尾部(栈顶)的节点往前挪一个
s->stack = s->stack->prev;
//链式栈的节点个数-1
s->size--;
return true;
}
输入一个大于零的十进制数,转化为十六进制,转化过程中使用链式栈结构来实现。输入: 123 输出 :转化为八进制是 0x7B。
#include
#include
#include
typedef int Datatype;
typedef struct Node
{
Datatype data; //数据
struct Node *prev; //指向上一个节点
}node;
//链式栈的节点
typedef struct List_stack
{
node *stack; //每一个节点的数据
int size; //长度
}Lstack;
//初始化链式栈
Lstack *init_list_stack()
{
Lstack *s = malloc(sizeof(Lstack));
if (s!=NULL)
{
s->stack = NULL;
s->size = 0;
}
return s;
}
//链式栈的栈空判断
bool isempty(Lstack *s)
{
return (s->size == 0);
}
//压栈(入栈)
bool push(Lstack *s, Datatype data)
{
node *new = malloc(sizeof(node));
if (new != NULL)
{
//如果new不为空
new->data = data;
//新节点的prev指向栈原来的尾部
new->prev = s->stack;
//新节点现在是不是就变为现在链式栈的尾部
s->stack = new;
//栈的大小+1
s->size++;
return true;
}
else
{
return false;
}
}
//弹栈(出栈)
bool pop(Lstack *s, Datatype *data)
{
//栈空
if (isempty(s))
{
return false;
}
//先将栈的尾部(栈顶)的节点的地址拿到
*data = s->stack->data;
//将尾部(栈顶)的节点往前挪一个
s->stack = s->stack->prev;
//链式栈的节点个数-1
s->size--;
return true;
}
int main(int argc, char const *argv[]) {
Lstack *s = init_list_stack();
int num;
Datatype data;
char hexDigits[] = "0123456789ABCDEF"; // 十六进制字符表示
printf("请输入一个十进制数:");
scanf("%d", &num);
while (num > 0) {
int num1 = num % 16;
push(s, num1);
num /= 16;
}
printf("转化为十六进制是:0x");
while (!isempty(s)) {
pop(s, &data);
printf("%c", hexDigits[data]);
}
printf("\n");
// 释放内存
while (pop(s, &data)) {
free(s->stack);
s->stack = s->stack->prev;
}
free(s);
return 0;
}
顺序栈和链栈是栈的两种不同实现方式,它们各有优点和缺点。
1.空间分配:顺序栈需要一次性分配一整块连续的内存空间,可能导致空间浪费。而链栈则可以动态分配空间,用多少分配多少,因此在内存利用上更有优势。
2.时间复杂度:由于顺序栈是基于数组的,因此在访问元素时可以直接通过索引进行访问,时间复杂度为O(1)。而链栈需要通过指针进行遍历,时间复杂度为O(n)。
3.插入和删除:两者在插入和删除操作上都是O(1)的时间复杂度。顺序栈通过改变栈顶指针位置进行插入和删除,链栈通过改变指针的指向进行插入和删除。
栈在计算机科学和日常编程中扮演着重要的角色,以下是一些常见的使用场景:
1.函数调用:每当程序调用一个函数时,系统会将返回地址和必要的信息存入系统栈中,待函数运行完毕后再从栈中恢复这些信息。
2.表达式求值:栈可以用于算术或逻辑表达式的求值。例如,计算机程序通常使用两个栈来处理数学表达式,一个栈用于存储操作数,另一个栈用于存储运算符。
3.括号匹配:栈可以用于检查一段文本中的括号是否正确匹配。当遇到一个开括号时,将其压入栈中。当遇到一个闭括号时,从栈顶弹出一个开括号,如果能够匹配,则继续处理,否则就是不匹配。
总的来说,栈是一种非常实用的数据结构,理解和掌握栈以及其操作对于深入理解计算机程序的运行机制以及编写高效的代码有着重要的作用。
另外留一个括号匹配的题,代码会放评论区。
学习数据结构是一个漫长的过程,但只要我们持续不断地学习和实践,就一定能够掌握它。希望这篇文章能帮助你对栈有更深的理解和应用。如果你有任何问题或者想要讨论的点,欢迎在下面的评论区留言。
更多C语言、Linux系统、ARM板实战和数据结构相关文章,关注专栏:
手撕C语言
玩转linux
脚踢数据结构
6818(ARM)开发板实战
一键三连喔
~