# 从零开始的数据结构与算法(六):栈

533 阅读6分钟

1 定义

栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。(度娘百科)

栈结构图

栈是一种中的数据结构,在做函数调用的时候存储断点,以及处理递归问题时都要用到栈。

在计算机系统中,栈则是一个具有以上属性的动态内存区域。在函数间进行互相调用时,程序将函数相关的变量环境信息压入栈中,也可以将数据从栈顶弹出。

在现实世界中,也有很多栈结构的体现。比如像枪械的弹匣就是一个标准的栈结构。装弹时子弹先后被压入弹匣,使用时后装入的子弹即顶部的子弹会先被射出,体现了先进后出的原则。

栈结构-弹匣

除此之外,经典的汉诺塔游戏也是根据栈结构规则设计的游戏。

汉诺塔

2 顺序存储栈

栈是一种逻辑结构,定义了先进后出的规则。在前面章节中讲到,任何逻辑结构在计算机中都是通过两种物理结构来存储:顺序存储和连式存储。两种物理结构都可以用来实现抽象的逻辑结构,并且各有优劣。首先介绍如何使用顺序存储的方式实现栈结构。

2.1 结构定义

/// 设置栈的长度
#define MAXSIZE 20
/// 栈结构
typedef struct {
    ElementType data[MAXSIZE];  // 顺序存储的数组来l保存栈的数据元素
    int top;                    // 栈顶下标
} Stack;

2.2 栈常用方法

/// 初始化栈
static Status stackInit(Stack *s) {
    // 数据不需要抹除,只需改变栈顶位置
    s->top = -1;
    return SUCCESS;
}

/// 清空栈
static Status stackClear(Stack *s) {
    // 同初始化栈,不用管数据
    s->top = -1;
    return SUCCESS;
}

/// 判断栈是否为空
static int stackIsEmpty(Stack s) {
    if (s.top == -1) {
        return 1;
    }
    return 0;
}

/// 获取栈的长度
static int stackLength(Stack s) {
    return s.top + 1;
}

/// 获取栈顶元素
static Status getTopElement(Stack s, ElementType *e) {
    if (s.top == -1) {
        return ERROR;
    }
    // 栈顶元素
    *e = s.data[s.top];
    return SUCCESS;
}

/// 遍历打印栈的数据元素
static Status stackTraverse(Stack s) {
    int i = 0;
    printf("Stack: ");
    while (i <= s.top) {
        printf("%d ", s.data[i]);
        i++;
    }
    printf("\n");
    return SUCCESS;
}

2.3 入栈

/// 数据元素入栈
static Status stackPush(Stack *s, ElementType e) {
    // 栈溢出判断
    if (s->top >= MAXSIZE - 1) {
        return ERROR;
    }
    // 改变栈顶位置
    s->top++;
    // 栈顶设置新的数据
    s->data[s->top] = e;
    return SUCCESS;
}

2.4 出栈

/// 数据元素出栈
static Status stackPop(Stack *s, ElementType *e) {
    // 空栈判断
    if (s->top < 0) {
        return ERROR;
    }
    // 返回数据元素的数据
    *e = s->data[s->top];
    // 改变栈顶位置
    s->top--;
    return SUCCESS;
}

2.5 使用

int main() {
    Stack s;
    printf("栈初始化\n");
    stackInit(&s);
    stackTraverse(s);
    if (stackIsEmpty(s)) {
        printf("\n栈是空的\n");
    } else {
        printf("\n栈不为空\n");
    }
    
    printf("\n构建栈\n");
    for (int i = 0; i < 6; i++) {
        ElementType element = i * 3 + 2;
        stackPush(&s, element);
    }
    stackTraverse(s);
    printf("栈的元素个数为 %d\n", stackLength(s));
    ElementType topE;
    getTopElement(s, &topE);
    printf("栈顶的元素数据为 %d\n", topE);

    ElementType e = 666;
    printf("\n入栈 ← %d\n", e);
    stackPush(&s, e);
    stackTraverse(s);

    ElementType e1;
    stackPop(&s, &e1);
    printf("\n出栈 → %d\n", e1);
    stackTraverse(s);
    
    ElementType e2;
    stackPop(&s, &e2);
    printf("\n出栈 → %d\n", e2);
    stackTraverse(s);

    printf("\n清空栈 x\n");
    stackClear(&s);
    stackTraverse(s);
    
    return 0;
}

打印结果:

栈初始化
[栈顶-栈底]Stack: 

栈是空的

构建栈
[栈顶-栈底]Stack: 2 5 8 11 14 17 
栈的元素个数为 6
栈顶的元素数据为 17

入栈 ← 666
[栈顶-栈底]Stack: 2 5 8 11 14 17 666 

出栈 → 666
[栈顶-栈底]Stack: 2 5 8 11 14 17 

