前言 栈本质上来看也是线性表,但它是操作受限的线性表。所谓操作受限,顾名思义,在线性表中,我们可以在头部、尾部和中间对线性表进行插入与删除操作;但是在栈中,规定了只能在表的尾部进行插入与删除。因此栈的操作比一般线性表的操作少了不少,但是实用性却十分广泛。

在栈中,表的尾部称为栈顶,表的头部则称为栈底,不含任何元素的空表称为空栈。

顺序栈的初始化 需要明确的是,顺序栈是用一组地址连续的存储单元依次存放从栈底到栈顶的数据元素。因此在设计栈的构造时,需要base一个指针来指向这片连续的存储单元,它同时也是栈底元素所在的位置,始终不变。还需要附设一个top指针指示栈顶元素所在的位置,再附设一个capacity值来表示栈的容量大小。由于是在顺序结构中,top指针可以直接看作数组的下标,因此直接使用int类型即可。

这样,我们初始化设计的顺序栈,需要经过三个步骤

申请一块连续的内存空间,并将base指针指向它 设置top的初值 设置capacity的初值 初始化结束后,可以得到如下的结构:

当然,这个结构并不是唯一的。在有些资料中,使用了数组来代替base指针,这本质上没有区别;或者不设置capacity值,这也无伤大雅;有的top初值赋为-1,这样会在实现插入和删除操作时有些变化,但只要按栈的概念来实现即可。

顺序栈的插入与删除 前面提到,栈插入和删除的位置是被规定的,只能在表的尾部(也就是栈顶)进行插入与删除,这样,一般称栈的插入操作为入栈,删除操作为出栈。这也就有了栈的特性:先进后出,或者说是后进先出。也就是说,第一个入栈的元素永远被压在最底端,当它上方还有元素时,是无法出栈的;它最先入栈,却只能最后出栈。

这样说明,栈的出入顺序是有迹可循的,比如" 1 "注定会在最后一个出栈(它之后没有其他元素进栈的前提下),假设栈中的元素出栈后不再进栈,那么越接近栈底的元素其出栈次序一定在越接近栈顶的元素之后。在这里不仔细讨论。

前面有说过,在初始化栈时,有些资料会将top值设为0,有些则设为-1;这样会在代码的具体实现时有些差异。当top值初始化为0时,它所指向的是当前可以插入的位置,因此要进行插入,只需要把数据存入结点中,再将top值加一;而top值初始化为-1时,需要先将其加一,再把数据存入结点中。

这样,在判断栈满栈空时也会有差异,自行注意即可。

由于是在顺序结构中,我们要删除某个结点就不需要释放掉该结点的内存空间了,只需要将top指针回溯,就可以当做把这个空间回收。当下次有元素入栈时,会直接将原来的数据覆盖掉。当然,这样对原

来的数据而言似乎不太安全,因此可以在指针回溯之前把它改成一个初值。假如结点的元素类型是指针类型,可以设置为null,本文中是int型,就直接设为0了。

增配内存空间 但凡是顺序存储结构,都需要考虑到空间的问题。初始化时设置的空间太大而实际上存储的数据很少,会造成内存空间的浪费;如果初始化时设置的空间太小而实际存储的数据很多,就会遇到存不下的问题。因此就实用性而言,实现动态增配内存空间是必要的。

一般的操作是:在栈初始化时分配一个合适的容量大小,当栈的容量不够用时再去寻找另外的空间。由于我们希望增配完空间后,数据仍按原来的逻辑存放,因此需要调用relloc函数(关于malloc、relloc和calloc函数的区别可以自行去查询)。

总而言之,我们用一个newBase指针来保存新申请到的空间,由于调用的是relloc方法,假设原有空间之后仍有满足需求的内存,系统将直接在原有空间之后扩容,然后返回原有空间的基地址。假如原有空间之后没有足够内存,系统会去额外开辟一个空间,大小为(当前栈的容量个数+需要增配的容量个数)*存储的数据类型的大小;然后将原来的数据拷贝到这片区域,将原来的空间释放,返回新空间的地址。假如此时内存中没有这样的额外空间可以开辟了,说明内存空间申请失败,返回NULL,原有空间不会释放。

全部代码

SeqStack.h
#ifndef SeqStack_h
#define SeqStack_h
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#define ElemType int
//栈的初始大小
#define STACK_INIT_SIZE 8
//再次分配的大小
#define STACK_INC_SIZE 2
typedef struct SeqStack{
ElemType *base;
//栈容量
int capacity;
//表示栈顶所在的位置,也表示了当前栈内元素的个数
int top;
}SeqStack;
//初始化
void InitStack(SeqStack *s);
//增加栈空间 0->失败 !0->成功
int Inc(SeqStack *s);
//1.入栈
void Push(SeqStack *s,ElemType x);
//2.出栈
void Pop(SeqStack *s);
//3.展示栈
void Show(SeqStack s);
void Show2(SeqStack *s);
//4.获取栈顶元素
void GetTop(SeqStack s,ElemType *x);
void GetTop2(SeqStack *s,ElemType *x);
//5.获取当前元素个数
int GetLength(SeqStack s);
int GetLength2(SeqStack *s);
//6.清除栈
void Clear(SeqStack *s);
//7.摧毁栈
void Destory(SeqStack *s);
//判断栈是否已满 0->未满  !0->已满
int IsFull(SeqStack *s);
//判断栈是否已空 0->未空  !0->已空
int IsEmpty(SeqStack *s);