数据结构——算法竞赛中熠熠生辉的两朵金花—“栈“和“队列“(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);   //输入每个余数
    }
}


相关文章
|
6天前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
118 75
|
6天前
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
27 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
|
6天前
|
存储 C语言 C++
【C++数据结构——栈与队列】链栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现链栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储整数,最大
32 9
|
6天前
|
C++
【C++数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】
【数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】(1)遇到左括号:进栈Push()(2)遇到右括号:若栈顶元素为左括号,则出栈Pop();否则返回false。(3)当遍历表达式结束,且栈为空时,则返回true,否则返回false。本关任务:编写一个程序利用栈判断左、右圆括号是否配对。为了完成本关任务,你需要掌握:栈对括号的处理。(1)遇到左括号:进栈Push()开始你的任务吧,祝你成功!测试输入:(()))
25 7
|
6天前
|
存储 算法 测试技术
【C++数据结构——树】二叉树的遍历算法(头歌教学实验平台习题) 【合集】
本任务旨在实现二叉树的遍历,包括先序、中序、后序和层次遍历。首先介绍了二叉树的基本概念与结构定义,并通过C++代码示例展示了如何定义二叉树节点及构建二叉树。接着详细讲解了四种遍历方法的递归实现逻辑,以及层次遍历中队列的应用。最后提供了测试用例和预期输出,确保代码正确性。通过这些内容,帮助读者理解并掌握二叉树遍历的核心思想与实现技巧。
25 2
|
22天前
|
存储 运维 监控
探索局域网电脑监控软件:Python算法与数据结构的巧妙结合
在数字化时代,局域网电脑监控软件成为企业管理和IT运维的重要工具,确保数据安全和网络稳定。本文探讨其背后的关键技术——Python中的算法与数据结构,如字典用于高效存储设备信息,以及数据收集、异常检测和聚合算法提升监控效率。通过Python代码示例,展示了如何实现基本监控功能,帮助读者理解其工作原理并激发技术兴趣。
55 20
|
20天前
|
算法
【算法】栈
栈相关算法题,供参考,附有链接地址及板书
|
2月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
268 9
|
2月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
43 1
|
2月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
81 5