栈的深度解析:顺序栈与链栈的实现

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 栈的深度解析:顺序栈与链栈的实现

引言

栈是一种重要的线性数据结构,遵循“后进先出”(LIFO)的原则。栈的应用非常广泛,如表达式求值、括号匹配、递归实现等。在本文中,我们将深入探讨栈的概念,并通过顺序栈和链栈两种实现方式进行对比分析。

一、基本概念

1.1 定义

栈(Stack)是一种只能在一端进行插入和删除操作的集合,遵循“后进先出”(LIFO)原则。即最后加入的元素最先被移除

1.2 基本操作

  • 入栈(Push):将新元素添加到栈顶。
  • 出栈(Pop):移除并返回栈顶元素。
  • 查看栈顶元素(Peek):返回栈顶元素,但不删除它。
  • 判断是否为空(isEmpty):检查栈是否没有元素。
  • 统计栈的大小(Size):获取栈中有效元素个数。

1.3 特点

操作限制:只能在栈顶进行元素的添加(入栈)和移除(出栈)。

栈顶元素:栈顶是当前可以访问和操作的元素。

空栈:栈为空时,无法进行出栈操作。

二、栈的实现

2.1 顺序栈

使用数组实现栈时,我们可以将数组的尾部作为栈顶。

入栈与出栈操作分别对应在数组尾部。添加元素与删除元素,时间复杂度都为 𝑂(1)

2.1.1 基本结构

typedef struct Stack
{
  DataType* arr;//数组实现
  int top;//栈顶
  int capacity;//记录容量
}ST;

2.1.2 功能实现

1).初始化栈
//初始化栈
void StackInit(ST* p)
{
  assert(p);
  p->arr = NULL;
  p->top = p->capacity = 0;
}
2).销毁栈
//销毁栈
void StackDestory(ST* p)
{
  assert(p);
  if (p->arr)
  {
    free(p->arr);
  }
  p->arr = NULL;
  p->top = p->capacity = 0;
}
3).入栈

//入栈
void StackPush(ST* p,DataType x)
{
  assert(p);
  checkcapacity(p);
  p->arr[p->top++] = x;
}
4).判空
//判断栈顶是否为空
bool StackEmpty(ST* p)
{
  assert(p);
  return p->top == 0;
}
5).出栈

//出栈
void StackPop(ST* p)
{
  assert(p);
  assert(!StackEmpty(p));//栈顶不为空才能删除
  --p->top;
}
6).取栈顶元素
//取栈顶元素
DataType StackTop(ST* p)
{
  assert(p);
  assert(!StackEmpty(p));
  return p->arr[p->top - 1];
}
7).栈长度
//获取栈中有效元素个数
int StackSize(ST* p)
{
  assert(p);
  return p->top;
}

2.2 链式栈

使用链表实现栈时,我们可以将链表的头节点视为栈顶,尾节点视为栈底。

对于入栈操作,我们只需将元素插入链表头部,这种节点插入方法被称为“头插法”。而对于

出栈操作,只需将头节点从链表中删除即可。

2.2.1 基本结构

//定义节点结构
typedef struct Node {
  DataType data;//数据域
  struct Node *next;//指针域
}Node;
// 定义链栈结构
typedef struct Stack{
  Node* top;            // 栈顶指针
  int size;             // 栈中有效元素个数
} ST;

2.2.2 功能实现

1).初始化栈
//初始化栈
void StackInit(ST* p)
{
  assert(p);
  p->top = NULL;
  p->size = 0;
}
2).销毁栈
//销毁栈
void StackDestory(ST* p) {
  Node* pcur = p->top; // 从栈顶开始
  Node* temp;
 
  while (pcur != NULL) {
    temp = pcur;         // 记录当前节点
    pcur = pcur->next; // 移动到下一个节点
    free(temp);             // 释放当前节点的内存
  }
 
  p->top = NULL;          // 将栈顶指针设置为 NULL
  p->size = 0;            // 重置栈的大小
}
3).入栈

//创建节点
//Node* CreateNode(DataType x)
//{
//  Node* newnode = (Node*)malloc(sizeof(Node));
//  if (newnode == NULL) {
//    perror("malloc fail");
//    exit(1);
//  }
//  newnode->data = x;
//  newnode->next = NULL;
//  return newnode;
//}
 
//入栈
void StackPush(ST* p,DataType x)
{
  assert(p);
  Node* newnode = CreateNode(x);
  newnode->next = p->top->next;
  p->top->next = newnode;
  ++p->size;
}
4).判空
//判断栈顶是否为空
bool StackEmpty(ST* p)
{
  assert(p);
  return p->top->next==NULL;//p->top->next是栈顶元素
}
5).出栈

