【算法与数据结构】栈的实现详解

简介: 【算法与数据结构】栈的实现详解

📝栈的概念及结构

栈的概念:

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

称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。

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

栈是一种限定只允许在一端进行插入和删除操作的线性数据结构。

栈的主要特点:


先进后出(LIFO, Last In First Out)。新添加的元素都放在栈顶,取出元素时也是从栈顶取出。


只允许在一端(栈顶)进行插入和删除操作。插入操作称为入栈,删除操作称为出栈。


栈内元素的访问只能是顺序访问,不能随机访问。


通常使用数组或链表来实现栈。

栈的基本操作:

- push(item): 将元素添加到栈顶。
- pop(): 弹出栈顶元素,同时删除该元素。
- peek(): 返回栈顶元素,但不删除。 
- isEmpty(): 检查栈是否为空。
- size(): 返回栈中元素的个数。

栈的结构:

使用数组实现栈时,维护一个top指针指向栈顶元素的下一个位置。入栈时将元素添加到数组top位置,并将top加1;出栈时从top位置取元素,并将top减1。

使用链表实现栈时,链表的头结点指向栈顶元素。入栈添加新节点到头结点后面,出栈删除头结点。

所以栈具有后进先出的特性,是一种限定只允许在一端插入和删除的线性数据结构。

🌉栈的实现

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

代价比较小。

使用数组实现栈确实具有一些优点:


内存连续性:数组在内存中是连续存储的,这使得对数组的访问速度相对较快,因为它允许缓存友好的访问模式。

尾部操作高效:数组在尾部插入或删除元素的时间复杂度为 O(1),这使得栈的 push 和 pop 操作效率很高。

但是,使用数组实现栈也有一些限制:

4. 固定大小:数组的大小一旦确定,就不能动态扩展,如果栈需要存储的元素数量超过了数组的大小,就会导致栈溢出。

5. 动态调整的开销:当栈的大小超出数组容量时,需要重新分配更大的数组并将原始数据复制到新数组中,这会引入一定的开销。


相比,链表实现栈的优点是:


动态大小:链表可以根据需要动态扩展,不受固定大小的限制。

插入和删除操作的效率:在链表中,插入和删除操作的时间复杂度为 O(1),不会像数组那样需要重新分配和复制数据。

但链表实现也有其缺点:


空间开销:链表中的每个节点都需要额外的指针来指向下一个节点,这会增加存储开销。

缓存不友好:由于节点在内存中不一定是连续存储的,可能会导致缓存未命中,从而降低访问速度。

因此,选择使用数组或链表实现栈取决于具体的需求和性能要求。如果需要高效的尾部操作和内存连续性,则数组实现可能更合适;而如果需要动态大小和高效的插入/删除操作,则链表实现可能更合适。

🌠栈的接口

本文将将使用动态链表实现栈:

StackCode.h

# define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

//方便修改
typedef int STDataType;
typedef struct Stack
{
  STDataType* _a;
  int _top;//栈顶
  int _capacity;//容量
}Stack;
//初始化栈
void StackInit(Stack* ps);
//入栈
void StackPush(Stack* ps, STDataType x);
//出栈
void StackPop(Stack* ps);
//获取栈顶元素
STDataType StackTop(Stack* ps);
//获取栈中有效元素个数
int StackSize(Stack* ps);
//检测栈是否有空,如果为空返true,如果不为空,返回false;
bool StackEmpty(Stack* ps);
//销毁栈
void StackDestroy(Stack* ps);

🌉初始化栈

//初始化栈
void StackInit(Stack* ps)
{
  assert(ps);//assert(ps)检查ps指针是否合法,防止空指针问题。
  ps->_a = NULL;//栈初始化时还未分配数组空间。
  ps->_capacity = ps->_top = 0;
}

注意:

_capacity和_top成员都设置为0

_capacity表示栈的总容量,初始化时为0表示还未分配空间。

_top表示栈顶元素的下一个位置,作为栈的有效元素计数器。初始化时为0表示栈为空。

🌠入栈

