猿创征文|栈和队列OJ刷题

简介: 猿创征文|栈和队列OJ刷题

前言

作者小蜗牛向前冲

名言我可以接收失败,但我不能接收放弃

如果觉的博主的文章还不错的话,还请 点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正。

为了提高自己编写代码的能力,特意开设刷题专题,记录自己刷题的思路和理解。欢迎❀大家的指正。

在下面的解题中我们都用C语言实现。

1用队列实现栈

在这里我们就是要将队列模拟实现成栈,我们知道队列是先进先出,栈是先进后出;那我们又如何实现呢?其实我们可以借助二个队列实现。下面我们借助图像来理解一下:

 

首先我们建立二个队列p1,p2,我们可能理解为入栈我们就将入数据到p1处。

其次,我们可以认为p2队列是用来中间存放数据的

这样我们就成功将5出栈了,如果要将4出栈,那么我们又得需要将p2中的数据倒入到p1中:

这样我们就完成了4出栈。

下面我们简单总结一下思路:

1我们要建立二个队列经行模拟。

2始终保持一个队列为空,在出栈时,将将n-1个数据都倒入到空队列中,那么留在另个队列的数据就可以直接出栈了。

下面是完成的解题代码:

typedef  int QDataType;
//定义队列
typedef struct QueueNode
{
  QDataType data;//数据类型
  struct QueueNode* next;
}QNode;
 
//定义指向头和尾的二个指针
typedef struct Queue
{
  QNode* head;
  QNode* tail;
  int size;
}Queue;
 
//初始化
void QueueInit(Queue* pq);
//销毁
void QueueDestroy(Queue* pq);
//入队
void QueuePush(Queue* pq, QDataType x);
//出队
void QueuePop(Queue* pq);
//返回指向队头的数据的指针
QDataType QueueFront(Queue* pq);
//返回指向队尾的数据的指针
QDataType QueueBack(Queue* pq);
//判断队列是否为空
bool QueueEmpty(Queue* pq);
//返回队列的大小
int QueueSize(Queue* pq);
 
//初始化
void QueueInit(Queue* pq)
{
  assert(pq);
  pq->head = pq->tail = NULL;
  pq->size = 0;
}
 
//销毁
void QueueDestroy(Queue* pq)
{
  assert(pq);
  QNode* cur = pq->head;
  while (cur)
  {
    QNode* del = cur;
    cur = cur->next;//指向下个节点
    free(del);
  }
  pq->head = pq->tail = NULL;//防止出现野指针
  pq->size = 0;
}
 
//入队
void QueuePush(Queue* pq, QDataType x)
{
  assert(pq);
  //申请节点
  QNode* newNode = (QNode*)malloc(sizeof(QNode));
  if (newNode==NULL)
  {
    perror("malloc fail");
    exit(-1);
  }
  else
  {
    newNode->data = x;
    newNode->next = NULL;
  }
  //队列为空
  if (pq->tail == NULL)
  {
    pq->head = pq->tail = newNode;
  }
  //不为空
  else
  {
    pq->tail->next = newNode;
    pq->tail = newNode;//tail指针指向newNode
  }
  pq->size++;
}
 
//出队
void QueuePop(Queue* pq)
{
  assert(pq);
  //断言队列是否为空
  assert(!QueueEmpty(pq));
  //当队列中就一个数据时
  if (pq->head->next == NULL)
  {
    free(pq->head);
    pq->head = pq->tail = NULL;
  }
  else
  {
    QNode* del = pq->head;
    pq->head = pq->head->next;//头变为下个节点
    free(del);
    del = NULL;
  }
  pq->size--;
}
 
//判断队列是否为空
bool QueueEmpty(Queue* pq)
{
  assert(pq);
  return pq->tail == NULL;
}
 
//返回指向队头的数据的指针
QDataType QueueFront(Queue* pq)
{
  assert(pq);
  //断言队列是否为空
  assert(!QueueEmpty(pq));
  return pq->head->data;
}
 
//返回指向队尾的数据的指针
QDataType QueueBack(Queue* pq)
{
  assert(pq);
  //断言队列是否为空
  assert(!QueueEmpty(pq));
  return pq->tail->data;
}
 
//返回队列的大小
int QueueSize(Queue* pq)
{
  return pq->size;
}
 
typedef struct {
    Queue p1;//入栈
    Queue p2;//出栈
 
} MyStack;
 
 
MyStack* myStackCreate() {
      MyStack* obj = (MyStack*)malloc(sizeof(MyStack));
        //对栈初始化
        QueueInit(&obj->p1);
        QueueInit(&obj->p2);
        return obj;
}
 
void myStackPush(MyStack* obj, int x) {
    //当p1为空时就将数据倒入
    if(!QueueEmpty(&obj->p1))
    {
        QueuePush(&obj->p1,x);
    }
    else
    {
        QueuePush(&obj->p2,x);
    }
 
}
 
int myStackPop(MyStack* obj) {
    Queue* empty = &obj->p1;//假设p1为空
    Queue* noEmpty = &obj->p2;
    //如果p1队列为非空
    if(!QueueEmpty(&obj->p1))
    {
        empty = &obj->p2;
        noEmpty = &obj->p1;
    }
    //将非空队列中的N-1的数据都倒入到空队列中,那么原队列还剩的1个数据就是要返回的栈顶数据
    while(QueueSize(noEmpty)>1)
    {
        QueuePush(empty,QueueFront(noEmpty));//入栈
        QueuePop(noEmpty);//出栈
    }
    int top =  QueueFront(noEmpty);
     QueuePop(noEmpty);//出栈
    return top;
}
 
int myStackTop(MyStack* obj) {
    if(!QueueEmpty(&obj->p1))
    {
        return QueueBack(&obj->p1);
    }
    else
    {
        return QueueBack(&obj->p2);
    }
}
 
bool myStackEmpty(MyStack* obj) {
    return QueueEmpty(&obj->p1) && QueueEmpty(&obj->p2);
}
 
void myStackFree(MyStack* obj) {
     QueueDestroy(&obj->p1);
     QueueDestroy(&obj->p2);
     free(obj);
}

2 栈实现队列

要用二个栈实现队列,这是很好实现的。

为什么这么说呢?我们可以用栈PushST来模拟入队列栈PopST模拟出队列

下面我们顺着这个思路来,来图解一下 1,2,3,4的入队列和出队列的过程。

1数据入队列

2 要出队列时,当PopST为空的时候,就将PushST的数据倒入到PopST中,然后直接出就行。

完整代码:

typedef  int STDataType;
//定义栈
typedef struct Stack
{
  STDataType* arr;//数据类型
  int pos;//数组下标
  int capacity;//栈的容量
}ST;
 
//初始化
void StackInit(ST* ps);
//销毁
void StackDestroy(ST* ps);
//入栈
void StackPush(ST* ps, STDataType x);
//出栈
void StackPop(ST* ps);
显示返回栈顶数据
STDataType StackTop(ST* ps);
//判断栈是否为空
bool StackEmpty(ST* ps);
//返回栈的长度//初始化
void StackInit(ST* ps)
{
  assert(ps);
  ps->arr = NULL;//初始数组为空
  ps->pos = ps->capacity = 0;//初始为0
}
 
//销毁
void StackDestroy(ST* ps)
{
  assert(ps);
  free(ps->arr);//arr是整个栈的地址
  ps->arr = NULL;
  ps->capacity = ps->pos = 0;
}
 
//入栈
void StackPush(ST* ps, STDataType x)
{
  assert(ps);
  //判断栈的空间是否满
  if (ps->pos == ps->capacity)
  {
    //扩容
    int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;//扩2倍
    STDataType* tmp = (STDataType*)realloc(ps->arr, newCapacity * sizeof(STDataType));
    if (tmp == NULL)
    {
      perror("reamlloc fail");
      exit(-1);
    }
    //跟新容量
    ps->arr = tmp;
    ps->capacity = newCapacity;
  }
  //入栈
  ps->arr[ps->pos] = x;
  ps->pos++;//下标++
}
 
//出栈
void StackPop(ST* ps)
{
  assert(ps);
  //断言栈是否为空
  assert(!StackEmpty(ps));
  --ps->pos;
}
 
//判断栈是否为空
bool StackEmpty(ST* ps)
{
  assert(ps);
  return ps->pos == 0;
}
 
//显示返回栈顶数据
STDataType StackTop(ST* ps)
{
  assert(ps);
  //断言栈是否为空
  assert(!StackEmpty(ps));
  return  ps->arr[ps->pos - 1];//下标已经前移
}
 
//返回栈的长度
int StackSize(ST* ps)
{
  assert(ps);
  return ps->pos;
}
int StackSize(ST* ps);
 
typedef struct {
  ST PushST;//入队列
  ST PopST;//出队列
} MyQueue;
 
 
MyQueue* myQueueCreate() {
  //为模拟的队列开辟空间
  MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue));
  //初始化
  StackInit(&obj->PushST);
  StackInit(&obj->PopST);
  return obj;
}
 
void myQueuePush(MyQueue* obj, int x) {
  StackPush(&obj->PushST, x);
}
void PushSTTOPopST(MyQueue* obj)
{
  //判断PopST是否为空
  if (StackEmpty(&obj->PopST))
  {
    while (!(StackEmpty(&obj->PushST)))
    {
      StackPush(&obj->PopST, StackTop(&obj->PushST));
      StackPop(&obj->PushST);
    }
  }
}
 
int myQueuePop(MyQueue* obj) {
  //将PuahST中的元素都倒入PopST中
  PushSTTOPopST(obj);
  int fornt = StackTop(&obj->PopST);
  StackPop(&obj->PopST);
  return fornt;
}
 
int myQueuePeek(MyQueue* obj) {
  //将PuahST中的元素都倒入PopST中
  PushSTTOPopST(obj);
  return StackTop(&obj->PopST);
 
}
 
bool myQueueEmpty(MyQueue* obj) {
  return StackEmpty(&obj->PushST) && StackEmpty(&obj->PopST);
}
 
void myQueueFree(MyQueue* obj) {
  //销毁空间
  StackDestroy(&obj->PushST);
  StackDestroy(&obj->PushST);
  free(obj);
}

3 设计循环队列

对于设计循环队列来说有二种思路:

1 用数组实现

2 用链表实现

 

对于这二种思路,我会首选思路1,为什么会这么说呢?

因为用链表实现循环队列,首先你要malloc多份空间,而且这样你还不好判断循环队列是否已满!

所以:我会选择用思路1来为大家解题。

此题有个重点:那就是如何判断队列是否以满。

因为在对于空和满来说,队列中指针fornt和back都是指向同一个位置。

那么我们这么去解决呢?

我们有二个思路:

1多开辟一块空间

2用size标记队列的长度,当size==k时就满

下面我们用思路1来解决:

代码转换:

bool myCircularQueueIsFull(MyCircularQueue* obj) {
    return (obj->back + 1) % obj->n == obj->fornt;
 
}

完整代码:

typedef struct {
    int* arr;
    int fornt;
    int back;
    int n;
 
} MyCircularQueue;
 
 
MyCircularQueue* myCircularQueueCreate(int k) {
    MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    obj->arr = (int*)malloc(sizeof(int) * (k + 1));
    obj->fornt = obj->back=0;
    obj->n = k + 1;
    return obj;
}
 
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    return obj->fornt == obj->back;
}
 
bool myCircularQueueIsFull(MyCircularQueue* obj) {
    return (obj->back + 1) % obj->n == obj->fornt;
 
}
 
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    if (myCircularQueueIsFull(obj))
    {
        return false;
    }
    obj->arr[obj->back] = value;
    obj->back++;
    //如果back指针已经指向尾,需要重新指向头
    obj->back %= obj->n;
    return true;
 
}
 
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    if (myCircularQueueIsEmpty(obj))
    {
        return false;
    }
    obj->fornt++;
    //如果fornt指针已经指向尾,需要重新指向头
    obj->fornt %= obj->n;
    return true;
}
 
int myCircularQueueFront(MyCircularQueue* obj) {
    if (myCircularQueueIsEmpty(obj))
    {
        return -1;
    }
    return obj->arr[obj->fornt];
}
 
