【数据结构初阶】一个队列怎么实现栈~~OJ

简介: 【数据结构初阶】一个队列怎么实现栈~~OJ

1.用两个队列实现栈

用队列实现栈


a04c59162f314b5581a3b57ca8c19f69.png

思路:主要是“入栈”和“出栈”


左边的是栈,假设1234依次进栈,那么如果要用右边的两个队列实现栈的功能,就应该按照4321的次序出栈,按照题目要求得用两个队列实现4321出数据的形式,并且完成栈的初始化,判空,销毁等功能。


第一步:实现“入栈”,我们将要入的每一个数据都入在其中一个队列,而保持另一个为空


第二步:实现“出栈步骤1”,因为每出一个数据都要来回倒腾数据,使得这次这个队列为空,下一次就是另一个为空,所以这里为了方便,我们就不关心q1和q2谁为空,而是用空队列和非空队列来描述操作。


我们先取非空队列里的队头数据入到之前预留好的空队列中,然后将非空队列里队头数据出队列,依次类推,直到原来的非空队列中只剩一个元素,此时,原来非空队列里的前size-1个数据都保留到了原来的空队列中。


第三步:实现“出栈步骤2”,将原来的非空队列中的唯一一个数据4出队列,即完成一次出队列的过程。


739aa5297daa4f95949489f3ce6faaa3.png


这里有很多细节值得我们的注意:


1.模拟初始化栈的时候,在定义MyStack时不能定义一个局部变量,然后返回局部变量的地址,这是经典的返回栈空间地址的错误。(下面是错误的写法)


MyStack* myStackCreate() {
    MyStack obj;
    //...
    return &obj;
}

2.模拟出栈的时候,我们为了方便,定义出empty和noempty两个队列的指针,而非拷贝!

(下面是错误的写法)

int myStackPop(MyStack* obj) {
    Queue empty=obj->q1;
    Queue noempty=obj->q2;
    if(QueueEmpty(&obj->q2))
    {
        empty=obj->q2;
        noempty=obj->q1;
    }
    while(QueueSize(&noempty)>1)
    {
        QueuePush(&empty,QueueFront(noempty));
        QueuePop(&noempty);
    }
    int ret=QueueFront(&noempty);
    QueuePop(&noempty);
    return ret;
}

3.模拟销毁栈的时候 ,不能直接free(obj),这样释放掉的仅仅是MyStack这个结构体的变量,而结构体变量里的一些指针所指向的动态开辟的空间并不会被释放掉!!(下面是错误的写法)


而将QueueDestory(&obj->q1);QueueDestory(&obj->q2);调用Queue的函数一个一个将链表结点释放才能真正达到释放的目的~~

void myStackFree(MyStack* obj) {
     free(obj);
}

4c332e4c01064fb3aca947bb50c87221.png


下面是完整的正确的代码~~

