数据结构——算法竞赛中熠熠生辉的两朵金花—“栈“和“队列“(1)

简介: 数据结构——算法竞赛中熠熠生辉的两朵金花—“栈“和“队列“(1)


栈的定义和基本操作


栈的定义


栈(stack)是限定仅在表尾进行插入和删除操作的线性表

我们把允许插入和删除的一端称为栈顶(top),另一端称为(bottom),不含任何数据元素的栈称为空栈。栈又称为后进先出(Last In First Out)的线性表,简称LIFO结构。


注意:栈是一个线性表。也就是说栈是具有线性关系的,拥有它自己的直接前驱和直接后继。只是,栈是一种特殊的线性表,只能在表尾进行插入和删除操作,这里的表尾通常也被叫做栈顶(top)

微信图片_20221018104334.jpg

栈的基本操作


栈的插入操作(push),叫做进栈,也称压栈、入栈。类似于将子弹压入弹夹

栈的删除操作(pop),叫做出栈,有的也叫做弹栈。如同将弹夹中的子弹取出

进栈示意图:

微信图片_20221018104413.jpg

出栈示意图:

微信图片_20221018104516.jpg

栈的存储和实现


栈数组实现——算法竞赛必备


栈在算法竞赛中也算是常客了。只是使用它的时候需要注意,我们是用数组模拟栈的思想来实现栈。那,为什么要用数组了?我们平时学的都是结构体+指针实现的呀~


就笔者自己使用的C++而言,因为一般竞赛中的测试数据都要拉到极致,比如十万、一百万,假如使用结构体的方式,在new这十万、一百万的空间的时候,可能就超时了。包括下文的队列也是同样的道理,在竞赛中,建议用数组模拟。结构体加指针的玩法,笔者盲猜是用在工程中优化代码效率的

只是C++选手也可以偷偷懒,直接调用C++标准库中提供的库函数

image.jpeg

下面就从一道例题来引入怎么用数组快速的模拟栈吧~

例题描述:微信图片_20221018104656.jpg

原题传送门


参考代码(C++版本)

#include <iostream>
using namespace std;
const int N = 100010;
int m;
int stk[N];     //用于模拟栈的数组
int tt;         //尾指针
int main()
{
    cin >> m;
    while (m -- )
    {
        string op;
        int x;
        cin >> op;
        if (op == "push")
        {
            cin >> x;
            stk[ ++ tt] = x;    //尾指针向后移动,实现进栈
        }
        else if (op == "pop") tt -- ; // 尾指针向前移动,剔除数组末尾元素,实现出栈
        else if (op == "empty") cout << (tt ? "NO" : "YES") << endl;
        else cout << stk[tt] << endl;
    }
    return 0;
}

进栈(push)


数组模拟的栈,进栈代码很简单,只有一行…

    stk[ ++ tt] = x;    //尾指针向后移动,实现进栈

操作的原理图如下:

微信图片_20221018104846.jpg

出栈(pop)


出栈操作就更短了~

     else if (op == "pop") tt -- ; // 尾指针向前移动,剔除数组末尾元素,实现出栈

微信图片_20221018104932.jpg

获取信息——栈顶元素和栈是否为空


获取栈顶元素也就是获取尾指针tt所指的空间中的信息,因为是数组模拟的,所以直接将tt放到数组里面

   else cout << stk[tt] << endl;

判断栈是不是空栈是通过数组下标实现的。假如现在尾指针tt在栈底(索引为0)的位置,这个栈就是空栈。对于这道题而言,取巧用了三元运算符

        else if (op == "empty") cout << (tt ? "NO" : "YES") << endl;

微信图片_20221018105020.jpg

栈的链式存储结构及实现——工程开发和期末应试必备


下面的内容了,是数据结构用在工程上和期末考试的卷子上的

栈的链式存储结构实际上就是一个单链表,叫做链栈。插入和删除操作只能在链栈的栈顶进行。栈顶指针Top应该在链表的哪头?

微信图片_20221018105143.jpg

假如按照正常逻辑,放在链表的尾部,插入操作要从头结点开始,挨着挨着遍历过去,到最后一个元素,也就是栈顶就可以实现插入。

删除操作了?删除操作其实是没有办法进行的,因为链表的删除要知道被删除结点的前一个结点的信息,我们没法从链表的尾结点倒着回去找它的前一个结点,也没有办法确定它的前一个结点的信息,因此删除操作就无法实现。

