【学习记录】栈 202004010

305 阅读3分钟

堆栈

堆栈都是一种数据项按序排列的数据结构,只能在一端(称为栈顶)对数据项进行插入和删除。

特殊的操作使得堆栈中的元素满足 LIFO (后进先出),如图所示:

本文所涉及的代码在这


堆栈可以使用顺序存储结构和链式存储结构来实现。

顺序结构 实现

堆栈的顺序存储实现借助了数组,同时引入了 top 来标志栈内元素的个数,当元素为空时,一般地,我们使用 top = -1 来表示。

初始化

使用数组来存储堆栈数据。同时使用 top 标志堆栈的空状态。


/// 构建一个空栈
/// @param s 结果带回
Status stackInit(Stack *s){
    s->top = -1;
    return SUCCESS;
}

/// 清空一个栈,顺序存储空间不能释放,将所有位置都标记为未使用就是清空。
/// @param s 结果带回
Status stackClear(Stack *s){
    s->top = -1;
    return SUCCESS;
}


/// 判断栈是否为空
/// @param s 栈
Status stackIsEmpty(Stack s){
    return -1 == s.top ? TRUE : FALSE;
}

堆栈的插入又叫做压栈入栈,它只能在栈顶进行。


/// 压栈
/// @param s 堆栈
/// @param e 新元素
Status stackPush(Stack *s, ElementType e){
    if (MAXSIZE == stackGetCount(*s)) {
        printf("栈已经满了!\n");
        return ERROR;
    }else{
//        栈顶 +1 ,并且将新的数据放入堆栈。
        s->data[++s->top] = e;
    }
    return SUCCESS;
}

堆栈的删除又叫做弹栈或者出栈,它只能在栈顶进行操作。


/// 弹栈
/// @param s 栈
/// @param e 元素值带回
Status stackPop(Stack *s, ElementType *e){
    if (-1 == s->top) {
        printf("栈已经空了!\n");
        return ERROR;
    }else{
//        取出当前元素带回,栈顶 -1。
        *e = s->data[s->top--];
    }
    return SUCCESS;
}

链表结构 实现

链式堆栈的机构如图所示:

初始化

本部分还实现了一些辅助型的功能,例如,栈的遍历和判空等。


/// 初始化堆栈
/// @param s 堆栈
Status stackInit(ChainStack *s){
    s->top = NULL;
    s->count = 0;
    return SUCCESS;
}


/// 清空堆栈 清空堆栈的时候需要将栈内的结点都释放掉。
/// @param s 堆栈
Status stackClear(ChainStack *s){
    StackNodePtr p, temp;
    
    p = s->top;
    while (p) {
        temp = p;
        p = p->next;
        free(temp);
    }
    s->count = 0;
    s->top = NULL;
    return SUCCESS;
}


/// 判断堆栈是否为空
/// @param s 堆栈
Status stackIsEmpty(ChainStack s){
    return 0 == s.count;
}


/// 获取堆栈的长度
/// @param s 堆栈
Status stackGetCount(ChainStack s){
    return s.count;
}


/// 遍历输出堆栈
/// @param s 堆栈
Status stackPrint(ChainStack s){
    StackNodePtr p;
    
    p = s.top;
    
    printf("链表信息:");
    while (p) {
        printf("%d, ", p->data);
        p = p->next;
    }
    printf("\n\n");
    
    return SUCCESS;;
}

压栈

压栈操作需要注意的是对于top指针的重新定位:


/// 压栈
/// @param s 堆栈
/// @param e 入栈元素
Status stackPush(ChainStack *s, ElementType e){
    
    StackNodePtr k;
    k = (StackNodePtr)malloc(sizeof(StackNode));
    k->data = e;
    
    k->next = s->top;
    s->top = k;
    s->count++;
    
    return SUCCESS;
}


弹栈

弹栈操作需要将top指针重新定位,还要将出栈结点释放,将值带回:


/// 弹栈
/// @param s 堆栈
/// @param e 出栈元素带回
Status stackPop(ChainStack *s, ElementType *e){
    
    if (!stackGetCount(*s)) {
        printf("堆栈已经空了!");
        return ERROR;
    }
    
    StackNodePtr k;
    
    k = s->top;
    *e = k->data;
    s->top = s->top->next;
    free(k);
    
    return SUCCESS;
}


写在最后

栈的应用不仅仅是在于数据结构上的应用,在许多问题的算法解决上堆栈的FIFO 思想都贯穿其中,例如,在程序运行时,函数的嵌套调用时,运行现场以及临时变量的保留底层都是使用了堆栈的思想。一些算法题:例如括号匹配,字符串的匹配都可以使用堆栈的思想来解决。

递归

函数的递归调用就是用到了堆栈来保留现场,所以递归是是分浪费空间的。

理论上,所有的递归都能改成循环来实现。

下⾯面3种情况下,我们会使⽤用到递归来解决问题:

  • 定义是递归的
  • 数据结构是递归的
  • 问题的解法是递归的