typedef int QDateType;
typedef struct QueueNode
{
  struct QueueNode* next;
  QDateType val;
}QueueNode;
typedef struct Queue
{
  QueueNode* head;
  QueueNode* tail;
}Queue;
void QueueInit(Queue* ps);
void QueuePush(Queue* ps, QDateType x);
void QueuePop(Queue* ps);
QDateType QueueFront(Queue* ps);
QDateType QueueBack(Queue* ps);
bool QueueEmpty(Queue* ps);
int QueueSize(Queue* ps);
void QueueDestory(Queue* ps);
void QueueInit(Queue* ps)
{
  assert(ps);
  ps->head = ps->tail = NULL;
}
void QueueDestory(Queue* ps)
{
  assert(ps);
  QueueNode* cur = ps->head;
  while (cur)
  {
    QueueNode* next = cur->next;
    free(cur);
    cur = next;
  }
  ps->head = ps->tail = NULL;
}
//入队列:尾插
void QueuePush(Queue* ps,QDateType x)
{
  assert(ps);
  QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
  newnode->next = NULL;
  newnode->val = x;
  if (newnode == NULL)
  {
    perror("malloc fail.");
    exit(-1);
  }
  if (ps->tail == NULL)
  {
    ps->head = ps->tail = newnode;
  }
  else
  {
    ps->tail->next = newnode;
    ps->tail = ps->tail->next;
  }
}
void QueuePop(Queue* ps)
{
  assert(ps);
  assert(!QueueEmpty(ps));
  if (ps->head == ps->tail)
  {
    free(ps->head);
    ps->head = ps->tail = NULL;
  }
  else
  {
    QueueNode* next = ps->head->next;
    free(ps->head);
    ps->head = next;
  } 
}
QDateType QueueFront(Queue* ps)
{
  assert(ps);
  assert(!QueueEmpty(ps));
  return ps->head->val;
}
QDateType QueueBack(Queue* ps)
{
  assert(ps);
  assert(!QueueEmpty(ps));
  return ps->tail->val;
}
bool QueueEmpty(Queue* ps)
{
  assert(ps);
  return ps->tail == NULL;
}
int QueueSize(Queue* ps)
{
  assert(ps);
  int size = 0;
  QueueNode* cur = ps->head;
  while(cur)
  {
    ++size;
    cur = cur->next;
  }
  return size;
}
typedef struct {
    Queue q1;
    Queue q2;
} MyStack;
MyStack* myStackCreate() {
    MyStack* obj=(MyStack*)malloc(sizeof(MyStack));
    QueueInit(&obj->q1);
    QueueInit(&obj->q2);
    return obj;
}
void myStackPush(MyStack* obj, int x) {
    //代码书写方式1:
    // Queue* empty=&obj->q1;
    // Queue* noempty=&obj->q2;
    // if(QueueEmpty(&obj->q2))
    // {
    //     empty=&obj->q2;
    //     noempty=&obj->q1;
    // }
    // QueuePush(noempty,x);
    //代码书写方式2:
    if(!QueueEmpty(&obj->q1))
    {
        QueuePush(&obj->q1,x);
    }
    else
    {
        QueuePush(&obj->q2,x);
    }
}
int myStackPop(MyStack* obj) {
    Queue* empty=&obj->q1;
    Queue* noempty=&obj->q2;
    if(QueueEmpty(&obj->q2))
    {
        empty=&obj->q2;
        noempty=&obj->q1;
    }
    while(QueueSize(noempty)>1)
    {
        QueuePush(empty,QueueFront(noempty));
        QueuePop(noempty);
    }
    int ret=QueueFront(noempty);
    QueuePop(noempty);
    return ret;
}
int myStackTop(MyStack* obj) {
    if(!QueueEmpty(&obj->q1))
    {
        return QueueBack(&obj->q1);
    }
    else 
    {
        return QueueBack(&obj->q2);
    }
}
bool myStackEmpty(MyStack* obj) {
        return QueueEmpty(&obj->q1)&&QueueEmpty(&obj->q2);
}
void myStackFree(MyStack* obj) {
     QueueDestory(&obj->q1);
     QueueDestory(&obj->q2);
     free(obj);
}

变式:如何用一个队列实现栈


029a571dc91344699b56b9cb449fbadb.png

typedef struct {
    Queue q;
} MyStack;
MyStack* myStackCreate() {
    MyStack* obj=(MyStack*)malloc(sizeof(MyStack));
    QueueInit(&obj->q);
    return obj;
}
void myStackPush(MyStack* obj, int x) {
   QueuePush(&obj->q,x);
   for(int i=0;i<QueueSize(&obj->q)-1;i++)
   {
       QueuePush(&obj->q,QueueFront(&obj->q));
       QueuePop(&obj->q);
   }
}
int myStackPop(MyStack* obj) {
    int top=QueueFront(&obj->q);
    QueuePop(&obj->q);
    return top;
}
int myStackTop(MyStack* obj) {
    return QueueFront(&obj->q);
}
bool myStackEmpty(MyStack* obj) {
    return QueueEmpty(&obj->q);
}
void myStackFree(MyStack* obj) {
    QueueDestory(&obj->q);
    free(obj);
}

4f06736141054cf097b756278c9ef464.png

2.用两个栈实现队列

用两个栈实现队列

5fd61e8125b44c0c921d2c6e1fcfaa46.png


思路:两个栈,一个用作入数据PushST,一个用作出数据PopST,在栈内倒转两次就变成正序了~~


343ee7e6b7da4698808a0cdd08def3b6.png

“队列”入数据:直接入PushST栈


"队列"出数据:如果PopST栈为空,则将PushST栈内的数据先全部倒到PopST中,然后再在PopST中出数据;

                       如果PopST栈不为空,则直接在PopST中出数据。


需要特别注意的是:"队列"出数据的时候,如果PopST栈不为空,则直接在PopST中出数据。这样可以避免123先入栈后,出了一个3后,再入数据4的情况出错!