微信图片_20221018105237.jpg

因此经过前人的总结,将头指针所指的位置当做栈顶对于插入和删除操作都十分方便


链栈类型定义


和单链表相同,定义一个结点类型,类型中的成员变量是数据域和指针域

typedef int Datatype;
typedef struct stacknode{
    Datatype data;        //数据域
    struct stacknode* next; //指针域
}LinkStack;

初始化栈


首先创建一个链栈S,然后通过NULL将这个创建的栈清空。返回被清空后的链栈的地址信息

参考实现代码:

LinkStack* InitStack()
{
    LinkStack* S;
    S = NULL;
    return S;
}

判断栈空


判断一个栈是否为空,若栈为空,则返回1,否则返回0

参考实现代码:

int EmptyStack(LinkStack* S)
{
    if(S == NULL) return 1;
    else return 0;
}

进栈


实现流程:

①将新插入的结点p的指针域指向原栈顶S

②将栈顶S指向新结点p微信图片_20221018105421.jpg

参考实现代码:

LinkStack* push(LinkStack *S,Datatype x)
{
    LinkStack* p;
    p = (LinkStack*)malloc(sizeof(struct stacknode)); //生成新结点 
    p->data = x;      //将x放到新结点的数据域 
    p->next = S;    //将新结点插入链表的表头之前 
    S = p;          //让头指针执行这个新插入的p。可以理解为让top指针指向这个新插入的结点
  return S;       //返回栈顶S 
}

出栈


①p指针指向原栈顶S

② 栈顶S指向其下一个结点

③释放p指针所指的空间微信图片_20221018105530.jpg

参考实现代码

LinkStack* pop(LinkStack* S , Datatype *x)
{
    LinkStack* p;
    if(EmptyStack(S)) //先判断栈是否为空栈
    {
        printf("栈为空\n");
        return NULL;
    }else
    {
      *x = S->data;   //栈顶元素取出来赋值给x 
      p = S;        //p指针指向原本的栈顶S 
      S = S->next;    //原栈顶S指向下一个结点 
      free(p);      //释放原栈顶空间 
      return S;     //返回栈顶S 
    }
}

链栈的两个核心操作入栈push 和 出栈pop 就没有啦,相信小伙伴已经明白是这么一回事了。下面的获取栈顶元素和输出栈中内容就相对轻松很多


取栈顶元素


参考实现代码:

int GetTop(LinkStack* S,Datatype *x)
{
    if(EmptyStack(S))  //先判断栈是否为空
    {
        printf("栈为空\n");
        return 0;
    }else
    {
        *x = S->data; //栈顶元素赋值给变量x 
        return 1;
    }
}

显示栈内元素


参考实现代码

void ShowStack(LinkStack *S)
{
    LinkStack *p = S;
    if(p == NULL)
    {
        printf("栈为空\n");
    }else
    {
        printf("从栈顶起,各元素依次为:\n");
        while(p != NULL)
        {
            printf("%d\t",p->data);
            p = p->next;
        }
    }
}

栈的应用举例


"进制转换"领域


以十进制转二进制为例,操作的流程是每次模2取得余数,整数自身除以权重2,从而更新整数自己。

可以考虑是将余数放到数组里,用一个循环反转数组,再用一个循环将新的数组遍历输出,听起来其实还是蛮麻烦的

实现流程:

用一个循环处理传入的十进制整数x,将它和2取余的结果入栈

用一个循环输出栈中的内容

参考实现代码

//十进制转二进制
void D_B(LinkStack *S,Datatype x)
{
    while(x)         //让余数入栈
    {
        S = push(S,x % 2);
        x /= 2;
    }
    printf("转换后的二进制为:");
    while(!EmptyStack(S))
    {
        S = pop(S,&x);    //依次从栈中弹出每一个余数
        printf("%d",x);   //输入每个余数
    }
}


