用队列实现栈和用栈实现队列

目录

  • 用队列实现栈
    • 创建栈
    • 实现入栈
    • 实现出栈
    • 判空
    • 取栈顶元素
    • 释放
  • 用栈实现队列
    • 创建队列
    • 入队
    • 出队
    • 返回队列开头的元素
    • 判空
    • 释放

前面我们实现了栈和队列,其实栈和队列之间是可以相互实现的

下面我们来看一下 用 队列实现栈用栈实现队列

用队列实现栈

使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)
实现 MyStack 类:
void push(int x) 将元素 x 压入栈顶。
int pop() 移除并返回栈顶元素。
int top() 返回栈顶元素。
boolean empty() 如果栈是空的,返回 true ,否则返回 false

如图是2个队列和1个栈
用队列实现栈和用栈实现队列_第1张图片
此时我们已经向栈中插入了5个元素,因为用队列去模拟,所以元素存放在栈中

因为用2个队列实现栈,所以我们创建一个MyStack的结构体,里面的元素是2个队列

typedef struct {
    Queue q1;
    Queue q2;
} MyStack;

创建栈

因为函数会返回创建栈的地址,如果定义的这个栈为局部遍历,则出函数后这个值就会被销毁,所以我们需要将这个栈开辟到堆区

然后也调用队列中的初始化函数,队栈结构体中2个队列进行开辟和初始化

MyStack* myStackCreate() {
    MyStack* new = (MyStack*)malloc(sizeof(MyStack));
    if(new==NULL)
    {
        perror("malloc fail");
        return NULL;
    }
    QueueInit(&new->q1);
    QueueInit(&new->q2);
    return new;
}

实现入栈

在非空的队列中插入元素,就相当于入栈了

void myStackPush(MyStack* obj, int x) {
    if(!QueueEmpty(&obj->q1))
    {
        QueuePush(&obj->q1,x);
    }
    else
    {
         QueuePush(&obj->q2,x);
    }
}

实现出栈

假设我们现在要出栈,按照栈的特点,先出的应该是5,但是由于队列的特点,只能先进先出,所以我们可以把最后一个元素之前的所有元素都出队并放到为空的那个队列中,再将最后一个元素出队,就做到的出栈的操作
用队列实现栈和用栈实现队列_第2张图片

所以这里需要找到空的那个队列,将有元素的队列中前面元素都放到空队列中后,再将最后一个元素出队,这样还是一个空队列一个有元素队列
所以一直会有一个空队列和一个非空队列
用队列实现栈和用栈实现队列_第3张图片

根据这个思路我们实现一下代码

首先要判断出空队列和非空队列,如果直接使用if else语句就会是语句冗余

所以我们可以定义emptyQnonemptyQ指针表示空队列和非空队列,然后判断出q1和q2哪个才是空队列和非空队列,emptyQ指向空队列,nonemptyQ指向非空队列

	Queue* emptyQ = &obj->q1;
    Queue* nonemptyQ = &obj->q2;
    if(!QueueEmpty(&obj->q1))
    {
        nonemptyQ = &obj->q1;
        emptyQ = &obj->q2;
    }

然后将非空队列中最后一个元素前的所有元素放入空数组中

	while(QueueSize(nonemptyQ)>1)
    {
        QueuePush(emptyQ,QueueFront(nonemptyQ));
        QueuePop(nonemptyQ);
    }

最后返回原非空队列中唯一的那个值,然后将它出队列

完整代码:

int myStackPop(MyStack* obj) {
    Queue* emptyQ = &obj->q1;
    Queue* nonemptyQ = &obj->q2;
    if(!QueueEmpty(&obj->q1))
    {
        nonemptyQ = &obj->q1;
        emptyQ = &obj->q2;
    }

    while(QueueSize(nonemptyQ)>1)
    {
        QueuePush(emptyQ,QueueFront(nonemptyQ));
        QueuePop(nonemptyQ);
    }
    int front = QueueFront(nonemptyQ);
    QueuePop(nonemptyQ);
    return front;
}

判空

当两个队列都为空,那么模拟的栈也为空

bool myStackEmpty(MyStack* obj) {
    return QueueEmpty(&obj->q1)&&QueueEmpty(&obj->q2);
}

取栈顶元素

元素都存在非空队列中,先找到非空队列,然后调用队列的QueueBack取出队列中最后一个值

int myStackTop(MyStack* obj) {
    if(!QueueEmpty(&obj->q1))
    {
        return QueueBack(&obj->q1);
    }
    else
    {
        return QueueBack(&obj->q2);
    }
}

