基本数据结构 -- 栈详解

  栈是一种后进先出的线性表,是最基本的一种数据结构,在许多地方都有应用。

一、什么是栈

  栈是限制插入和删除只能在一个位置上进行的线性表。其中,允许插入和删除的一端位于表的末端,叫做栈顶(top),不允许插入和删除的另一端叫做栈底(bottom)。对栈的基本操作有 PUSH(压栈)POP (出栈),前者相当于表的插入操作(向栈顶插入一个元素),后者则是删除操作(删除一个栈顶元素)。栈是一种后进先出(LIFO)的数据结构,最先被删除的是最近压栈的元素。栈就像是一个箱子,往里面放入一个小盒子就相当于压栈操作,往里面取出一个小盒子就是出栈操作,取盒子的时候,最后放进去的盒子会最先被取出来,最先放进去的盒子会最后被取出来,这即是后入先出。下面是一个栈的示意图:

  

二、栈的实现

  由于栈是一个表,因此任何实现表的方法都可以用来实现栈。主要有两种方式,链表实现和数组实现。

2.1 栈的链表实现

  可以使用单链表来实现栈。通过在表顶端插入一个元素来实现 PUSH,通过删除表顶端元素来实现 POP。使用链表方式实现的栈又叫动态栈。动态栈有链表的部分特性,即元素与元素之间在物理存储上可以不连续,但是功能有些受限制,动态栈只能在栈顶处进行插入和删除操作,不能在栈尾或栈中间进行插入和删除操作。

  栈的链表实现代码如下,编译环境是 win10,vs2015:

#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "windows.h"

struct stack_node {
    int data;
    struct stack_node *next;
};

typedef struct stack_node *PtrToNode;
typedef PtrToNode Stack;

Stack create_stack();
void push_stack(Stack s, int data);
void pop_stack(Stack s);
int top_stack(Stack s);
int stack_is_empty(Stack s);

int main()
{
    Stack stack = create_stack();        // 新建一个空栈
    int top_data,i;
    // 压栈操作,执行10次
    for (i = 0;i < 10;i++) {
        push_stack(stack, i);
    }
    // 出栈操作,执行1次
    pop_stack(stack);
    // 返回栈顶元素的值
    top_data = top_stack(stack);
    printf("%d\n", top_data);

    system("pause");
}

/* 创建一个空栈 */
Stack create_stack()
{
    Stack S;

    S = (Stack)malloc(sizeof(struct stack_node));
    if (S == NULL)
        printf("malloc fair!\n");
    S->next = NULL;

    return S;
}

/* PUSH 操作 */
void push_stack(Stack s,int data)
{
    // 新建一个结点,用于存放压入栈内的元素,即新的栈顶
    PtrToNode head_node = (PtrToNode)malloc(sizeof(struct stack_node));
    if (head_node == NULL)
        printf("malloc fair!\n");

    head_node->data = data;            // 添加数据
    head_node->next = s->next;        // 新的栈顶 head_node 的 next 指针指向原来的栈顶 s->next
    s->next = head_node;            // s->next 现在指向新的栈顶
}

/* POP 操作 */
void pop_stack(Stack s)
{
    PtrToNode head_node = (PtrToNode)malloc(sizeof(struct stack_node));
    if (head_node == NULL)
        printf("malloc fair!\n");

    // 先判断栈是否为空,若栈为空,则不能再进行出栈操作,报错
    if (stack_is_empty(s)) {
        printf("Error! Stack is empty!\n");
    }
    else {
        head_node = s->next;            // head_node 为栈顶
        s->next = head_node->next;        // s->next 指向 head_node->next ,即新的栈顶
        free(head_node);                // 释放原来栈顶元素所占的内存
    }
}

/* 查看栈顶元素 */
int top_stack(Stack s)
{
    if (stack_is_empty(s)) {
        printf("Error! Stack is empty!\n");
        return 0;
    }
    else {
        return s->next->data;
    }
}

/* 判断栈是否为空 */
int stack_is_empty(Stack s)
{
    return s->next == NULL;
}

  该程序将数字 1-9 分别压栈,然后执行一次出栈操作,最后打印栈顶元素,结果为8。

2.2 栈的数组实现

  同样,栈也可以用数组来实现。使用数组方式实现的栈叫静态栈

  用数组实现栈很简单,每个栈都有一个 TopOfStack,用来表示栈顶在数组中的下标,对于空栈,该值为 -1(这就是空栈的初始化)。当需要压栈时,只需要将 TopOfStack 加 1,然后将数组中该下标处的值置为压入栈的值即可;出栈操作更简单,只需要将 TopOfStack 减 1 即可。需要注意的是,对空栈的 POP 操作和对满栈的 PUSH 操作都会产生数组越界并引起程序崩溃。

  栈的数组实现方法如下,编译环境是 win10,vs2015:

#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "windows.h"

