目录
前言
1. 题目:使用队列实现栈
2. 思路
3. 分析
3.1 创建栈
3.2入栈
3.3 出栈
3.4 栈顶数据
3.5 判空和 “ 栈 ” 的销毁
4. 题解
总结
前言
我们已经学习了栈和队列,也都实现了它们各自的底层接口,那么接下我们就要开始栈和队列的专项刷题训练。
1. 题目:使用队列实现栈
题目描述:
题目链接:
队列实现栈https://leetcode.cn/problems/implement-stack-using-queues/
2. 思路
队列的结构是先进先出,题目的要求是,让我们利用队列的底层接口来实现栈,不可以改变队列的底层逻辑,所以如果你的思路是逆置队列这个链表,那这个思路就被pass掉了。
那我们要如何利用队列尾进头出的特性来实现栈的尾进尾出呢?题目中给了我们两个队列,我们要使用这两个队列实现栈。
入栈操作好说,问题在于出栈问题,思路是这样的:我们有两个队列,一个队列用于存储数据,另外一个队列(空队列)用于拷贝数据,将原队列的前n-1个数据拷贝到空队列中,然后再将原队列剩余的最后一个元素出队列,这样就模拟实现了栈的尾出。
3. 分析
根据上述的思路分析,队列实现栈,入栈不需要什么特殊操作例如我们入栈:1、2、3、4、5,出栈呢就是:5、4、3、2、1。
上述的思路已经介绍了解决办法,也是非常简单的,但有人可能会问:那这样算法的效率岂不是很低?这种方法的效率确实低,但是这道题目考察的并不是效率的问题,而实性质问题,这也是一道经典的面试题目。这道题目并不难,但它考察对数据结构的理解,各各接口的实现中有很多需要注意的细节。
首先这道题目是并没有给现成的队列,使用C语言解决需要我们现成造轮子,这也是C语言刷题的弊端,有很多题目都需要造轮子。那么这里我们就可以直接复制前边我们实现的队列。
接下来就是我们开始注意实现接口:
首先题目中给了我们两个队列,为了便于传参和使用,我们可以定义一个结构体:
typedef struct {
Que q1; //注意这里定于的队列类型一定要与自己定义的队列结构体类型对应
Que q2;
} MyStack;
这里我们在前边介绍结构体时提到过,匿名结构体。
3.1 创建栈
MyStack* myStackCreate() {}
题目给出的接口如上,那这里我们要怎么创建我们的栈呢?是这样吗?
MyStack* myStackCreate() {MyStack st;//…return &st;
}
对函数和指针比较熟悉的同学可能就已经发现不行,为什么不行?这里就牵扯到了函数相关的知识,函数内创建的变量都是存储在栈区,出了函数就会被销毁,内存已经被销毁,返回指针还有什么意义呢?所以这里需要使用malloc函数,动态内存分配开辟的空间在堆区,程序结束前不主动释放就一直存在。所以上述的创建变量的方法不可取。
正确的方法:
MyStack* myStackCreate() {MyStack* pst=(MyStack*)malloc(sizeof(MyStack));QueueInit(&pst->q1);QueueInit(&pst->q2);return pst;
}
这里的pst->q1,就等价于我们在创建的队列的结构体变量:Que q;在调用接口时需要传地址过去。
3.2入栈
接下来就是入栈,题目中给了我们两个队列,为了后续出栈操作我们需要确保一个队列为空,用于拷贝数据,所以我们入栈时需要在不为空的队列入。
void myStackPush(MyStack* obj, int x) {if(!IsEmpty(&obj->q1)){QueuePush(&obj->q1,x);}else{QueuePush(&obj->q2,x);}
}
如果两个都为空那就随便选一个都可以。
3.3 出栈
在进行出栈操作的时候,我们需要判断哪一个队列为空,然后将非空队列的前n-1个元素依次拷贝到空队列当中。这里我们可以先假设队列1为空,然后在判断队列1是否为空,如果不为空那就是队列2为空,进行修改。这个假设的方法还是很实用的。
拷贝过程如下:
注意这里是拷贝,不是将原队列的节点插入到空队列,而是通过队头数据这个函数接口来将数据传过去,然后入队(调用入队接口),入队之后及时更新队头(出队)。
int myStackPop(MyStack* obj) {Que* Empty=&obj->q1;Que* NoEmpty=&obj->q2;if(!IsEmpty(&obj->q1)){Empty=&obj->q2;NoEmpty=&obj->q1;}while(QueueSize(NoEmpty)>1){QueuePush(Empty,QueueFront(NoEmpty));QueuePop(NoEmpty);}int top=QueueFront(NoEmpty);//最后保存非空队列最后一个队列节点的数据,便于返回QueuePop(NoEmpty); //最后一个元素出队。return top;
}
3.4 栈顶数据
栈顶数据接口实现就简单了,我们前边对队列进行实现时,有队头和队尾数据的接口,我们可以直接调用。
int myStackTop(MyStack* obj) {if(!IsEmpty(&obj->q1)){return QueueBlack(&obj->q1);}else{return QueueBlack(&obj->q2);}
}
3.5 判空和 “ 栈 ” 的销毁
判空就很简单,如果两个队列都为空,那么这个 “ 栈 ” 也就为空。
bool myStackEmpty(MyStack* obj) {return (IsEmpty(&obj->q1)&&IsEmpty(&obj->q2));
}
“ 栈 ”的销毁,这里就不能直接free掉obj了,如果直接释放那我们程序中的两个队列就会丢失无法释放,所以在释放掉obj之前,我们需要先将两个队列销毁。
void myStackFree(MyStack* obj) {DestoryQueue(&obj->q1);DestoryQueue(&obj->q2);free(obj);
}
4. 题解
完整代码如下:
typedef int Datatype;
typedef struct QueueNode
{struct QueueNode* next;Datatype data;}QueueNode;
typedef struct Queue
{QueueNode* head;QueueNode* tail;int size;
}Que;
//初始化队列
void QueueInit(Que* pq);
//入队
void QueuePush(Que* pq, Datatype x);
//出队
void QueuePop(Que* pq);
//队头数据
Datatype QueueFront(Que* pq);
//队尾数据
Datatype QueueBlack(Que* pq);
//判空
bool IsEmpty(Que* pq);
//队列大小
int QueueSize(Que* pq);
//销毁队列
void DestoryQueue(Que* pq);void QueueInit(Que* pq)
{assert(pq);pq->head = pq->tail = NULL;pq->size = 0;
}
void QueuePush(Que* pq, Datatype x)
{assert(pq);QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));if (newnode == NULL){perror("malloc");exit(-1);}newnode->data = x;newnode->next = NULL;if (pq->tail == NULL){pq->head = pq->tail = newnode;}else{pq->tail->next = newnode;pq->tail = newnode;}pq->size++;
}
void QueuePop(Que* pq)
{assert(pq);assert(!IsEmpty(pq));if (pq->head->next == NULL){free(pq->head);pq->head = pq->tail = NULL;}else{QueueNode* next = pq->head->next;free(pq->head);pq->head = next;}pq->size--;
}
Datatype QueueFront(Que* pq)
{assert(pq);assert(!IsEmpty(pq));return pq->head->data;
}
Datatype QueueBlack(Que* pq)
{assert(pq);assert(!IsEmpty(pq));return pq->tail->data;
}
bool IsEmpty(Que* pq)
{assert(pq);return (pq->head == NULL);
}
int QueueSize(Que* pq)
{assert(pq);return pq->size;
}
void DestoryQueue(Que* pq)
{assert(pq);QueueNode* cur = pq->head;while (cur){QueueNode* next = cur->next;free(cur);cur = next;}pq->head = pq->tail = NULL;pq->size = 0;
}
typedef struct {
Que q1;
Que q2;
} MyStack;MyStack* myStackCreate() {MyStack* pst=(MyStack*)malloc(sizeof(MyStack));QueueInit(&pst->q1);QueueInit(&pst->q2);return pst;
}void myStackPush(MyStack* obj, int x) {if(!IsEmpty(&obj->q1)){QueuePush(&obj->q1,x);}else{QueuePush(&obj->q2,x);}
}int myStackPop(MyStack* obj) {Que* Empty=&obj->q1;Que* NoEmpty=&obj->q2;if(!IsEmpty(&obj->q1)){Empty=&obj->q2;NoEmpty=&obj->q1;}while(QueueSize(NoEmpty)>1){QueuePush(Empty,QueueFront(NoEmpty));QueuePop(NoEmpty);}int top=QueueFront(NoEmpty);QueuePop(NoEmpty);return top;
}int myStackTop(MyStack* obj) {if(!IsEmpty(&obj->q1)){return QueueBlack(&obj->q1);}else{return QueueBlack(&obj->q2);}
}bool myStackEmpty(MyStack* obj) {return (IsEmpty(&obj->q1)&&IsEmpty(&obj->q2));
}void myStackFree(MyStack* obj) {DestoryQueue(&obj->q1);DestoryQueue(&obj->q2);free(obj);
}
总结
本文队列模拟实现栈,让我们在实践中深入思考了数据结构的本质和应用,为我们的编程能力和问题解决能力提供了锻炼。本期内容到此结束,感谢阅读!