int myCircularQueueRear(MyCircularQueue* obj) {
    if (myCircularQueueIsEmpty(obj))
    {
        return -1;
    }
    else if(obj->back==0)
    return obj->arr[obj->n-1];
    else
    return obj->arr[obj->back-1];
    // return obj->arr[(obj->back -1 + obj->n) % obj->n];
}
 
 
 
void myCircularQueueFree(MyCircularQueue* obj) {
    free(obj->arr);
    free(obj);
 
}


相关文章
232.用栈实现队列,225. 用队列实现栈
在232题中,通过两个栈(`stIn`和`stOut`)模拟队列的先入先出(FIFO)行为。`push`操作将元素压入`stIn`,`pop`和`peek`操作则通过将`stIn`的元素转移到`stOut`来实现队列的顺序访问。 225题则是利用单个队列(`que`)模拟栈的后入先出(LIFO)特性。通过多次调整队列头部元素的位置,确保弹出顺序符合栈的要求。`top`操作直接返回队列尾部元素,`empty`判断队列是否为空。 两题均仅使用基础数据结构操作,展示了栈与队列之间的转换逻辑。
|
1月前
|
编译器 C语言 C++
栈区的非法访问导致的死循环(x64)
这段内容主要分析了一段C语言代码在VS2022中形成死循环的原因,涉及栈区内存布局和数组越界问题。代码中`arr[15]`越界访问,修改了变量`i`的值,导致`for`循环条件始终为真,形成死循环。原因是VS2022栈区从低地址到高地址分配内存,`arr`数组与`i`相邻,`arr[15]`恰好覆盖`i`的地址。而在VS2019中,栈区先分配高地址再分配低地址,因此相同代码表现不同。这说明编译器对栈区内存分配顺序的实现差异会导致程序行为不一致,需避免数组越界以确保代码健壮性。
23 0
栈区的非法访问导致的死循环(x64)
|
3月前
|
前端开发 Java
java实现队列数据结构代码详解
本文详细解析了Java中队列数据结构的实现,包括队列的基本概念、应用场景及代码实现。队列是一种遵循“先进先出”原则的线性结构,支持在队尾插入和队头删除操作。文章介绍了顺序队列与链式队列,并重点分析了循环队列的实现方式以解决溢出问题。通过具体代码示例(如`enqueue`入队和`dequeue`出队),展示了队列的操作逻辑,帮助读者深入理解其工作机制。
110 1
|
5月前
|
算法 调度 C++
STL——栈和队列和优先队列
通过以上对栈、队列和优先队列的详细解释和示例,希望能帮助读者更好地理解和应用这些重要的数据结构。
80 11
|
5月前
|
DataX
☀☀☀☀☀☀☀有关栈和队列应用的oj题讲解☼☼☼☼☼☼☼
### 简介 本文介绍了三种数据结构的实现方法:用两个队列实现栈、用两个栈实现队列以及设计循环队列。具体思路如下: 1. **用两个队列实现栈**: - 插入元素时,选择非空队列进行插入。 - 移除栈顶元素时,将非空队列中的元素依次转移到另一个队列,直到只剩下一个元素,然后弹出该元素。 - 判空条件为两个队列均为空。 2. **用两个栈实现队列**: - 插入元素时,选择非空栈进行插入。 - 移除队首元素时,将非空栈中的元素依次转移到另一个栈,再将这些元素重新放回原栈以保持顺序。 - 判空条件为两个栈均为空。
|
6月前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
301 77
|
6月前
|
C++
【C++数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】
【数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】(1)遇到左括号:进栈Push()(2)遇到右括号:若栈顶元素为左括号,则出栈Pop();否则返回false。(3)当遍历表达式结束,且栈为空时,则返回true,否则返回false。本关任务:编写一个程序利用栈判断左、右圆括号是否配对。为了完成本关任务,你需要掌握:栈对括号的处理。(1)遇到左括号:进栈Push()开始你的任务吧,祝你成功!测试输入:(()))
163 7
|
6月前
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
222 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
|
6月前
|
存储 C语言 C++
【C++数据结构——栈与队列】链栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现链栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储整数,最大
114 9
|
8月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
189 5