void StackPush(Stack* ps, STDataType x)
{
  assert(ps);
  //满了,需要扩容
  if (ps->_top == ps->_capacity)//判断栈是否满了(ps->_top == ps->_capacity),如果满了需要扩容:
  {//如果_capacity为0,新容量为4,否则新容量为原容量的2倍
    int newcapacity = ps->_capacity == 0 ? 4 : ps->_capacity * 2;
    STDataType* temp = (STDataType*)realloc(ps->_a, newcapacity * sizeof(STDataType));//使用realloc重新分配数组空间
    if (NULL == temp)
    {
      perror("realloc temp");//实际分配失败会打印错误并返回
      return;
    }

    ps->_a = temp;//扩容成功后,更新_a指针和_capacity
    ps->_capacity = newcapacity;

  }
  //将元素x赋值到栈顶位置ps->_a[ps->_top]
  ps->_a[ps->_top] = x;
  ps->_top++;
}

🌉出栈

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

修改_top指针,就可以模拟出栈操作了。元素本身不做删除,只是修改指针来"移除"顶部元素。

🌠获取栈顶元素

//获取栈顶元素
STDataType StackTop(Stack* ps)
{
  assert(ps);
  assert(!StackEmpty(ps));

  return ps->_a[ps->_top - 1];
}

由于栈使用数组实现,元素位置从0开始编号,栈顶指针_top指向当前栈顶元素的下一个位置,所以实际栈顶元素的位置是_top - 1

🌉获取栈中有效元素个数

//获取栈中有效元素个数
int StackSize(Stack* ps)
{
  assert(ps);

  return ps->_top;
}

栈使用数组实现,元素从0开始插入,_top指针指向当前最后一个元素的下一个位置,所以_top值就代表当前栈中元素的个数

🌉检测栈是否为空

//检测栈是否为空,如果为空返回true,结果不为空false
bool StackEmpty(Stack* ps)
{
  assert(ps);
  if (ps->_top == 0)
    return true;
  else
    return false;
  
}

🌉销毁栈

//销毁栈
void StackDestroy(Stack* ps)
{
  assert(ps);
  ps->_a = NULL;
  ps->_capacity = ps->_top   = 0;
}

}

Stack.c文件:

# define _CRT_SECURE_NO_WARNINGS 1
#include "StackCode.h"

//初始化栈
void StackInit(Stack* ps)
{
  assert(ps);
  ps->_a = NULL;
  ps->_capacity = ps->_top = 0;
}
//入栈
void StackPush(Stack* ps, STDataType x)
{
  assert(ps);
  //满了,需要扩容
  if (ps->_top == ps->_capacity)
  {
    int newcapacity = ps->_capacity == 0 ? 4 : ps->_capacity * 2;
    STDataType* temp = (STDataType*)realloc(ps->_a, newcapacity * sizeof(STDataType));
    if (NULL == temp)
    {
      perror("realloc temp");
      return;
    }

    ps->_a = temp;
    ps->_capacity = newcapacity;

  }
  ps->_a[ps->_top] = x;
  ps->_top++;
}

//出栈
void StackPop(Stack* ps)
{
  assert(ps);
  assert(!StackEmpty(ps));
  ps->_top--;
}
 
//获取栈顶元素
STDataType StackTop(Stack* ps)
{
  assert(ps);
  assert(!StackEmpty(ps));

  return ps->_a[ps->_top - 1];
}
//获取栈中有效元素个数
int StackSize(Stack* ps)
{
  assert(ps);

  return ps->_top;
}
//检测栈是否为空,如果为空返回true,结果不为空false
bool StackEmpty(Stack* ps)
{
  assert(ps);
  if (ps->_top == 0)
    return true;
  else
    return false;
  
}
//销毁栈
void StackDestroy(Stack* ps)
{
  assert(ps);
  ps->_a = NULL;
  ps->_capacity = ps->_top   = 0;
}

🌉测试文件

Test.c

#include "STackCode.h"