//出栈
void StackPop(ST* p)
{
  assert(p);
  assert(!StackEmpty(p));//栈顶不为空才能删除
  Node* temp = p->top->next;
  p->top->next = p->top->next->next;
  free(temp);
  temp = NULL;
  --p->size;
}
6).取栈顶元素
//取栈顶元素
DataType StackTop(ST* p)
{
  assert(p);
  return p->top->next->data;
}
7).栈长度
//获取栈中有效元素个数
int StackSize(ST* p)
{
  assert(p);
  return p->size;
}

2.3 对比

顺序栈vs链栈

特点 顺序栈 链式栈
存储结构 基于数组 基于链表
内存管理 静态分配(也可动态扩容) 动态分配
空间效率 容量固定(也可动态扩容) 动态扩展
访问速度 O(1)时间复杂度 O(1)时间复杂度
栈溢出 容易发生 不易发生

三、完整代码

3.1  顺序栈

Stack.h

该部分主要包括函数的声明、以及头文件的引用

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int DataType;
typedef struct Stack
{
  DataType* arr;//数组实现
  int top;//栈顶
  int capacity;//记录容量
}ST;
//初始化栈
void StackInit(ST* p);
//销毁栈
void StackDestory(ST* p);
//入栈
void StackPush(ST* p, DataType x);
//出栈
void StackPop(ST* p);
//取栈顶元素
DataType StackTop(ST* p);
//获取栈中有效元素个数
int StackSize(ST* p);
Stack.c

该部分主要包括函数的定义

#define _CRT_SECURE_NO_WARNINGS
#include"Stack.h"
//初始化栈
void StackInit(ST* p)
{
  assert(p);
  p->arr = NULL;
  p->top = p->capacity = 0;
}
//销毁栈
void StackDestory(ST* p)
{
  assert(p);
  if (p->arr)
  {
    free(p->arr);
  }
  p->arr = NULL;
  p->top = p->capacity = 0;
}
//判断扩容
void checkcapacity(ST* p)
{
  assert(p);
  if (p->capacity == p->top)
  {
    int NewCap = p->capacity == 0 ? 4 : 2 * p->capacity;
    DataType* tmp = (DataType*)realloc(p->arr, NewCap * sizeof(DataType));
    if (tmp == NULL)
    {
      perror("realloc fail!");
      exit(1);
    }
    p->arr = tmp;
    p->capacity = NewCap;
  }
}
//入栈
void StackPush(ST* p,DataType x)
{
  assert(p);
  checkcapacity(p);
  p->arr[p->top++] = x;
}
//判断栈顶是否为空
bool StackEmpty(ST* p)
{
  assert(p);
  return p->top == 0;
}
//出栈
void StackPop(ST* p)
{
  assert(p);
  assert(!StackEmpty(p));//栈顶不为空才能删除
  --p->top;
}
//取栈顶元素
DataType StackTop(ST* p)
{
  assert(p);
  assert(!StackEmpty(p));
  return p->arr[p->top - 1];
}
//获取栈中有效元素个数
int StackSize(ST* p)
{
  assert(p);
  return p->top;
}
main.c

该部分用来测试,即函数的使用

#define _CRT_SECURE_NO_WARNINGS
#include"Stack.h"
void test01()
{
  ST st;
  StackInit (&st);
  StackPush (&st,1);
  StackPush (&st,3);
  StackPush (&st,5);
  StackPush (&st,7);
  while (!StackEmpty(&st))//栈顶元素依次出栈
  {
    DataType  data = StackTop(&st);
    printf("%d ", data);
    StackPop(&st);//出栈
  }
}
int main()
{
  test01();
  return 0;
}

3.2 链式栈

Stack.h

该部分主要包括函数的声明、以及头文件的引用

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int DataType;
//定义节点结构
typedef struct Node {
  DataType data;//数据域
  struct Node *next;//指针域
}Node;
// 定义链栈结构
typedef struct Stack{
  Node* top;            // 栈顶指针
  int size;             // 栈中有效元素个数
} ST;
//初始化栈
void StackInit(ST* p);
// 创建链表头节点
Node* CreateHead();
//销毁栈
void StackDestory(ST* p);
//入栈
void StackPush(ST* p, DataType x);
//出栈
void StackPop(ST* p);
//取栈顶元素
DataType StackTop(ST* p);
//获取栈中有效元素个数
int StackSize(ST* p);
的引用
Stack.c

该部分主要包括函数的定义