#define MinStackSize 5
#define EmptyTOS -1

struct stack_array {
    int capacity;            // 栈的容量
    int top_of_stack;        // 栈顶的下标
    int *array;                // 用于存放栈的数组
};

typedef struct stack_array *ArrayRecord;
typedef ArrayRecord Stack;

Stack create_stack(int stack_capacity);
void make_empty(Stack s);
void push_stack(Stack s, int data);
int top_stack(Stack s);
void pop_stack(Stack s);
int stack_is_empty(Stack s);
int stack_is_full(Stack s);

int main()
{
    Stack stack = create_stack(100);
    int topdata, i;
    for (i = 0;i < 10;i++) {
        push_stack(stack, i);
    }
    pop_stack(stack);
    pop_stack(stack);
    topdata = top_stack(stack);
    printf("%d\n", topdata);

    system("pause");
}

/* 创建一个栈 */
Stack create_stack(int stack_capacity)
{
    Stack S;

    if (stack_capacity < MinStackSize)
        printf("Error! Stack size is too small!\n");

    S = (Stack)malloc(sizeof(struct stack_array));
    if (S == NULL)
        printf("malloc error!\n");

    S->array = (int *)malloc(sizeof(struct stack_array) * stack_capacity);
    if (S->array == NULL)
        printf("malloc error!\n");
    S->capacity = stack_capacity;

    make_empty(S);
    return S;
}

/* 创建一个空栈 */
void make_empty(Stack s)
{
    // 栈顶的下标为 -1 表示栈为空
    s->top_of_stack = EmptyTOS;
}

/* PUSH 操作 */
void push_stack(Stack s, int data)
{
    if (stack_is_full(s)) {
        printf("Error! Stack is full!\n");
    }
    else {
        s->top_of_stack++;
        s->array[s->top_of_stack] = data;
    }
}

/* POP 操作 */
void pop_stack(Stack s)
{
    if (stack_is_empty(s)) {
        printf("Error! Stack is empty!\n");
    }
    else {
        s->top_of_stack--;
    }
}

/* 返回栈顶元素 */
int top_stack(Stack s)
{
    if (stack_is_empty(s)) {
        printf("Error! Stack is empty!\n");
        return 0;
    }
    else {
        return s->array[s->top_of_stack];
    }
}

/* 检测栈是否为空栈 */
int stack_is_empty(Stack s)
{
    // 栈顶的下标为 -1 表示栈为空
    return s->top_of_stack == EmptyTOS;
}

/* 检测栈是否为满栈 */
int stack_is_full(Stack s)
{
    // 栈顶的下标为 capacity - 1 表示栈满了(数组下标从 0 开始)
    return s->top_of_stack == --s->capacity;
}

  该程序将数字 1-9 分别压栈,然后执行两次出栈操作,最后打印栈顶元素,结果为7。

2.3 栈的链表实现和数组实现的优缺点

  使用链表来实现栈,内存动态分配,可以不必担心内存分配的问题,但是 malloc 和 free 的调用开销会比较大。

  使用数组实现的栈,需要提前声明一个数组的大小,如果数组大小不够,则可能会发生数组越界,如果数组太大,则会浪费一定的空间。一般而言,会给数组声明一个足够大而不至于浪费太多空间的大小。除了这个问题,用数组实现的栈执行效率会比用链表来实现的高。

  这两种实现方式中,栈的操作如 PUSH、POP 均是以常数时间运行的,执行速度很快,因此,栈的执行效率通常很高。

三、栈的应用

  栈的应用十分广泛 ,在函数调用、中断处理、表达式求值、内存分配等操作中都需要用到栈。本文接下来描述一下栈在函数调用中的应用:

  假设有一个函数 f(),现在函数 f() 要调用函数 g() ,而函数 g() 又需要调用函数 h() 。当函数 f() 开始调用函数 g() 时,函数 f() 的所有局部变量需要由系统存储起来,否则被调用的新函数 g() 将会覆盖调用函数 f() 的变量;不仅如此,主调函数当前的位置也是需要保存的,以便被调函数执行完后知道回到哪里接着执行调用函数。同样的,函数 g() 调用函数 h() 时,g() 的相关信息也需要存储起来。在函数 h() 执行完成后,再从系统中取出函数 g()  的相关信息接着执行函数 g();当函数 g() 执行完成后,从系统中取出函数 f() 的相关信息然后接着执行函数 f()。从这里的描述中可以看到,函数调用时,调用函数的信息是存放在一个后进先出结构中的,显然,用栈来存放再好不过,用一幅图演示一下:

参考资料:

《算法导论 第三版》

《数据结构与算法分析--C语言描述》

原文地址:https://www.cnblogs.com/tongye/p/9687442.html

时间: 2024-11-01 00:15:37

基本数据结构 -- 栈详解的相关文章

Java性能分析之线程栈详解(下)