typedef struct {
    Stack PushST;
    Stack 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);
}
//未复用myQueuePeek代码的书写代码方式1:
   int myQueuePop(MyQueue* obj) {
   if(StackEmpty(&obj->PopST))
   {
       while(!StackEmpty(&obj->PushST))
       {
           StackPush(&obj->PopST,StackTop(&obj->PushST));
           StackPop(&obj->PushST);
       }
   }
   int top=StackTop(&obj->PopST);
   StackPop(&obj->PopST);
   return top;
   }
   int myQueuePeek(MyQueue* obj);
//复用myQueuePeek代码的书写代码方式2:
// int myQueuePop(MyQueue* obj) {
// //    if(StackEmpty(&obj->PopST))
// //    {
// //        while(!StackEmpty(&obj->PushST))
// //        {
// //            StackPush(&obj->PopST,StackTop(&obj->PushST));
// //            StackPop(&obj->PushST);
// //        }
// //    }
//    int top=myQueuePeek(obj);
//    StackPop(&obj->PopST);
//    return top;
//    }
int myQueuePeek(MyQueue* obj) {
   if(StackEmpty(&obj->PopST))
   {
       while(!StackEmpty(&obj->PushST))
       {
           StackPush(&obj->PopST,StackTop(&obj->PushST));
           StackPop(&obj->PushST);
       }
   }
   return StackTop(&obj->PopST);
}
bool myQueueEmpty(MyQueue* obj) {
   return StackEmpty(&obj->PushST)&&StackEmpty(&obj->PopST);
}
void myQueueFree(MyQueue* obj) {
    StackDestory(&obj->PushST);
    StackDestory(&obj->PopST);
    free(obj);
}
目录
相关文章
|
11月前
|
前端开发 Java
java实现队列数据结构代码详解
本文详细解析了Java中队列数据结构的实现,包括队列的基本概念、应用场景及代码实现。队列是一种遵循“先进先出”原则的线性结构,支持在队尾插入和队头删除操作。文章介绍了顺序队列与链式队列,并重点分析了循环队列的实现方式以解决溢出问题。通过具体代码示例(如`enqueue`入队和`dequeue`出队),展示了队列的操作逻辑,帮助读者深入理解其工作机制。
448 1
|
9月前
|
编译器 C语言 C++
栈区的非法访问导致的死循环(x64)
这段内容主要分析了一段C语言代码在VS2022中形成死循环的原因,涉及栈区内存布局和数组越界问题。代码中`arr[15]`越界访问,修改了变量`i`的值,导致`for`循环条件始终为真,形成死循环。原因是VS2022栈区从低地址到高地址分配内存,`arr`数组与`i`相邻,`arr[15]`恰好覆盖`i`的地址。而在VS2019中,栈区先分配高地址再分配低地址,因此相同代码表现不同。这说明编译器对栈区内存分配顺序的实现差异会导致程序行为不一致,需避免数组越界以确保代码健壮性。
198 0
栈区的非法访问导致的死循环(x64)
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
743 77
232.用栈实现队列,225. 用队列实现栈
在232题中,通过两个栈(`stIn`和`stOut`)模拟队列的先入先出(FIFO)行为。`push`操作将元素压入`stIn`,`pop`和`peek`操作则通过将`stIn`的元素转移到`stOut`来实现队列的顺序访问。 225题则是利用单个队列(`que`)模拟栈的后入先出(LIFO)特性。通过多次调整队列头部元素的位置,确保弹出顺序符合栈的要求。`top`操作直接返回队列尾部元素,`empty`判断队列是否为空。 两题均仅使用基础数据结构操作,展示了栈与队列之间的转换逻辑。
|
算法 调度 C++
STL——栈和队列和优先队列
通过以上对栈、队列和优先队列的详细解释和示例,希望能帮助读者更好地理解和应用这些重要的数据结构。
307 11
☀☀☀☀☀☀☀有关栈和队列应用的oj题讲解☼☼☼☼☼☼☼
### 简介 本文介绍了三种数据结构的实现方法:用两个队列实现栈、用两个栈实现队列以及设计循环队列。具体思路如下: 1. **用两个队列实现栈**: - 插入元素时,选择非空队列进行插入。 - 移除栈顶元素时,将非空队列中的元素依次转移到另一个队列,直到只剩下一个元素,然后弹出该元素。 - 判空条件为两个队列均为空。 2. **用两个栈实现队列**: - 插入元素时,选择非空栈进行插入。 - 移除队首元素时,将非空栈中的元素依次转移到另一个栈,再将这些元素重新放回原栈以保持顺序。 - 判空条件为两个栈均为空。
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
1167 10
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
367 59
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
499 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
|
存储 C语言 C++
【C++数据结构——栈与队列】链栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现链栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储整数,最大
292 9