在前面的学习中,我们学习了最基本经典的线性表,和一些相关基本操作,而今天要学习的是一种比较特殊的性线表: 栈
栈是什么?
栈的性质
栈是一种特殊的线性表,它不仅具有普通线性表的性质(即元素类型相同,一对一,一头一尾),还具有另外一个重要性质:
Last In First Out
这是什么意思呢?
我们可以把栈想象成一个容器,里面的元素就是一个一个的盘子
这个容器的特点就是 第一个放进去的盘子最后才能拿出来,最后放进去的盘子,最先拿出来
这也就是栈的特点 第一个进来 最后一个出去
栈的基本操作
栈的最基本操作只有两种: 入栈(Push)和出栈(Pop)
相当于线性表只能在末尾插入,删除只能从头删除
当入栈顺序为 A B C时,出栈顺序可以有哪几种呢?
1.ABC (A进A出,B进B出,C进C出)
2.ACB(A进A出,B进C进,C出B出)
3.BAC(A进B进,B出A出,C出)
4.CBA(A进B进C进C出B出A出)
5.BCA(A进B进B出,C进C出A出) //这个难想一点
怎么能写的不重不漏呢?可以借助树状图
接下来我们依旧从存储结构入手,分别讲栈的顺序存储和链式存储
栈的顺序表示: 顺序栈
顺序栈,即用一个数组来表示。在之前的线性表学习中,我们知道使用顺序存储时,需要用一个结构体去储存表的基本信息(首元素地址,表长,表的容量)。
在栈中,这些都没有变化。首元素地址是指栈底元素,表长有了一个新的表达方式 :top
top = 0 时,代表这是个空栈,没有任何元素;每有一个元素被压入栈中,top++;每有一个元素被弹出栈时,top--。
top代表了栈中的元素个数 (有些版本中top从-1开始,top代表栈顶元素的下标)
基本操作
栈的定义
#define INITSIZE 100
typedef struct {
int* base;//栈底元素的地址
int top; //栈顶指针,实际上是一个数
int maxSize; //栈的容量
}sqStack;
初始化空栈
void initStack(sqStack* S) {
S->base = (int*)malloc(INITSIZE * sizeof(int));
if (!S->base) {
printf("Overflow!"); exit(1);
}
S->top = 0;
S->maxSize = INITSIZE;
}
获取长度
int getLen(sqStack* S) {
return S->top;
}
获取栈顶元素
int getTop(sqStack* S, int* x) {
if (S->top == 0) return 0; //注意检查栈是否为空
*x = S->base[S->top -1];//注意栈中有top个元素时,栈顶元素下标为top-1
return 1;
}
元素进栈(push)
int push(sqStack* S, int x) {
//先检查栈是否已满,如果满了则重新分配空间
if (S->top == S->maxSize) {
S->base = (int*)realloc(S->base, (S->maxSize + 1) * sizeof(int));
if (!S->base) {
printf("Overflow!"); exit(1);}
S->maxSize++;
}
S->base[S->top++] = x;//先放元素,top后++
return 1;
}
元素出栈
int pop(sqStack* S, int* x) {
//先检查栈是否为空
if (S->top == 0) {
return 0;
}
*x = S->base[--S->top];
return 1;
}
解释一下*x = S->base[--S->top]:
比如说当栈里有五个元素时,top = 5; 那么栈顶元素下标为4 ,--top正好可以带回被弹出的元素的值,并且修改弹出元素后的top值
判断栈是否为空
int emptyStack(sqStack* S) {
return (S->top == 0 ? 1 : 0);
}
输出栈的元素
void outputStack(sqStack* S) {
int i = 0;
while (i < S->top) {
printf("%d ", S->base[i]);
i++;
}
}
两栈共享空间
当内存受限,可以通过共享内存来节约空间。将两个栈的栈底设置到数组的两端,让它们的栈顶向数组中间延长。当两个栈的栈顶相遇时,才会发生“上溢”。
在定义栈的时候,使用两个指针top1 top2来分别代表两个栈的栈顶就行
基本操作会有一点点变化
(1)向以数组末端的为栈底的栈压数据时top--,出栈时++
(2)判断栈是否会溢出是看两个栈的top是否相同
typedef struct {
int capacity;
int *data;
int top1;
int top2;
} TwoStacks;
TwoStacks* createStacks() {
int capacility = 50; //假设初始大小为50
TwoStacks* stacks = (TwoStacks*)malloc(sizeof(TwoStacks));
stacks->capacity = capacity;
stacks->data = (int*)malloc(capacity * sizeof(int));
stacks->top1 = 0; // 栈1的栈顶指针
stacks->top2 = capacity - 1; // 栈2的栈顶指针
return stacks;
}
void push1(TwoStacks* stacks, int value) {
if (stacks->top1 + 1 == stacks->top2) {
printf("Stack 1 overflow\n");
return;
}
stacks->data[stacks->top1 ++] = value;
}
void push2(TwoStacks* stacks, int value) {
if (stacks->top2 - 1 == stacks->top1) {
printf("Stack 2 overflow\n");
return;
}
stacks->data[stacks->top2--] = value;
}
int pop1(TwoStacks* stacks) {
if (stacks->top1 == 0) {
printf("Stack 1 underflow\n");
exit(1);
}
return stacks->data[--stacks->top1];
}
int pop2(TwoStacks* stacks) {
if (stacks->top2 == stacks->capacity - 1) {
printf("Stack 2 underflow\n");
exit(1);
}
return stacks->data[++stacks->top2];
}
栈的链式表示: 链栈
和顺序栈的不同之处在于:
1.链栈没有最大容量的限制
2.只在表头操作: 即 头插法压入元素,从头弹出元素;
3.top指向链表的首结点代表栈顶;bottom指向链表的尾结点代表栈底。
基本操作
基本操作也很简单 我就不详细写了,直接上代码
typedef struct node {
int data;
struct node* next;
}linkStack;
linkStack* initStack() {
linkStack* S = (linkStack*)malloc(sizeof(linkStack));
if (!S) exit(1);
S->next = NULL;
return S;
}
int push(linkStack* S,int x) {
//为新元素申请结点
linkStack* m = (linkStack*)malloc(sizeof(linkStack));
if (!m) return 0;
m->data = x;
//头插
m->next = S->next;
S->next = m;
return 1;
}
int pop(linkStack* S, int* x) {
//栈为空,弹栈失败
if (!S->next) return 0;
linkStack* m;
m = S->next; //栈顶地址
*x = m->data; //带回栈顶元素的值
//删除栈顶元素的结点
S->next = m->next;
free(m);
return 1;
}
可以补两句关于如何摧毁一个链栈 如果是顺序栈的话 直接将首元素地址free就行了
但是 摧毁链栈必须要摧毁所有的结点,因为每一个结点后面都储存了下一个结点的位置,如果只释放头结点,后续结点不会被释放,会导致内存泄漏
int destroy(linkStack* S) {
linkStack* m, * n; //两个指针一前一后遍历链表
m = S; n = S->next;
while (m) {
free(m);
m = n;
n = n->next;
}
free(n);
}
在这节中,我们学习了栈的基本性质,从顺序存储和链式存储两个角度分别写了一些基本操作的代码。如果有什么问题,欢迎在评论区批评指正!