1. 栈的定义

1.1 定义

栈是限定仅在表尾进行插入和删除操作的线性表。
栈顶:把允许插入和删除的一端称为栈顶。
栈底:把和栈顶对应的另一端称为栈底。
空栈:不含任何数据元素的栈称为空栈。
栈的特性:先进后出,也叫做后进先出。
栈是一种特殊的线性表,栈内元素具有线性关系,即前驱和后继关系,定义中所说的仅在表尾进行插入和删除操作,即在栈顶进行插入和删除,而不是栈底。

1.2 插入与删除概念

栈的插入操作,叫做进栈,也叫压栈、入栈,如下图所示:
入栈示意图.png
栈的删除操作,叫做出站,也叫弹栈,如下图所示:
出栈示意图.png

2. 栈的顺序存储结构

2.1 栈的顺序存储结构定义

栈是特殊的线性表,如果采用顺序存储结构,我们称这种栈为顺序栈,我们用数组来实现顺序存储。

在顺序存储结构中,如果在数组的头部位置进行插入和删除,会涉及到后续元素的移位工作,所以我们在数组的末尾进行插入和删除操作,会更加便利,因此我们将数组的头部(即下标为0的位置)称为栈底,数组的尾部称为栈顶。

我们定义top指针(这里的指针并非实际意义上的指针,只是一种称呼)用来记录栈顶元素在数组中的位置,若栈的长度为MAXSIZE,那么栈顶位置top必须小于MAXSIZE,当栈存在一个元素时,top等于0,若为空栈,则top等于-1。

顺序栈的结构定义

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 20 /* 存储空间初始分配量 */

typedef int Status;
typedef int SElemType; /* SElemType类型根据实际情况而定,这里假设为int */

/* 顺序栈结构 */
typedef struct
{
    SElemType data[MAXSIZE];
    int top; /* 用于指向栈顶位置 */
}SqStack;

2.2 顺序栈初始化

顺序栈的结构比较简单,我们使用一段地址连续的存储单元来存储数据,所以在创建栈对象的时候,即申请了存储空间,因此初始化只需要设置初始值即可。

代码实现

//构建一个空栈S
Status InitStack(SqStack *S){
    // 若栈不存在,返回error。
    if (S == NULL) {
        return ERROR;
    }
    // 设置top为-1,此时为空栈。
    S->top = -1;
    return OK;
}

2.3 顺序栈进栈操作——插入元素

进栈即为在栈顶插入元素。
思路

  1. 判断栈是否已满,如果满了,则无法插入数据,返回error。
  2. 将栈顶指针top++。
  3. 将新插入的元素赋值给栈顶空间。
  4. 返回OK。

代码实现

// 插入元素e为新栈顶元素
Status PushData(SqStack *S, SElemType e){
    //栈已满
    if (S->top == MAXSIZE -1) {
        return ERROR;
    }
    //栈顶指针+1;
    S->top ++;
    //将新插入的元素赋值给栈顶空间
    S->data[S->top] = e;
    return OK;
}

2.4 顺序栈出栈操作——删除元素

思路

  1. 判断栈是否为空栈,如果是空栈,则无法进行操作,返回error。
  2. 将删除元素赋值给e,并带回调用函数。
  3. 将栈顶指针top--。
  4. 返回OK。

代码实现

// 删除S栈顶元素,并且用e带回
Status Pop(SqStack *S,SElemType *e){
    //空栈,则返回error;
    if (S->top == -1) {
        return ERROR;
    }
    //将要删除的栈顶元素赋值给e
    *e = S->data[S->top];
    //栈顶指针--;
    S->top--;
    return OK;
}

2.5 顺序栈置空与判断

置空
将栈置空,我们不需要将顺序栈的元素都清空,只需要修改top指针即可。

代码实现

// 将栈置空
Status ClearStack(SqStack *S){
    S->top = -1;
    return OK;
}

判断
上面我们说过,当栈的top指针为-1的时候,则栈为空栈。

代码实现

// 判断顺序栈是否为空;
Status StackEmpty(SqStack S){
    if (S.top == -1)
        return TRUE;
    else
        return FALSE;
}

2.6 获取顺序栈栈顶元素

获取顺序栈栈顶元素即获取数组最后一个元素。

代码实现

// 获取栈顶
Status GetTop(SqStack S,SElemType *e){
    if (S.top == -1)
        return ERROR;
    else
        *e = S.data[S.top];
    return OK;
}

2.7 遍历顺序栈元素

遍历顺序栈元素即遍历数组的每个元素。

代码实现

// 从栈底到栈顶依次对栈中的每个元素打印
Status StackTraverse(SqStack S){
    int i = 0;
    printf("此栈中所有元素");
    while (i<=S.top) {
        printf("%d ",S.data[i++]);
    }
    printf("\n");
    return OK;
}

3. 栈的链式存储结构

3.1 栈的链式存储结构定义

上面我们了解的栈的顺序存储结构,那么如果栈用链表结构进行存储,即为栈的链式存储结构,我们成为链式栈,或链栈。

