1.基本概念
栈是一种逻辑结构,是特殊的线性表,特殊在只能在固定一端操作
只要满足上述条件,那么这种特殊的线性表就会呈现出一种"后进先出"的逻辑,这种逻辑就被称为栈,栈在生活中到处可见,比如堆叠的盘子、电梯中的人等等。
由于约定了只能在线性表固定的一端进行操作,于是给栈这种特殊的线性表的"插入"、“删除”,另起了下面这些特殊的名称:
-
栈顶 : 可以进行插入删除的一端
-
栈底:栈顶的对端
-
入栈: 将节点插入栈顶之上,也称为压栈,函数名通常为push()
-
出栈:将节点从栈顶剔除,也称为弹栈,函数名通常额外pop()
-
取栈顶: 取得栈顶元素,但不出栈,函数名通常为top()
基于这种固定一端操作的简单约定,栈获得了"后进先出"的基本特征,如图所示,最后一个放入的元素,最先被拿出来:
2.存储方式
栈只是一种数据逻辑,如何将数据存储于内存则是另外一回事,一般而言,可以采用顺序存储形成顺序栈,或者采用链式存储形成链式栈。
1.顺序栈
顺序存储意味着开辟一块连续的内存用于存储数据节点,一般而言,管理栈数据除了需要一块连续的内存之外,还需要记录栈的总容量、当前栈的元素个数、当前栈顶元素位置,如果有多线程还需要配合互斥锁和信号量等信息,为了方便管理,通常将这些信息统一于一个管理结构体中;
struct seqStack
{
datatype *data; // 顺序栈入口
int size; // 顺序栈总容量
int top; // 顺序栈栈顶元素下标
};
2.链式栈
链式栈的组织形式与链表无异,只不过插入删除被约束在固定的一端。为了便于操作,通常也会创建所谓管理结构体,用来存储栈顶指针、栈元素个数等信息:
// 链式栈节点
typedef struct node
{
datatype data;
struct node *next;
}node;
// 链式栈管理结构体
struct linkStack
{
node *top; // 链式栈栈顶指针
int size; // 链式栈当前元素个数
};
3.基本操作
不管是顺序栈,链式栈,栈的操作逻辑都是一样的,但由于存储形式不同,代码的实现是不同的。下面分别将顺序栈和链式栈的基本核心操作罗列出来:
1.顺序栈
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef int dataType;
typedef struct seqStack
{
dataType *data; // 顺序栈入口
int size; // 顺序栈总容量
int top; // 顺序栈栈顶元素下标
} seqStack;
// 初始化空栈
seqStack *initStack(int size)
{
seqStack *stack = (seqStack *)malloc(sizeof(seqStack));
if (stack != NULL)
{
stack->data = (dataType *)malloc(sizeof(dataType) * size);
if (stack->data == NULL)
{
free(stack);
return NULL;
}
stack->size = size;
stack->top = -1;
}
return stack;
}
// 判断栈是否已满
bool isFull(seqStack *s)
{
return s->top == s->size - 1;
}
// 判断栈是否为空
bool isEmpty(seqStack *s)
{
return s->top == -1;
}
// 入栈
bool push(seqStack *s, dataType data)
{
if (isFull(s))
return false;
s->data[++s->top] = data;
return true;
}
// 取栈顶元素
bool top(seqStack *s, dataType *pm)
{
if (isEmpty(s))
return false;
*pm = s->data[s->top];
return true;
}
// 出栈
bool pop(seqStack *s, dataType *pm)
{
if (top(s, pm) == false)
return false;
s->top--;
return true;
}
int main(int argc, char const *argv[])
{
// 初始化栈
seqStack *s = initStack(10);
if (s == NULL)
{
perror("init stack failed:");
return -1;
}
//入栈
push(s,1);
push(s,2);
push(s,3);
push(s,4);
dataType data;
//弹栈
while (s->top != -1)
{
bool ret = pop(s,&data);
if(!ret)
{
printf("栈为空!\n");
continue;
}
printf("%d\t",data);
}
printf("\n");
// 释放内存
free(s->data);
free(s);
return 0;
}
「课堂练习1」
使用顺序栈,接收键盘的输入,实现如下功能:
-
输入数字时,依次入栈。
-
输入字母时,依次出栈。
-
每次入栈或者出栈,都将顺序栈中的各个元素输出出来。
#include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include <ctype.h> // isdigit() 函数 isalpha() 函数 typedef int dataType; typedef struct seqStack { dataType *data; // 顺序栈入口 dataType size; // 顺序栈总容量 dataType top; // 顺序栈栈顶元素下标 } seqStack; // 初始化空栈 seqStack *initStack(int size) { seqStack *stack = (seqStack *)malloc(sizeof(seqStack)); if (stack != NULL) { stack->data = (dataType *)malloc(sizeof(dataType) * size); if (stack->data == NULL) { free(stack); return NULL; } stack->size = size; stack->top = -1; } return stack; } // 判断栈是否已满 bool isFull(seqStack *s) { return s->top == s->size - 1; } // 判断栈是否为空 bool isEmpty(seqStack *s) { return s->top == -1; } // 入栈 bool push(seqStack *s, dataType data) { if (isFull(s)) return false; s->data[++s->top] = data; return true; } // 取栈顶元素 bool top(seqStack *s, dataType *pm) { if (isEmpty(s)) return false; *pm = s->data[s->top]; return true; } // 出栈 bool pop(seqStack *s, dataType *pm) { if (top(s, pm) == false) return false; s->top--; return true; } // 输出栈中的所有元素 void show_stack(seqStack *s) { if(isEmpty(s)) { printf("空栈!\n"); return; } for (int i = s->top; i > -1; i--) { printf(" %d",s->data[i]); if( i == s->top) { printf("<——栈顶"); printf("\n"); } } printf("------------------\n"); printf("总计%d个元素\n",s->top+1 ); } int main(int argc, char const *argv[]) { // 初始化栈 seqStack *s = initStack(10); if (s == NULL) { perror("init stack failed:"); return -1; } char input; printf("请输入数字(0-9)进行入栈,输入字母(a-z或A-Z)进行出栈,输入'q'退出程序:\n"); while (1) { scanf(" %c",&input); if(input == 'q') break; else if(isdigit(input)) //如果输入数字,入栈 { int num = input - '0'; if(!push(s,num)) printf("栈已满!无法入栈~\n"); } else if(isalpha(input)) //输入字母,出栈 { dataType data; if(!pop(s,&data)) printf("栈已空!无法出栈~\n"); } else printf("无效的输入!请输入字母或者字符~\n"); show_stack(s); } // 释放内存 free(s->data); free(s); return 0; }
2.链式栈
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int dataType;
// 链式栈节点
typedef struct node
{
dataType data;
struct node *next;
}node;
// 链式栈管理结构体
typedef struct LinkStack
{
node *top; //链式栈 栈顶指针
int size; //链式栈当前元素个数
}LinkStack;
//初始化空栈
LinkStack *init_stack(void)
{
LinkStack *s = (LinkStack*)malloc(sizeof(LinkStack));
if(s != NULL)
{
s->top = NULL;
s->size = 0;
}
return s;
}
//判断栈是否为空
bool isEmpty(LinkStack *s)
{
return (s->size == 0);
}
//入栈
bool push(LinkStack *s,dataType data)
{
//创建链表节点
node *new = (node*)malloc(sizeof(node));
if(new == NULL)
return false;
new->data = data;
//将节点置入栈顶
new->next = s->top;
s->top = new;
//更新栈元素个数
s->size++;
return true;
}
//出栈
bool pop(LinkStack *s,dataType *data)
{
if(isEmpty(s))
return false;
node *tmp = s->top;
//将原栈顶元素剔除出栈
s->top = tmp->next;
tmp->next = NULL;
// 返回栈顶元素,并释放节点
*data = tmp->data;
free(tmp);
//更新栈元素个数
s->size--;
return true;
}
// 取栈顶元素
bool top(LinkStack *s, dataType *pm)
{
if(isEmpty(s))
return false;
// 返回栈顶元素,并释放节点
*pm = s->top->data;
return true;
}
void show(LinkStack *s)
{
if(isEmpty(s))
return ;
node *p = s->top;
int n = s->size;
while (n--)
{
printf("%d\t",p->data);
p =p->next;
}
printf("\n");
}
int main(int argc, char const *argv[])
{
//初始化栈
LinkStack *s = init_stack();
if(s == NULL)
{
perror("init stack failed:");
return -1;
}
//入栈
push(s,1);
push(s,2);
push(s,3);
//取栈顶元素
dataType data;
if(top(s,&data))
printf("成功!取到栈顶元素%d \n",data);
else
printf("失败!取了个寂寞!\n");
show(s);
//出栈
if(pop(s,&data))
printf("成功!出栈元素%d \n",data);
else
printf("失败!吊都没出来!\n");
show(s);
return 0;
}
「课堂练习2」
-
使用链式栈,实现十进制转八进制:键盘输入一个十进制数,经过链式栈的相关算法,输出八进制数。
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef int dataType;
// 链式栈节点
typedef struct node
{
dataType data;
struct node *next;
} node;
// 链式栈管理结构体
typedef struct LinkStack
{
node *top; // 链式栈 栈顶指针
int size; // 链式栈当前元素个数
} LinkStack;
// 初始化空栈
LinkStack *init_stack(void)
{
LinkStack *s = (LinkStack *)malloc(sizeof(LinkStack));
if (s != NULL)
{
s->top = NULL;
s->size = 0;
}
return s;
}
// 判断栈是否为空
bool isEmpty(LinkStack *s)
{
return (s->size == 0);
}
// 入栈
bool push(LinkStack *s, dataType data)
{
// 创建链表节点
node *new = (node *)malloc(sizeof(node));
if (new == NULL)
return false;
new->data = data;
// 将节点置入栈顶
new->next = s->top;
s->top = new;
// 更新栈元素个数
s->size++;
return true;
}
// 出栈
bool pop(LinkStack *s, dataType *data)
{
if (isEmpty(s))
return false;
node *tmp = s->top;
// 将原栈顶元素剔除出栈
s->top = tmp->next;
tmp->next = NULL;
// 返回栈顶元素,并释放节点
*data = tmp->data;
free(tmp);
// 更新栈元素个数
s->size--;
return true;
}
// 取栈顶元素
bool top(LinkStack *s, dataType *pm)
{
if (isEmpty(s))
return false;
// 返回栈顶元素,并释放节点
*pm = s->top->data;
return true;
}
//显示
void show(LinkStack *s)
{
if (isEmpty(s))
{
printf("栈为空\n");
return;
}
node *p = s->top;
while (p != NULL)
{
printf("%d", p->data);
p = p->next;
}
printf("\n");
}
// 销毁栈,释放内存
void destroy_stack(LinkStack *s)
{
node *p = s->top;
while (p != NULL)
{
node *temp = p;
p = p->next;
free(temp);
}
free(s);
}
int main(int argc, char const *argv[])
{
// 初始化栈
LinkStack *s = init_stack();
if (s == NULL)
{
perror("初始化栈失败!");
return -1;
}
printf("请输入一个十进制整数: ");
int n,num;
if (scanf("%d", &n) != 1)
{
printf("输入无效\n");
// 清除输入缓冲区中的残余数据
while (getchar() != '\n');
destroy_stack(s);
return -1;
}
num = n; // 记录原始输入值
// 十进制转八进制
if (n == 0)
{
push(s, 0);
}
else
{
while (n > 0)
{
int remain = n % 8;
push(s, remain); //余数入栈
n /= 8;
}
}
// 显示转换结果
printf("%d转换后的八进制数为",num);
show(s);
// 销毁栈
destroy_stack(s);
return 0;
}
perror("初始化栈失败!");
return -1;
}
printf("请输入一个十进制整数: ");
int n,num;
if (scanf("%d", &n) != 1)
{
printf("输入无效\n");
// 清除输入缓冲区中的残余数据
while (getchar() != '\n');
destroy_stack(s);
return -1;
}
num = n; // 记录原始输入值
// 十进制转八进制
if (n == 0)
{
push(s, 0);
}
else
{
while (n > 0)
{
int remain = n % 8;
push(s, remain); //余数入栈
n /= 8;
}
}
// 显示转换结果
printf("%d转换后的八进制数为",num);
show(s);
// 销毁栈
destroy_stack(s);
return 0;
}