Java性能分析之线程栈详解(下) 转载自:微信公众号"测试那点事儿" 结合jstack结果对线程状态详解 上篇文章详细介绍了线程栈的作用.状态.任何查看理解,本篇文章结合jstack工具来查看线程状态,并列出重点关注目标.Jstack是常用的排查工具,它能输出在某一个时间,Java进程中所有线程的状态,很多时候这些状态信息能给我们的排查工作带来有用的线索. Jstack的输出中,Java线程状态主要是以下几种: 1.BLOCKED 线程在等待monitor锁(synchronized

Java堆和栈详解

Java把内存分成两种,一种叫做栈内存,一种叫做堆内存 在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配.当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为该变量分配的内存空间,该内存空间可以立刻被另作他用. 堆内存用于存放由new创建的对象和数组. 在堆中分配的内存,由java虚拟机自动垃圾回收器来管理.在堆中产生了一个数组或者对象后,还可以在栈中定义一个特殊的变量,这个变量的取值等于数组或 者对象在堆内

数据结构 重点详解

线性数据结构 线性表-顺序表 代码实现: #include <bits/stdc++.h> #define TRUE 1 #define FALSE 0 #define OK 1 #define ERROR 0 #define INFEASIBLE -1 #define OVERFLOW -2 #define LIST_INIT_SIZE 100 //线性表初始空间分配量 #define LISTINCREMENT 10 //线性表空间分配的增量 using namespace std; ty

Android 特有的数据结构SpareArray 详解

在android developer 的开发文当中是这么描述SparesArray的: SparseArrays map integers to Objects. Unlike a normal array ofObjects, there can be gaps in the indices. It is intended to be more efficientthan using a HashMap to map Integers to Objects. 大概意思是SparseArrays

栈 详解

官方定义是这样的:栈(Stack)是一个后进先出的线性表,它要求只在表尾进行删除和插入操作. 栈是一种重要的线性结构,可以这样讲,栈是线性表的一种具体表现形式,但是它在操作上有一些特殊的要求和限制: --栈的元素必须“先进后出” --栈的操作只能在这个线性表的表尾进行. --注:对于栈来说,这个表尾称为栈的栈顶,相应的表头称为栈底. 入栈操作: 入栈操作又叫压栈操作,就是向栈中存放数据. 入栈操作要在栈顶进行,每次向栈中压入一个数据,top指针就要+1,直到栈满为止. 出栈操作: 出栈操作就是在

OpenCV学习笔记(四十)——再谈OpenCV数据结构Mat详解

原文:http://blog.csdn.net/yang_xian521/article/details/7107786 我记得开始接触OpenCV就是因为一个算法里面需要2维动态数组,那时候看core这部分也算是走马观花吧,随着使用的增多,对Mat这个结构越来越喜爱,也觉得有必要温故而知新,于是这次再看看Mat. Mat最大的优势跟STL很相似,都是对内存进行动态的管理,不需要之前用户手动的管理内存,对于一些大型的开发,有时候投入的lpImage内存管理的时间甚至比关注算法实现的时间还要多,这

数据结构-数组详解例子

struct Array { ElemType *base; /* 数组元素基址,由InitArray分配 */ int dim; /* 数组维数 */ int *bounds; /* 数组维界基址,由InitArray分配 */ int *constants; /* 数组映象函数常量基址,由InitArray分配 */ }; 对于三维数组a[3][2][2] base 是存储数组元素的指针,其指向的内存存储着: a[0][0][0] , a[0][0][1], a[0][1][0]......

详解JavaScript调用栈、尾递归和手动优化

调用栈(Call Stack) 调用栈(Call Stack)是一个基本的计算机概念,这里引入一个概念:栈帧. 栈帧是指为一个函数调用单独分配的那部分栈空间. 当运行的程序从当前函数调用另外一个函数时,就会为下一个函数建立一个新的栈帧,并且进入这个栈帧,这个栈帧称为当前帧.而原来的函数也有一个对应的栈帧,被称为调用帧.每一个栈帧里面都会存入当前函数的局部变量. 当函数被调用时,就会被加入到调用栈顶部,执行结束之后,就会从调用栈顶部移除该函数.并将程序运行权利(帧指针)交给此时栈顶的栈帧.这种后进

图的应用详解-数据结构

图的应用详解-数据结构 概述 最小生成树——无向连通图的所有生成树中有一棵边的权值总和最小的生成树 拓扑排序 ——由偏序定义得到拓扑有序的操作便是拓扑排序.建立模型是AOV网 关键路径——在AOE-网中有些活动可以并行地进行,所以完成工程的最短时间是从开始点到完成点的最长路径的长度,路径长度最长的路径叫做关键路径(Critical Path). 最短路径——最短路径问题是图研究中的一个经典算法问题, 旨在寻找图(由结点和路径组成的)中两结点之间的最短路径. 1.最小生成树 1.1 问题背景: