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

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

栈总结


对于栈而言,最主要是要清楚它先入后出的特性。其次拿捏清楚指向栈顶元素的"指针",因为无论是数组模拟的栈还是结构体实现的栈,核心操作都是对指针的移动


队列


队列的定义和基本运算


队列的定义


队列是只允许在一端进行插入操作,而在另外一端进行删除操作的线性表

队列是一种先进先出(First In First Out)的线性表,简称FIFO。允许插入的一端称为队尾,允许删除的一端称为队头。


队列的基本运算


队列的存储和实现


队列数组实现——算法竞赛必备


队列在算法竞赛中也是常客了,主流的依旧是使用的数组模拟的队列。原因和栈用数组模拟的原因一致。

队列用得最多,知名度最广的应该是在边权相等的情况下用宽度优先搜索去求最短路径,进而还有拓扑排序等等


下面依旧是从一道例题中引入数组模拟队列

样例描述:

image.jpeg

原题传送门

参考实现代码(C++版本)

#include <iostream>
using namespace std;
const int N = 100010;
int m;
int q[N];//模拟队列的数组
hh, tt = -1;//队头和队尾
int main()
{
    cin >> m;
    while (m -- )
    {
        string op;
        int x;
        cin >> op;
        if (op == "push")
        {
            cin >> x;
            q[ ++ tt] = x; //移动尾指针实现插入
        }
        else if (op == "pop") hh ++ ; //移动头指针位置,实现删除
        else if (op == "empty") cout << (hh <= tt ? "NO" : "YES") << endl;
        else cout << q[hh] << endl;
    }
    return 0;
}

队列的初始状态如下图

微信图片_20221018110607.jpg

在初始化状态中,队头队尾都放在索引为0的位置也是可以的,那么在后面移动尾指针tt的时候使用后缀++即可。

    //队尾从-1开始
     q[ ++ tt] = x; //移动尾指针实现插入
     //变通===> 队尾从0开始
     q[tt ++] = x; //移动尾指针实现插入

入队

因为本质是数组,所以当有新数据插入的时候,让新数据插入到数组的尾部就可以实现入队操作。

微信图片_20221018110717.jpg

            cin >> x;
            q[ ++ tt] = x; //移动尾指针实现插入

出队


数组模拟的队列中,使用队头指针hh来维护队列中第一个元素的位置。当有元素要出队了,那么通过移动队头指针调整队列的区间,从而确定新的队头。

微信图片_20221018110802.jpg

    else if (op == "pop") hh ++ ; //移动头指针位置,实现删除

微信图片_20221018110821.png

队列链表实现——工程开发和期末应试必备


链队列类型定义


使用结构体加上指针实现的队列在本质上仍旧是线性表中的链表,只是对它开发了新的特性,实现了先进先出(FIFO)的效果。

参考实现代码

typedef int DataType;   //定义DataType为int 型
typedef struct qnode{   //链队列存储类型 
  DataType data;      //定义链队中每个结点的数据域 
  struct qnode *next;   //定义链队中每个结点的指针域 
}LinkListQ; 
typedef struct
{
  LinkListQ *front,*rear; //链队列的队头指针和队尾指针 
}LinkQueue;         

只是对于队列而言,为了实现维护一段队伍的效果,需要额外增加一个队头指针front和一个队尾指针rear

LinkListQ *front,*rear; //链队列的队头指针和队尾指针 

初始化队列


操作流程:

①先建立一个队列的头结点Q,该头结点中有维护队列的队头指针front和队尾指针rear

② 建立一个链队的头结点p,并让其指针域为空

③ 将Q->front 和 Q->rear都指向该头结点并返回指针Q

参考实现代码:

LinkQueue *InitQueue()
{
  LinkQueue *Q;
  LinkListQ *p;
  Q = (LinkQueue *) malloc(sizeof (LinkQueue));  //建立链队列头指针所指的结点 
  p = (LinkListQ *)malloc(sizeof(LinkListQ));   //建立链队列的头结点 
  p->next = NULL;
  Q->front = p;                 //Q指针所指的front指针指向p 
  Q->rear  = p;                 //Q指针所指的rear指针指向p 
  return Q;
}

image.jpeg

入队操作


实现流程:

①将新结点插入到队尾,原本队尾的指针域Q->rear->next 指向新结点p(Q->rear->next = p;)

②将队尾Q->rear指向新结点p(Q->rear = p; )

参考实现代码:

//入队函数 
void InQueue(LinkQueue *Q,DataType x)
{
  LinkListQ *p;
  p = (LinkListQ *)malloc(sizeof(LinkListQ)); //生成新的结点
  p->data = x;                //将x存入新结点的数据域 
  p->next = NULL;         
  Q->rear->next = p;              //将新结点插入到链队之后 
  Q->rear = p;                //队尾指针指向队尾元素 
}

微信图片_20221018111042.jpg

出队操作


操作①:将队头指针的指针域指向原本队头元素的下一个位置的地址(Q->front->next = p->next;)

操作②:当队列中仅含有一个元素的时候,出队后队尾指针指向队头指针所指向的位置(if(p->next == NULL) Q->rear = Q->front)

操作③:释放p指针所指的空间


参考实现代码:

//判断队列是否为空的函数
int EmptyQueue(LinkQueue *Q) 
{
  if(Q->front == Q->rear) return 1;
  else return 0;
}
//出队函数 
int DeQueue(LinkQueue *Q,DataType *x)
{
  LinkListQ *p;
  if(EmptyQueue(Q))             //调用判空函数,判断当前队列是不是为空
  {
    printf("队空,无法出队任何元素\n") ;
    return 0;
  }else                   //队列不空 
  {
    p = Q->front->next;         //p指向队头元素
    *x = p->data;           //取出队头元素赋值给x
    Q->front->next = p->next;     //队头指针的指针域中存放新队头元素的地址
    if(p->next == NULL)  Q->rear = Q->front; //处理队列中只有一个元素的情况
    free(p);
    return 1;
  }
}

微信图片_20221018111129.jpg

求队头元素


参考实现代码:

int GetFront(LinkQueue *Q,DataType *x) 
{
  if(EmptyQueue(Q))       //调用判空函数,判断当前队列是不是为空
  {
    printf("队列为空,无法获取任何元素");
    return 0;
  }else
  {
    *x = Q->front->next->data;  //将队头元素中存放的数据给x
    return 1; 
  }
}

遍历链队


void ShowQueue(LinkQueue *Q)
{
  LinkListQ *p = Q->front->next;
  if(p == NULL) printf("队列为空,无法显示任何元素\n");
  else
  {
    printf("从队头起,队列中的每个元素是:");
    while(p != NULL)
    {
      printf("%d ",p->data);
      p = p->next;
    }
  }
}

队列的应用举例


"宽度优先搜索(BFS)"方向


队列是宽搜实现的核心结构,因为笔者之前已经详解总结和演示过宽搜,那我这里就不再赘述啦,小伙伴们可以看看这篇文章喔


"优先队列"——堆


优先队列是一种特殊的队列了,优先队列的出队是按照元素的优先级出队,比如,数值最小的先出队,或者数值最大的先出队。优先队列和二叉树结合起来,就又变成了一种神器——样同样的,因为笔者之前已经写过和堆相关的内容了,现在就直接推荐小伙伴们去看一看啦数据结构——被优先队列玩起来的“树“


队列总结


数组模拟的队列实现和操作都是比较清晰和轻松的,只是有时候需要注意元素假如要进行多次的入队和出队,那么用于实现队列结构的数组开头部分的空间就会被严重浪费,这种情况可以采用"循环队列"的方式优化。笔者这里就不展开讲了,我想在后续的《算法基础》专栏中结合习题进行系统的剖析。


结构体+指针的实现方式就要对指针十分小心,清楚当前的指针是指向谁的


相关文章
|
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`判断队列是否为空。 两题均仅使用基础数据结构操作,展示了栈与队列之间的转换逻辑。
|
8月前
|
存储 算法 Java
算法系列之数据结构-二叉树
树是一种重要的非线性数据结构,广泛应用于各种算法和应用中。本文介绍了树的基本概念、常见类型(如二叉树、满二叉树、完全二叉树、平衡二叉树、B树等)及其在Java中的实现。通过递归方法实现了二叉树的前序、中序、后序和层次遍历,并展示了具体的代码示例和运行结果。掌握树结构有助于提高编程能力,优化算法设计。
251 10
 算法系列之数据结构-二叉树
|
8月前
|
算法 Java
算法系列之数据结构-Huffman树
Huffman树(哈夫曼树)又称最优二叉树,是一种带权路径长度最短的二叉树,常用于信息传输、数据压缩等方面。它的构造基于字符出现的频率,通过将频率较低的字符组合在一起,最终形成一棵树。在Huffman树中,每个叶节点代表一个字符,而每个字符的编码则是从根节点到叶节点的路径所对应的二进制序列。
205 3
 算法系列之数据结构-Huffman树
|
8月前
|
算法 Java
算法系列之数据结构-二叉搜索树
二叉查找树(Binary Search Tree,简称BST)是一种常用的数据结构,它能够高效地进行查找、插入和删除操作。二叉查找树的特点是,对于树中的每个节点,其左子树中的所有节点都小于该节点,而右子树中的所有节点都大于该节点。
302 22
|
9月前
|
存储 机器学习/深度学习 算法
C 408—《数据结构》算法题基础篇—链表(下)
408考研——《数据结构》算法题基础篇之链表(下)。
312 30
|
9月前
|
算法 调度 C++
STL——栈和队列和优先队列
通过以上对栈、队列和优先队列的详细解释和示例,希望能帮助读者更好地理解和应用这些重要的数据结构。
219 11
|
9月前
|
DataX
☀☀☀☀☀☀☀有关栈和队列应用的oj题讲解☼☼☼☼☼☼☼
### 简介 本文介绍了三种数据结构的实现方法:用两个队列实现栈、用两个栈实现队列以及设计循环队列。具体思路如下: 1. **用两个队列实现栈**: - 插入元素时,选择非空队列进行插入。 - 移除栈顶元素时,将非空队列中的元素依次转移到另一个队列,直到只剩下一个元素,然后弹出该元素。 - 判空条件为两个队列均为空。 2. **用两个栈实现队列**: - 插入元素时,选择非空栈进行插入。 - 移除队首元素时,将非空栈中的元素依次转移到另一个栈,再将这些元素重新放回原栈以保持顺序。 - 判空条件为两个栈均为空。