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


相关文章
|
3天前
|
缓存 算法 Java
刷算法,你应该知道的队列经典应用
文章介绍了队列的基本特性和经典应用,包括如何用队列实现栈、使用优先级队列解决Top K问题,并通过LeetCode题目示例展示了队列在算法实现中的应用。
刷算法,你应该知道的队列经典应用
|
3天前
栈的几个经典应用,真的绝了
文章总结了栈的几个经典应用场景,包括使用两个栈来实现队列的功能以及利用栈进行对称匹配,并通过LeetCode上的题目示例展示了栈在实际问题中的应用。
栈的几个经典应用,真的绝了
|
1天前
|
机器学习/深度学习 人工智能 算法
【人工智能】线性回归模型:数据结构、算法详解与人工智能应用,附代码实现
线性回归是一种预测性建模技术,它研究的是因变量(目标)和自变量(特征)之间的关系。这种关系可以表示为一个线性方程,其中因变量是自变量的线性组合。
9 2
|
4天前
|
存储 网络协议 Linux
用户态协议栈06-TCP三次握手
用户态协议栈06-TCP三次握手
|
3天前
|
存储 缓存 算法
深入解析B树:数据结构、存储结构与算法优势
深入解析B树:数据结构、存储结构与算法优势
|
3天前
|
算法
【数据结构与算法】优先级队列
【数据结构与算法】优先级队列
6 0
|
3天前
|
存储 算法
【数据结构与算法】队列(顺序存储)
【数据结构与算法】队列(顺序存储)
5 0
|
4天前
|
存储
全局变量和局部变量在堆和栈的区别
全局变量和局部变量在堆和栈的区别
9 0
|
4天前
|
存储 人工智能 运维
高质量存储力发展问题之浪潮信息发布的大模型智算软件栈的定义如何解决
高质量存储力发展问题之浪潮信息发布的大模型智算软件栈的定义如何解决
8 0
【数据结构】栈和队列
【数据结构】栈和队列