在链式栈中,我们采用单链表的形式存储栈的每个元素,那么我们如何定义栈顶和栈底呢,如果将单链表的表尾当做栈顶,那么每次插入和删除的时候,我们则需要遍历找到尾部元素,这样很麻烦,每个链表都有自己的头指针,因此,将链表的头部当做栈顶,我们只需要修改一下链表元素前后关系即可。

链式栈中,我们只会在链表的头部进行操作,所以链表的头结点的意思荡然无存,因此链表的第一个元素即为首元结点。

顺序栈中,我们需要考虑栈的溢出情况,因为我们每次申请的空间是固定的,但是在链式栈中,无需考虑溢出情况,除非计算机的内存不够了。

在链式栈中,当top指针为NULL的时候,则认为这是一个空链式栈。

链式栈的结构定义

/* 链式栈结点结构 */
typedef struct StackNode
{
    SElemType data;
    struct StackNode *next;
}StackNode,*LinkStackPtr;

/* 链式栈结构 */
typedef struct
{
    LinkStackPtr top;
    int count;
}LinkStack;

链式栈示意图
链式栈示意图.png

3.2 链式栈初始化

链式栈中没有采用头结点,因此初始化时只需要设置初始值,不需要开辟空间。

代码实现

/* 构造一个空栈S */
Status InitStack(LinkStack *S)
{
    S->top=NULL;
    S->count=0;
    return OK;
}

3.3 链式栈进栈操作——插入元素

思路

  1. 创建新增的结点temp,并对新增结点进行赋值。
  2. 将新增结点temp的后继指向当前栈顶结点S->top。
  3. 修改栈顶指针S->top为temp。
  4. 栈元素数量加1。
  5. 返回OK。

链式栈插入元素.png

代码实现

/* 插入元素e到链栈S (成为栈顶新元素)*/
Status Push(LinkStack *S, SElemType e){
    //创建新结点temp
    LinkStackPtr temp = (LinkStackPtr)malloc(sizeof(StackNode));
    //赋值
    temp->data = e;
    //把当前的栈顶元素赋值给新结点的直接后继, 参考上图第①步骤;
    temp->next = S->top;
    //将新结点temp 赋值给栈顶指针,参考图例第②步骤;
    S->top = temp;
    S->count++;
    return OK;
}

3.4 链式栈出栈操作——删除元素

思路

  1. 若栈内已无元素,则无法删除元素,返回error。
  2. 将栈顶元素值赋值给e,并带回调用函数。
  3. 创建临时变量p指向栈顶元素。
  4. 将栈顶指针向下移动一位。
  5. 释放原栈顶元素p。
  6. 栈元素数量减1.
  7. 返回OK。

链式栈删除元素.png

代码实现

/* 若栈不为空,则删除S的栈顶元素,用e返回其值. 并返回OK,否则返回ERROR*/
Status Pop(LinkStack *S,SElemType *e){
    LinkStackPtr p;
    if ((*S).count == 0) {
        return ERROR;
    }
    //将栈顶元素赋值给*e
    *e = S->top->data;
    //将栈顶结点赋值给p,参考图例①
    p = S->top;
    //使得栈顶指针下移一位, 指向后一结点. 参考图例②
    S->top= S->top->next;
    //释放p
    free(p);
    //个数--
    S->count--;
    return OK;
}

3.5 链式栈置空与判断

置空

  1. 创建结点指针p,q,将p指向栈顶。
  2. 循环遍历链表,将p赋值给q,p指向下一个结点,释放q。
  3. 当遍历到末尾时,即链表所有结点都已经释放,设置栈元素数量为0.
  4. 返回OK。

代码实现

/* 把链栈S置为空栈*/
Status ClearStack(LinkStack *S){
    LinkStackPtr p,q;
    p = S->top;
    while (p) {
        q = p;
        p = p->next;
        free(q);
    }
    S->count = 0;
    return OK;
}

判断
判断是否为空栈,只需要判断count是否为0即可。

代码实现

/* 若栈S为空栈,则返回TRUE, 否则返回FALSE*/
Status StackEmpty(LinkStack S){
    if (S.count == 0)
        return TRUE;
    else
        return FALSE;
}

3.6 链式栈获取栈顶元素

获取链式栈栈顶元素即获取链表的第一个结点元素。

代码实现

/* 若链栈S不为空,则用e返回栈顶元素,并返回OK ,否则返回ERROR*/
Status GetTop(LinkStack S,SElemType *e){
    if(S.top == NULL)
        return ERROR;
    else
        *e = S.top->data;
    return OK;
}

3.7 遍历链式栈元素

遍历链式栈元素即遍历链表的每个元素。
代码实现

/* 遍历链栈*/
Status StackTraverse(LinkStack S){
    LinkStackPtr p;
    p = S.top;
    while (p) {
        printf("%d ",p->data);
        p = p->next;
    }
    printf("\n");
    return OK;
}

Daniel_Coder
7 声望1 粉丝