出栈 → 17
[栈顶-栈底]Stack: 2 5 8 11 14 

清空栈 x
[栈顶-栈底]Stack: 

3 链式存储栈

3.1 结构定义

/*
/// 设置栈的长度
#define MAXSIZE 20
/// 顺序存储栈的结构
typedef struct {
    ElementType data[MAXSIZE];  // 顺序存储的数组来l保存栈的数据元素
    int top;                    // 栈顶下标
} Stack;
*/

/// 栈节点结构
/// next 指向先入栈的元素
struct StackNode {
    ElementType data;           // 数据域
    struct StackNode *next;     // 后继指针
};
typedef struct StackNode *StackNodePtr;

/// 链式存储栈的结构
typedef struct {
    StackNodePtr top;   // 栈顶数据元素
    int count;          // 栈的数量
} Stack;

链式存储相对于顺序存储的优势在于不受初始空间的限制,所以不需要设置 MAXSIZE。

首先定义栈中数据元素的结构 struct StackNode。可以根据需要使用双向链表来定义节点。

在节点的基础上定义栈的结构。相对于顺序存储栈的结构,链式存储栈保存的是栈顶元素与元素个数,要区别记忆。

3.2 栈常用方法

/// 初始化栈
static Status stackInit(Stack *s) {
    s->top = NULL;
    s->count = 0;
    return SUCCESS;
}

/// 清空栈
static Status stackClear(Stack *s) {
    StackNodePtr top = s->top;
    StackNodePtr temp;
    // 释放所有数据元素
    while (top) {
        temp = top;
        top = top->next;
        free(temp);
    }
    // 修改栈顶,避免野指针
    s->top = NULL;
    // 修改数量
    s->count = 0;
    return SUCCESS;
}

/// 判断栈是否为空
static int stackIsEmpty(Stack s) {
    if (s.count <= 0) {
        return 1;
    }
    return 0;
}

/// 获取栈的长度
static int stackLength(Stack s) {
    return s.count;
}

/// 获取栈顶元素
static Status getTopElement(Stack s, ElementType *e) {
    if (s.count <= 0) {
        return ERROR;
    }
    // 栈顶元素
    *e = s.top->data;
    return SUCCESS;
}

3.3 入栈

/// 数据元素入栈
static Status stackPush(Stack *s, ElementType e) {
    // 栈溢出判断
    if (s->top >= MAXSIZE - 1) {
        return ERROR;
    }
    // 改变栈顶位置
    s->top++;
    // 栈顶设置新的数据
    s->data[s->top] = e;
    return SUCCESS;
}

3.4 出栈

/// 数据元素出栈
static Status stackPop(Stack *s, ElementType *e) {
    // 空栈判断
    if (s->count <= 0) {
        return ERROR;
    }
    // 获取栈顶数据元素
    StackNodePtr top = s->top;
    // 释放前改变栈顶位置
    s->top = top->next;
    // 返回数据元素的数据
    *e = top->data;
    // 释放数据元素
    free(top);
    // 改变栈的数量
    s->count--;
    return SUCCESS;
}

2.5 使用

int main() {
    Stack s;
    printf("栈初始化\n");
    stackInit(&s);
    stackTraverse(s);
    if (stackIsEmpty(s)) {
        printf("\n栈是空的\n");
    } else {
        printf("\n栈不为空\n");
    }
    
    printf("\n构建栈\n");
    for (int i = 0; i < 6; i++) {
        ElementType element = i * 3 + 2;
        stackPush(&s, element);
    }
    stackTraverse(s);
    printf("栈的元素个数为 %d\n", stackLength(s));
    ElementType topE;
    getTopElement(s, &topE);
    printf("栈顶的元素数据为 %d\n", topE);

    ElementType e = 666;
    printf("\n入栈 ← %d\n", e);
    stackPush(&s, e);
    stackTraverse(s);

    ElementType e1;
    stackPop(&s, &e1);
    printf("\n出栈 → %d\n", e1);
    stackTraverse(s);
    
    ElementType e2;
    stackPop(&s, &e2);
    printf("\n出栈 → %d\n", e2);
    stackTraverse(s);

    printf("\n清空栈 x\n");
    stackClear(&s);
    stackTraverse(s);
    
    return 0;
}

打印结果:

栈初始化
[栈顶-栈底]Stack: 

栈是空的

构建栈
[栈顶-栈底]Stack: 17 14 11 8 5 2 
栈的元素个数为 6
栈顶的元素数据为 17

入栈 ← 666
[栈顶-栈底]Stack: 666 17 14 11 8 5 2 

出栈 → 666
[栈顶-栈底]Stack: 17 14 11 8 5 2 

出栈 → 17
[栈顶-栈底]Stack: 14 11 8 5 2 

清空栈 x
[栈顶-栈底]Stack: 

参考文章:十张GIFs让你弄懂递归等概念