相关文章
|
7月前
|
前端开发 Java
java实现队列数据结构代码详解
本文详细解析了Java中队列数据结构的实现,包括队列的基本概念、应用场景及代码实现。队列是一种遵循“先进先出”原则的线性结构,支持在队尾插入和队头删除操作。文章介绍了顺序队列与链式队列,并重点分析了循环队列的实现方式以解决溢出问题。通过具体代码示例(如`enqueue`入队和`dequeue`出队),展示了队列的操作逻辑,帮助读者深入理解其工作机制。
219 1
|
4月前
|
存储 监控 安全
企业上网监控系统中红黑树数据结构的 Python 算法实现与应用研究
企业上网监控系统需高效处理海量数据,传统数据结构存在性能瓶颈。红黑树通过自平衡机制,确保查找、插入、删除操作的时间复杂度稳定在 O(log n),适用于网络记录存储、设备信息维护及安全事件排序等场景。本文分析红黑树的理论基础、应用场景及 Python 实现,并探讨其在企业监控系统中的实践价值,提升系统性能与稳定性。
127 1
|
4月前
|
存储 监控 算法
基于跳表数据结构的企业局域网监控异常连接实时检测 C++ 算法研究
跳表(Skip List)是一种基于概率的数据结构,适用于企业局域网监控中海量连接记录的高效处理。其通过多层索引机制实现快速查找、插入和删除操作,时间复杂度为 $O(\log n)$,优于链表和平衡树。跳表在异常连接识别、黑名单管理和历史记录溯源等场景中表现出色,具备实现简单、支持范围查询等优势,是企业网络监控中动态数据管理的理想选择。
132 0
|
5月前
|
编译器 C语言 C++
栈区的非法访问导致的死循环(x64)
这段内容主要分析了一段C语言代码在VS2022中形成死循环的原因,涉及栈区内存布局和数组越界问题。代码中`arr[15]`越界访问,修改了变量`i`的值,导致`for`循环条件始终为真,形成死循环。原因是VS2022栈区从低地址到高地址分配内存,`arr`数组与`i`相邻,`arr[15]`恰好覆盖`i`的地址。而在VS2019中,栈区先分配高地址再分配低地址,因此相同代码表现不同。这说明编译器对栈区内存分配顺序的实现差异会导致程序行为不一致,需避免数组越界以确保代码健壮性。
99 0
栈区的非法访问导致的死循环(x64)
232.用栈实现队列,225. 用队列实现栈
在232题中,通过两个栈(`stIn`和`stOut`)模拟队列的先入先出(FIFO)行为。`push`操作将元素压入`stIn`,`pop`和`peek`操作则通过将`stIn`的元素转移到`stOut`来实现队列的顺序访问。 225题则是利用单个队列(`que`)模拟栈的后入先出(LIFO)特性。通过多次调整队列头部元素的位置,确保弹出顺序符合栈的要求。`top`操作直接返回队列尾部元素,`empty`判断队列是否为空。 两题均仅使用基础数据结构操作,展示了栈与队列之间的转换逻辑。
|
9月前
|
算法 调度 C++
STL——栈和队列和优先队列
通过以上对栈、队列和优先队列的详细解释和示例,希望能帮助读者更好地理解和应用这些重要的数据结构。
219 11
|
9月前
|
DataX
☀☀☀☀☀☀☀有关栈和队列应用的oj题讲解☼☼☼☼☼☼☼
### 简介 本文介绍了三种数据结构的实现方法:用两个队列实现栈、用两个栈实现队列以及设计循环队列。具体思路如下: 1. **用两个队列实现栈**: - 插入元素时,选择非空队列进行插入。 - 移除栈顶元素时,将非空队列中的元素依次转移到另一个队列,直到只剩下一个元素,然后弹出该元素。 - 判空条件为两个队列均为空。 2. **用两个栈实现队列**: - 插入元素时,选择非空栈进行插入。 - 移除队首元素时,将非空栈中的元素依次转移到另一个栈,再将这些元素重新放回原栈以保持顺序。 - 判空条件为两个栈均为空。
|
9月前
|
缓存 监控 算法
内网监控管理软件:PHP 语言队列算法揭秘
在数字化办公环境中,内网监控管理软件对企业的稳定运行和信息安全至关重要。本文深入介绍PHP中的队列算法及其在内网监控软件中的应用,包括监控数据收集、任务调度和日志记录等场景,通过代码示例展示其实现方法。队列算法可提高性能、保证数据顺序并实现异步处理,为企业提供高效的安全保障。
135 1
|
23天前
|
机器学习/深度学习 算法 机器人
【水下图像增强融合算法】基于融合的水下图像与视频增强研究(Matlab代码实现)
【水下图像增强融合算法】基于融合的水下图像与视频增强研究(Matlab代码实现)
142 0
|
1月前
|
数据采集 分布式计算 并行计算
mRMR算法实现特征选择-MATLAB
mRMR算法实现特征选择-MATLAB
115 2