#pragma once
#include"Stack.h"
//初始化栈
void StackInit(ST* p)
{
  assert(p);
  p->top = NULL;
  p->size = 0;
}
//创建节点
Node* CreateNode(DataType x)
{
  Node* newnode = (Node*)malloc(sizeof(Node));
  if (newnode == NULL) {
    perror("malloc fail");
    exit(1);
  }
  newnode->data = x;
  newnode->next = NULL;
  return newnode;
}
// 创建链表头节点
Node* CreateHead()
{
  Node* headnode = CreateNode(0); // 头节点值为0(或任何不使用的值)
  return headnode;
}
//入栈
void StackPush(ST* p,DataType x)
{
  assert(p);
  Node* newnode = CreateNode(x);
  newnode->next = p->top->next;
  p->top->next = newnode;
  ++p->size;
}
//判断栈顶是否为空
bool StackEmpty(ST* p)
{
  assert(p);
  return p->top->next==NULL;//p->top->next是栈顶元素
}
//出栈
void StackPop(ST* p)
{
  assert(p);
  assert(!StackEmpty(p));//栈顶不为空才能删除
  Node* temp = p->top->next;
  p->top->next = p->top->next->next;
  free(temp);
  temp = NULL;
  --p->size;
}
//取栈顶元素
DataType StackTop(ST* p)
{
  assert(p);
  return p->top->next->data;
}
//获取栈中有效元素个数
int StackSize(ST* p)
{
  assert(p);
  return p->size;
}
//销毁栈
void StackDestory(ST* p) {
  Node* pcur = p->top; // 从栈顶开始
  Node* temp;
 
  while (pcur != NULL) {
    temp = pcur;         // 记录当前节点
    pcur = pcur->next; // 移动到下一个节点
    free(temp);             // 释放当前节点的内存
  }
 
  p->top = NULL;          // 将栈顶指针设置为 NULL
  p->size = 0;            // 重置栈的大小
}
main.c

该部分用来测试,即函数的使用

#define _CRT_SECURE_NO_WARNINGS
#include"Stack.h"
void test01()
{
  ST st;
  StackInit(&st);
  st.top = CreateHead();//栈顶指针指向头节点,故头节点的next为栈顶元素
  StackPush(&st,1);
  StackPush(&st,2);
  StackPush(&st,3);
  //StackPop(&st);
  //int data = StackTop(&st);
  //int size=StackSize(&st);
  //printf("%d\n", data);
  //printf("%d", size);
  //while (!StackEmpty(&st))
  //{
  //  DataType  data = StackTop(&st);
  //  printf("%d ", data);
  //  StackPop(&st);//出栈
  //}
  StackDestory(&st);
  //st.top = NULL;
}
int main()
{
  test01();
  return 0;
}

四、总结

栈是一种重要的基础数据结构,适用于多种计算场景。通过顺序栈和链式栈的实现,我们可以更好地理解栈的工作原理及其应用。选择哪种实现方式取决于具体需求,顺序栈在内存使用上更高效,而链式栈则提供了更大的灵活性。希望这篇博客能帮助你更好地理解栈的概念和实现!


相关文章
|
5月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
582 9
|
5月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
146 58
|
3月前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
242 77
|
2月前
|
算法 调度 C++
STL——栈和队列和优先队列
通过以上对栈、队列和优先队列的详细解释和示例,希望能帮助读者更好地理解和应用这些重要的数据结构。
39 11
|
2月前
|
网络协议 Linux Go
自己动手编写tcp/ip协议栈1:tcp包解析
学习tuntap中的tun的使用方法,并使用tun接口解析了ip包和tcp包,这是实现tcp/ip协议栈的第一步。
74 15
|
2月前
|
DataX
☀☀☀☀☀☀☀有关栈和队列应用的oj题讲解☼☼☼☼☼☼☼
### 简介 本文介绍了三种数据结构的实现方法:用两个队列实现栈、用两个栈实现队列以及设计循环队列。具体思路如下: 1. **用两个队列实现栈**: - 插入元素时,选择非空队列进行插入。 - 移除栈顶元素时,将非空队列中的元素依次转移到另一个队列,直到只剩下一个元素,然后弹出该元素。 - 判空条件为两个队列均为空。 2. **用两个栈实现队列**: - 插入元素时,选择非空栈进行插入。 - 移除队首元素时,将非空栈中的元素依次转移到另一个栈,再将这些元素重新放回原栈以保持顺序。 - 判空条件为两个栈均为空。
|
3月前
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
146 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
|
3月前
|
存储 C语言 C++
【C++数据结构——栈与队列】链栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现链栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储整数,最大
82 9
|
3月前
|
C++
【C++数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】
【数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】(1)遇到左括号:进栈Push()(2)遇到右括号:若栈顶元素为左括号,则出栈Pop();否则返回false。(3)当遍历表达式结束,且栈为空时,则返回true,否则返回false。本关任务:编写一个程序利用栈判断左、右圆括号是否配对。为了完成本关任务,你需要掌握:栈对括号的处理。(1)遇到左括号:进栈Push()开始你的任务吧,祝你成功!测试输入:(()))
129 7
|
5月前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
172 21

推荐镜像

更多
下一篇
oss创建bucket