栈——“数据结构与算法”

简介: 栈——“数据结构与算法”

栈的概念及结构

栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。

进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。

栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。

压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。

出栈:栈的删除操作叫做出栈。出数据也在栈顶。

几个习题

1.一个栈的初始状态为空。现将元素1、2、3、4、5、A、B、C、D、E依次入栈,然后再依次出栈,则元素出栈的顺序是( B )。

A 12345ABCDE

B EDCBA54321

C ABCDE12345

D 54321EDCBA

2.若进栈序列为 1,2,3,4 ,进栈过程中可以出栈,则下列不可能的一个出栈序列是( C )

A 1,4,3,2

B 2,3,4,1

C 3,1,4,2

D 3,4,2,1

A选项:先进1,再立马出1,再进2 3 4,然后再出4 3 2

B选项:先进1 2,然后出2,然后进3,出3,进4,出4,最后再出1

C选项:无论怎么进出,都不会出现这样的组合,因为:都没有两个连续的数

D选项:先进1 2 3,再出3,然后进4,出4,再出2,最后出1

栈的实现

栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小。

下面,我们开始用代码来实现它:

typedef int STDataType;
typedef struct Stack
{
  STDataType* a;
  int top;//栈顶
  int capacity;//容量
}Stack;

这已经是一个常规操作了,利用结构体和typedef,定义一个栈

初始化栈:

// 初始化栈 
void StackInit(Stack* pst)
{
  assert(pst);
  pst->a = NULL;
  pst->top = 0;
  pst->capacity = 0;
}

销毁栈:

// 销毁栈 
void StackDestroy(Stack* pst)
{
  assert(pst);
  free(pst->a);
  pst->a = NULL;
  pst->top = pst->capacity = 0;
}

入栈:

这边使用了三目操作符

https://xiaoyalan.blog.csdn.net/article/details/128941939

https://xiaoyalan.blog.csdn.net/article/details/128993533

这是小雅兰写的操作符的相关知识点,有兴趣的可以来看看

// 入栈 
void StackPush(Stack* pst, STDataType x)
{
  assert(pst);
  //扩容
  if (pst->top == pst->capacity)
  {
    int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
    STDataType* tmp = (STDataType*)realloc(pst->a, newcapacity * sizeof(STDataType));
    if (tmp == NULL)
    {
      perror("realloc fail");
      return;
    }
    pst->a = tmp;
    pst->capacity = newcapacity;
  }
  pst->a[pst->top] = x;
  pst->top++;
}

检测栈是否为空:

// 检测栈是否为空
bool StackEmpty(Stack* pst)
{
  assert(pst);
  if (pst->top == 0)
  {
    return true;
  }
  else
  {
    return false;
  }
}

当然,这是一种比较low的写法,下面来看看这种写法:

// 检测栈是否为空
bool StackEmpty(Stack* pst)
{
  return pst->top==0;
}

这种写法,如此简洁,也实现了它该具备的功能,这样岂不是更好吗

出栈:

// 出栈 
void StackPop(Stack* pst)
{
  assert(pst);
  assert(!StackEmpty(pst));
  pst->top--;
}

获取栈顶元素:

// 获取栈顶元素 
STDataType StackTop(Stack* pst)
{
  assert(pst);
  assert(!StackEmpty(pst));
  return pst->a[pst->top - 1];
}

获取栈中有效元素的个数:

// 获取栈中有效元素个数 
int StackSize(Stack* pst)
{
  assert(pst);
  return pst->top;
}

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

要搞清楚这个概念,首先要明白”栈“原来的意思,如此才能把握本质。栈,存储货物或供旅客住宿的地方,可引申为仓库、中转站,所以引入到计算机领域里,就是指数据暂时存储的地方,所以才有进栈、出栈的说法。

首先,系统或者数据结构栈中数据内容的读取与插入(压入)push和 弹出pop是两回事。

压入是增加数据,弹出是删除数据 ,这些操作只能从栈顶即最低地址作为约束的接口界面入手操作 ,但读取栈中的数据是随便的,没有接口约束之说。很多人都误解这个理念从而对栈产生困惑。而系统栈在计算机体系结构中又起到一个跨部件交互的媒介区域的作用,即 cpu 与内存的交流通道 ,cpu只从系统给我们自己编写的应用程序所规定的栈入口线性地读取执行指令, 用一个形象的词来形容它就是pipeline(管道线、流水线)。cpu内部交互具体参见 EU与BIU的概念介绍。

栈作为一种数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照后进先出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。栈具有记忆作用,对栈的插入与删除操作中,不需要改变栈底指针。

栈是允许在同一端进行插入和删除操作的特殊线性表。允许进行插入和删除操作的一端称为栈顶(top),另一端为栈底(bottom)栈底固定,而栈顶浮动栈中元素个数为零时称为空栈。插入一般称为进栈(PUSH),删除则称为退栈(POP)。栈也称为先进后出表。

栈可以用来在函数调用的时候存储断点,做递归时要用到栈。