其实这里可以体现出在实现队列时,设计QueueBack函数的原因


释放

void myStackFree(MyStack* obj) 
{
    QueueDestroy(&obj->q1);
    QueueDestroy(&obj->q2);
    free(obj);
    obj = NULL;
}

用栈实现队列

使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty)
实现 MyQueue类:
void push(int x) 将元素 x 推到队列的末尾
int pop() 从队列的开头移除并返回元素
int peek() 返回队列开头的元素
boolean empty() 如果队列为空,返回 true ;否则,返回 false

我们先将一些数据入到一个栈中
用队列实现栈和用栈实现队列_第4张图片
如果我们想实现出队,首先出队的数据应该是1,但是根据栈的特点,1是最后一个出

所以需要将栈中非栈底元素全都入栈到另一个栈中,根据栈的特点,这些元素到另一个栈中后,元素的顺序对于队列来说就是“正的了”

用队列实现栈和用栈实现队列_第5张图片
所以对于这个栈,它的出栈顺序和要模拟的队列出队顺序是一样的

如果想入栈,如果入到之前“顺序正常”的栈中,则会改变最后出队的顺序

所以从上面的分析可以看出,2个栈可以单独负责一项任务,一个栈负责入队,一个负责出队

在定义MyQueue时,直接定义一个叫PushStack和一个叫PopStack的栈

typedef struct {
    ST PushStack;
    ST PopStack;
} MyQueue;

用队列实现栈和用栈实现队列_第6张图片

只要是入队,就往PushStack中入

当出队时,如果PopStack为空,就需要把PushStack中的数据先出栈,在入栈到PopStack中,然后出PopStack的栈顶元素
用队列实现栈和用栈实现队列_第7张图片
如果PopStack不为空,出队其实就是直接从PopStack中出栈,出PopStack的栈顶元素

所以可以看出,最终都是出PopStack的栈顶元素,只是如果PopStack为空,就需要将元素放到PopStack中,在实现时判断PopStack是否为空

所以下面我们来实现一下:


创建队列

MyQueue* myQueueCreate() {
    MyQueue*new = (MyQueue*)malloc(sizeof(MyQueue));
    if(new ==NULL)
    {
        perror("malloc fail");
        return NULL;
    }
    STInit(&new->PushStack);
    STInit(&new->PopStack);

    return new;

}

入队

根据前面的分析,入队就是将元素入栈到PushStack

void myQueuePush(MyQueue* obj, int x) {
    STPush(&obj->PushStack,x);
}

出队

如果PopStack为空,就需要把PushStack中的元素都放到PopStack

if(STEmpy(&obj->PopStack))
    {
        while(obj->PushStack.top>0)
        {
            STPush(&obj->PopStack,STTop(&obj->PushStack));
            STPop(&obj->PushStack);
        }
    }

如果PopStack为不为空,就直接出栈顶元素

 int top = STTop(&obj->PopStack);
    STPop(&obj->PopStack);
    return top;

完整代码:

int myQueuePop(MyQueue* obj) {
    if(STEmpy(&obj->PopStack))
    {
        while(obj->PushStack.top>0)
        {
            STPush(&obj->PopStack,STTop(&obj->PushStack));
            STPop(&obj->PushStack);
        }
    }

    int top = STTop(&obj->PopStack);
    STPop(&obj->PopStack);
    return top;
}

返回队列开头的元素

其实返回队列开头的元素的操作与上面出队列操作类似

PopStack为空时,就将PushStack中的元素出栈,再依次入栈到PopStack
PopStack不为空时,就直接返回栈顶元素

可以看出,这个操作与出队列的操作唯一有差距的一点就是,不用pop掉PopStack中的栈顶元素

int myQueuePeek(MyQueue* obj) {
    if(STEmpy(&obj->PopStack))
    {
        while(obj->PushStack.top>0)
        {
            STPush(&obj->PopStack,STTop(&obj->PushStack));
            STPop(&obj->PushStack);
        }
    }

    int top = STTop(&obj->PopStack);
   
    return top;
}  

判空

如果当PushStackPopStack都为空的话,模拟出的队列才为空

bool myQueueEmpty(MyQueue* obj) {
    return STEmpy(&obj->PushStack)&&STEmpy(&obj->PopStack);
}

释放

调用STDestroy函数,销毁模拟队列的2个栈,然后再free掉队列

void myQueueFree(MyQueue* obj) {
    STDestroy(&obj->PushStack);
    STDestroy(&obj->PopStack);
    free(obj);
    obj = NULL;
}

你可能感兴趣的:(#,C\C++数据结构,算法,数据结构,leetcode)