int main()
{
  Stack s;
  StackInit(&s);
  StackPush(&s,1);
  StackPush(&s,2);
  StackPush(&s,3);
  StackPush(&s,4);

  int top = StackTop(&s);
  printf("%d", top);
  StackPop(&s);

  StackPush(&s, 5);
  StackPush(&s, 6);

  while (!StackEmpty(&s))
  {
    int top = StackTop(&s);
    printf("%d", top);
    StackPop(&s);
  }

  StackDestroy(&s);

  return 0;
}

测试结果:

相关文章
|
7天前
|
算法 Java
算法系列之数据结构-Huffman树
Huffman树(哈夫曼树)又称最优二叉树,是一种带权路径长度最短的二叉树,常用于信息传输、数据压缩等方面。它的构造基于字符出现的频率,通过将频率较低的字符组合在一起,最终形成一棵树。在Huffman树中,每个叶节点代表一个字符,而每个字符的编码则是从根节点到叶节点的路径所对应的二进制序列。
32 3
 算法系列之数据结构-Huffman树
|
9天前
|
算法 Java
算法系列之数据结构-二叉搜索树
二叉查找树(Binary Search Tree,简称BST)是一种常用的数据结构,它能够高效地进行查找、插入和删除操作。二叉查找树的特点是,对于树中的每个节点,其左子树中的所有节点都小于该节点,而右子树中的所有节点都大于该节点。
54 22
|
10天前
|
存储 算法 Java
算法系列之数据结构-二叉树
树是一种重要的非线性数据结构,广泛应用于各种算法和应用中。本文介绍了树的基本概念、常见类型(如二叉树、满二叉树、完全二叉树、平衡二叉树、B树等)及其在Java中的实现。通过递归方法实现了二叉树的前序、中序、后序和层次遍历,并展示了具体的代码示例和运行结果。掌握树结构有助于提高编程能力,优化算法设计。
42 9
 算法系列之数据结构-二叉树
|
14天前
|
算法 调度 C++
STL——栈和队列和优先队列
通过以上对栈、队列和优先队列的详细解释和示例,希望能帮助读者更好地理解和应用这些重要的数据结构。
26 11
|
27天前
|
DataX
☀☀☀☀☀☀☀有关栈和队列应用的oj题讲解☼☼☼☼☼☼☼
### 简介 本文介绍了三种数据结构的实现方法:用两个队列实现栈、用两个栈实现队列以及设计循环队列。具体思路如下: 1. **用两个队列实现栈**: - 插入元素时,选择非空队列进行插入。 - 移除栈顶元素时,将非空队列中的元素依次转移到另一个队列,直到只剩下一个元素,然后弹出该元素。 - 判空条件为两个队列均为空。 2. **用两个栈实现队列**: - 插入元素时,选择非空栈进行插入。 - 移除队首元素时,将非空栈中的元素依次转移到另一个栈,再将这些元素重新放回原栈以保持顺序。 - 判空条件为两个栈均为空。
|
1月前
|
存储 机器学习/深度学习 算法
C 408—《数据结构》算法题基础篇—链表(下)
408考研——《数据结构》算法题基础篇之链表(下)。
94 29
|
1月前
|
存储 算法 C语言
C 408—《数据结构》算法题基础篇—链表(上)
408考研——《数据结构》算法题基础篇之链表(上)。
107 25
|
1月前
|
存储 人工智能 算法
C 408—《数据结构》算法题基础篇—数组(通俗易懂)
408考研——《数据结构》算法题基础篇之数组。(408算法题的入门)
77 23
|
2月前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
161 77
|
2月前
|
C++
【C++数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】
【数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】(1)遇到左括号:进栈Push()(2)遇到右括号:若栈顶元素为左括号,则出栈Pop();否则返回false。(3)当遍历表达式结束,且栈为空时,则返回true,否则返回false。本关任务:编写一个程序利用栈判断左、右圆括号是否配对。为了完成本关任务,你需要掌握:栈对括号的处理。(1)遇到左括号:进栈Push()开始你的任务吧,祝你成功!测试输入:(()))
48 7