在前面我们已经学习了如何使用c语言来实现栈和队列,二者较为明显的区别就是出入顺序,那我们是否可以使用他们彼此实现呢,答案是肯定的,接下来我们便一起试着互相实现吧!
一、使用队列实现栈
1.理解如何实现
栈是后进先出,那么我们就需要想办法将队列的顺序改变,我们肯定是不能直接改变队列的逻辑,那么一个队列似乎是做不到的,那我们就可以试着创建两个队列来实现,那么我们如何理解来创作呢,我来画个图一起理解一下:
首先我们给出两个队列q1和q2,我们将数据插入队列q1,我们需要解决的问题就是让“3”先出来,那我们该怎么做,显然,我们有另一个空队列q2,如果我们将q1中的所有数据(除了“3”)都一个个的插入到q2中,那我们在q1中就会只剩下数据“3”,那么我们再对他进行删除无论是队列还是栈在只有一个元素的时候所删除的数据位置均一样,就完美的完成了出“栈”操作,于是我们得到下面的情形:
我们可以看到,q1中什么都不剩,q2中除了被删除的数据其余都在,如果我们要进行下一步出栈操作,我们直接才重复一遍刚才的操作即可,只不过这次空的是q2。每次出栈之时我们都需要进行队列的空判断,空队列显然是不能有数据进行出“栈”的,同样的,我们在进行插入的时候也是不行的,也需要进行空的判断,接下来我们便开始实现操作。
2.实现实操
注意我们接下来的代码是建立在上一篇的队列实现代码之上的!!!
首先第一步我们需要创建一个结构能够包含q1与q1两个队列,于是我们可以写出:
typedef struct {
Queue q1;
Queue q2;
} MyStack;
这个可以有效的帮助我们来调用q1与q2。
然后我们需申请一个空间给他们,于是有:
MyStack* myStackCreate() {
MyStack* pst = (MyStack*)malloc(sizeof(MyStack));
QueueInit(&pst->q1);
QueueInit(&pst->q2);
return pst;
}
这里我们要注意并不是直接申请MyStack的空间就行了,我们依旧需要初始化q1与q2,然后将pst返回。
创建了之后我们就来实现“栈”的插入操作,插入我们肯定是不能直接无脑插入到q1或者q2中,这里我们使用if语句进行判断,当q1不为空的时候,我们再插入进去,反之为空我们就查取到q2,在这个else中完美的包括了q2为空和不为空的两种情况,q2也为空就收我们刚创建两个空的队列还未插入数据的情况:
void myStackPush(MyStack* obj, int x) {
if (!QueueEmpty(&(obj->q1)))
{
QueuePush(&(obj->q1), x);
}
else
{
QueuePush(&(obj->q2), x);
}
}
然后我们来实现出“栈”操作,同样我们也需要判断出来哪个不是空队列,两个都为空也下面然是不行的,我们首先设q1为空,设q2不为空,然后我们再用一个if语句来进行判断及时纠正,于是我们使用while将非空队列中的数据一个个的转移到空队列中,使用QueueSize(nonEmpty) > 1,来限制最后一个数据的位置,然后在while之外我们将空剩余一个数据的队列清空(包括没有数据的队列,均在if语句的条件中被囊括),于是我们可以写出下面的一段代码:
int myStackPop(MyStack* obj) {
Queue* empty = &(obj->q1);
Queue* nonEmpty = &(obj->q2);
if (!QueueEmpty(&(obj->q1)))
{
nonEmpty = &(obj->q1);
empty = &(obj->q2);
}
while (QueueSize(nonEmpty) > 1)
{
QueuePush(empty, QueueFront(nonEmpty));
QueuePop(nonEmpty);
}
int top = QueueFront(nonEmpty);
QueuePop(nonEmpty);
return top;
}
上面的代码不知道你有没有注意我返回的是int类型,这个其实并没有必要,我们可以通过这个锦上添花,他能够返回出“栈”的数据,所以我们返回void类型就行啦~
接着我们再来实现栈顶数据的显示函数,想要显示这个也是需要判断哪个是非空的,返回非空的队列的栈顶数据就可以啦,其实就是pop不进行删除操作,所以几乎和上面一样,不过我们在展示的时候需要记得把这个数据传到另外一个数组,否则该数组就一个栈顶元素其他什么也没有,同时注意转移后删除。
int myStackTop(MyStack* obj) {
Queue* empty = &(obj->q1);
Queue* nonEmpty = &(obj->q2);
if (!QueueEmpty(&(obj->q1)))
{
nonEmpty = &(obj->q1);
empty = &(obj->q2);
}
while (QueueSize(nonEmpty) > 1)
{
QueuePush(empty, QueueFront(nonEmpty));
QueuePop(nonEmpty);
}
int top = QueueFront(nonEmpty);
QueuePush(empty, top);
QueuePop(nonEmpty);
return top;
}
我们再来实现这种“栈”的判空函数,这个我们用脑子思考就行啦,两个队列,正常情况是由一个为空一个非空,如果“栈”为空的话,,就是两个队列均为空,于是我们就了可以写出下面的代码。
bool myStackEmpty(MyStack* obj) {
return QueueEmpty(&(obj->q1)) && QueueEmpty(&(obj->q2));
}
最后我们将“栈”进行销毁(free),还是循序渐进,优先将两个队列内的玄素一个个释放,我们直接使用destory就行了,最后将MyStack释放就行啦。
void myStackFree(MyStack* obj) {
QueueDestory(&(obj->q1));
QueueDestory(&(obj->q2));
free(obj);
}
这就是我们用队列实现栈,我们来回顾一下,我们使用了两个队列,采用一个为空另一个不为空的方式进行相互转移留下最后一个元素的方式进行队列的反方向出列实现用队列实现栈。
如果上面的你都学会了,我觉得你可以试试用栈实现队列,尽管我会写在后面,何不尝试一下自己动手呢,说不定也是需要两个栈呢~
二、使用栈实现队列
我们学习了栈和队列,那么我们如何使用栈实现队列呢?上面使用队列实现栈我们使用了两个队列,那这个我们也可以朝那个方向去想,可以使用两个栈来实现队列,我们接下来直接来画图理解:
可以看到的是我们同样也创建了两个栈,不同的是,并不是每个栈都能够“出队列”,我们将其中一个栈专门用来“入队列”,另一个专门用来“出队列”,这样简单也不会打乱其顺序,我直接将代码放在下面,相信同学们自行能够理解~
MyQueue* myQueueCreate()
{
MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue));
STInit(&(obj->pushst));
STInit(&(obj->popst));
return obj;
}void myQueuePush(MyQueue* obj, STDataType x)
{
STPush(&(obj->pushst), x);
}int myQueuePop(MyQueue* obj) {
int front = myQueuePeek(obj);
STPop(&(obj->popst));
return front;
}int myQueuePeek(MyQueue* obj) {
if (STEmpty(&(obj->popst)))
{
while (!STEmpty(&obj->pushst))
{
int top = STTop(&(obj->pushst));
STPush(&(obj->popst), top);
STPop(&(obj->pushst));
}
}
return STTop(&(obj->popst));
}bool myQueueEmpty(MyQueue* obj) {
return STEmpty(&(obj->pushst)) && STEmpty(&(obj->popst));
}void myQueueFree(MyQueue* obj) {
STDestroy(&(obj->pushst));
STDestroy(&(obj->popst));
free(obj);
}
上面的每个函数名都采用了一样的书写方式,相信能够看懂,对一些代码有疑问的也欢迎大家咨询我,或者在评论区讨论!