以上定义是在经典计算机科学中的解释。

在计算机系统中,栈则是一个具有以上属性的动态内存区域。程序可以将数据压入栈中,也可以将数据从栈顶弹出。在i386机器中,栈顶由称为esp的寄存器进行定位。压栈的操作使得栈顶的地址减小,弹出的操作使得栈顶的地址增大。

栈在程序的运行中有着举足轻重的作用。最重要的是栈保存了一个函数调用时所需要的维护信息,这常常称之为堆栈帧或者活动记录。堆栈帧一般包含如下几方面的信息:

1.函数的返回地址和参数
2. 临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量。


测试一下此代码的功能:

void testStack1()
{
  Stack st;
  StackInit(&st);
  StackPush(&st, 1);
  StackPush(&st, 2);
  StackPush(&st, 3);
  StackPush(&st, 4);
  printf("栈顶元素:%d\n", StackTop(&st));
  printf("栈中元素个数:%d\n", StackSize(&st));
  StackPop(&st);
  printf("栈顶元素:%d\n", StackTop(&st));
  StackPush(&st, 5);
  StackPush(&st, 6);
  while (!StackEmpty(&st))
  {
    printf("栈顶元素:%d\n", StackTop(&st));
    StackPop(&st);
  }
  StackDestroy(&st);
}
int main()
{
  testStack1();
  return 0;
}


源代码如下:

Stack.h的内容:

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int STDataType;
typedef struct Stack
{
  STDataType* a;
  int top;//栈顶
  int capacity;//容量
}Stack;
 
// 初始化栈 
void StackInit(Stack* pst);
 
// 销毁栈 
void StackDestroy(Stack* pst);
 
// 入栈 
void StackPush(Stack* pst, STDataType x);
 
// 出栈 
void StackPop(Stack* pst);
 
// 获取栈顶元素 
STDataType StackTop(Stack* pst);
 
// 获取栈中有效元素个数 
int StackSize(Stack* pst);
 
// 检测栈是否为空 
bool StackEmpty(Stack* pst);

Stack.c的内容:

#include"Stack.h"
// 初始化栈 
void StackInit(Stack* pst)
{
  assert(pst);
  pst->a = NULL;
  pst->top = 0;
  pst->capacity = 0;
}
// 销毁栈 
void StackDestroy(Stack* pst)
{
  assert(pst);
  free(pst->a);
  pst->a = NULL;
  pst->top = pst->capacity = 0;
}
// 入栈 
void StackPush(Stack* pst, STDataType x)
{
  assert(pst);
  //扩容
  if (pst->top == pst->capacity)
  {
    int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
    STDataType* tmp = (STDataType*)realloc(pst->a, newcapacity * sizeof(STDataType));
    if (tmp == NULL)
    {
      perror("realloc fail");
      return;
    }
    pst->a = tmp;
    pst->capacity = newcapacity;
  }
  pst->a[pst->top] = x;
  pst->top++;
}
// 检测栈是否为空
bool StackEmpty(Stack* pst)
{
  assert(pst);
  if (pst->top == 0)
  {
    return true;
  }
  else
  {
    return false;
  }
  //return pst->top==0;
}
// 出栈 
void StackPop(Stack* pst)
{
  assert(pst);
  assert(!StackEmpty(pst));
  pst->top--;
}
// 获取栈顶元素 
STDataType StackTop(Stack* pst)
{
  assert(pst);
  assert(!StackEmpty(pst));
  return pst->a[pst->top - 1];
}
 
// 获取栈中有效元素个数 
int StackSize(Stack* pst)
{
  assert(pst);
  return pst->top;
}


相关文章
|
15天前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
90 9
|
6天前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
15 1
|
9天前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
|
1月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
62 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
12天前
|
存储 JavaScript 前端开发
执行上下文和执行栈
执行上下文是JavaScript运行代码时的环境,每个执行上下文都有自己的变量对象、作用域链和this值。执行栈用于管理函数调用,每当调用一个函数,就会在栈中添加一个新的执行上下文。
|
14天前
|
存储
系统调用处理程序在内核栈中保存了哪些上下文信息?
【10月更文挑战第29天】系统调用处理程序在内核栈中保存的这些上下文信息对于保证系统调用的正确执行和用户程序的正常恢复至关重要。通过准确地保存和恢复这些信息,操作系统能够实现用户模式和内核模式之间的无缝切换,为用户程序提供稳定、可靠的系统服务。
41 4
|
1月前
|
算法 程序员 索引
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
栈的基本概念、应用场景以及如何使用数组和单链表模拟栈,并展示了如何利用栈和中缀表达式实现一个综合计算器。
30 1
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
|
18天前
|
算法 安全 NoSQL
2024重生之回溯数据结构与算法系列学习之栈和队列精题汇总(10)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第3章之IKUN和I原达人之数据结构与算法系列学习栈与队列精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
1月前
初步认识栈和队列
初步认识栈和队列
58 10
|
1月前
数据结构(栈与列队)
数据结构(栈与列队)
17 1